Add some documentation for the core package
This commit is contained in:
parent
468b446a4f
commit
a2725ba412
|
@ -1,4 +1,11 @@
|
|||
* Serene lang
|
||||
|
||||
** Development hint
|
||||
|
||||
*** Use Make... functions
|
||||
In order to create a new value in any type use the designated Make function, for example: MakeList
|
||||
|
||||
|
||||
** Setup development environment
|
||||
*** Emacs
|
||||
[[https://github.com/brotzeit/rustic][Rustic]] is highly recommended. Just install it and install the dependencies necessary
|
||||
|
|
|
@ -25,6 +25,10 @@ import (
|
|||
"serene-lang.org/bootstrap/pkg/ast"
|
||||
)
|
||||
|
||||
// Block struct represents a group of forms. Don't confuse it with
|
||||
// code blocks from other languages that specify a block using curly
|
||||
// brackets and indentation.
|
||||
// Blocks in serene are just a group of forms and nothing more.
|
||||
type Block struct {
|
||||
body []IExpr
|
||||
}
|
||||
|
@ -60,6 +64,7 @@ func (b *Block) SetContent(body []IExpr) {
|
|||
b.body = body
|
||||
}
|
||||
|
||||
// Append the given expr `form` to the block
|
||||
func (b *Block) Append(form IExpr) {
|
||||
b.body = append(b.body, form)
|
||||
}
|
||||
|
@ -68,10 +73,13 @@ func (b *Block) Count() int {
|
|||
return len(b.body)
|
||||
}
|
||||
|
||||
// MakeEmptyBlock creates an empty block
|
||||
func MakeEmptyBlock() *Block {
|
||||
return &Block{}
|
||||
}
|
||||
|
||||
// MakeBlock creates a block that holds the given array of
|
||||
// forms `body`.
|
||||
func MakeBlock(body []IExpr) *Block {
|
||||
return &Block{
|
||||
body: body,
|
||||
|
|
|
@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
package core
|
||||
|
||||
// ISeq is an interface describing a sequence of forms
|
||||
type ISeq interface {
|
||||
First() IExpr
|
||||
Rest() ISeq
|
||||
|
@ -27,6 +28,7 @@ type ICountable interface {
|
|||
Count() int
|
||||
}
|
||||
|
||||
// IColl describes a collection of values. A finite collection.
|
||||
type IColl interface {
|
||||
ISeq
|
||||
ICountable
|
||||
|
|
|
@ -34,6 +34,7 @@ func rep(rt *Runtime, line string) {
|
|||
fmt.Println(err)
|
||||
}
|
||||
|
||||
// Debug data, ugly right ? :))
|
||||
if rt.IsDebugMode() {
|
||||
fmt.Println("\n### DEBUG ###")
|
||||
Print(rt, ast)
|
||||
|
@ -53,6 +54,8 @@ func rep(rt *Runtime, line string) {
|
|||
Replace the readline implementation with go-prompt.
|
||||
*/
|
||||
|
||||
// REPL executes a Read Eval Print Loop locally reading from stdin and
|
||||
// writing to stdout
|
||||
func REPL(debug bool) {
|
||||
rt := MakeRuntime(debug)
|
||||
rt.CreateNS("user", "REPL", true)
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
Serene --- Yet an other Lisp
|
||||
|
||||
Copyright (c) 2020 Sameer Rahmani <lxsameer@gnu.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"serene-lang.org/bootstrap/pkg/ast"
|
||||
)
|
||||
|
||||
func Def(rt *Runtime, scope IScope, args *List) (IExpr, error) {
|
||||
switch args.Count() {
|
||||
case 2:
|
||||
name := args.First()
|
||||
|
||||
if name.GetType() != ast.Symbol {
|
||||
return nil, errors.New("The first argument of 'def' has to be a symbol")
|
||||
}
|
||||
|
||||
sym := name.(*Symbol)
|
||||
|
||||
//value = args.Rest().(*List).First()
|
||||
valueExpr := args.Rest().First()
|
||||
value, err := EvalForms(rt, scope, valueExpr)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns := rt.CurrentNS()
|
||||
ns.DefineGlobal(sym.GetName(), value, true)
|
||||
return sym, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("'def' form need at least 2 arguments")
|
||||
}
|
|
@ -25,7 +25,11 @@ import (
|
|||
"serene-lang.org/bootstrap/pkg/ast"
|
||||
)
|
||||
|
||||
func EvalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, error) {
|
||||
// evalForm evaluates the given expression `form` by a slightly different
|
||||
// evaluation rules. For example if `form` is a list instead of the formal
|
||||
// evaluation of a list it will evaluate all the elements and return the
|
||||
// evaluated list
|
||||
func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, error) {
|
||||
|
||||
switch form.GetType() {
|
||||
case ast.Nil:
|
||||
|
@ -48,11 +52,7 @@ func EvalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, error) {
|
|||
|
||||
return expr.Value, nil
|
||||
|
||||
// List evaluation rules:
|
||||
// * The first element of the list has to be an expression which implements `ICallable`
|
||||
// * The rest o the elements have to be evaluated only after we have determind the the
|
||||
// first element is `ICallable` and it's not a macro or special form.
|
||||
// * An empty list evaluates to itself.
|
||||
// Evaluate all the elements in the list instead of following the lisp convention
|
||||
case ast.List:
|
||||
var result []IExpr
|
||||
|
||||
|
@ -78,14 +78,29 @@ func EvalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, error) {
|
|||
|
||||
}
|
||||
|
||||
// EvalForms evaluates the given expr `expressions` (it can be a list, block, symbol or anything else)
|
||||
// with the given runtime `rt` and the scope `scope`.
|
||||
func EvalForms(rt *Runtime, scope IScope, expressions IExpr) (IExpr, error) {
|
||||
// EvalForms is the main and the most important evaluation function on Serene.
|
||||
// It's a long loooooooooooong function. Why? Well, Because we don't want to
|
||||
// waste call stack spots in order to have a well organized code.
|
||||
// In order to avoid stackoverflows and implement TCO ( which is a must for
|
||||
// a functional language we need to avoid unnecessary calls and keep as much
|
||||
// as possible in a loop.
|
||||
var ret IExpr
|
||||
var err error
|
||||
|
||||
tco:
|
||||
for {
|
||||
// The TCO loop is there to take advantage or the fact that
|
||||
// in order to call a function or a block we simply can change
|
||||
// the value of the `expressions` and `scope`
|
||||
var exprs []IExpr
|
||||
|
||||
// Block evaluation rules:
|
||||
// * If empty, return Nothing
|
||||
// * Otherwise evaluate the expressions in the block one by one
|
||||
// and return the last result
|
||||
if expressions.GetType() == ast.Block {
|
||||
if expressions.(*Block).Count() == 0 {
|
||||
return &Nothing, nil
|
||||
|
@ -94,33 +109,43 @@ tco:
|
|||
} else {
|
||||
exprs = []IExpr{expressions}
|
||||
}
|
||||
|
||||
body:
|
||||
for _, forms := range exprs {
|
||||
// Evaluating forms one by one
|
||||
|
||||
switch forms.GetType() {
|
||||
case ast.List:
|
||||
|
||||
default:
|
||||
ret, err = EvalForm(rt, scope, forms)
|
||||
break tco // return
|
||||
}
|
||||
|
||||
if forms.(*List).Count() == 0 {
|
||||
ret = &Nil
|
||||
break tco // return
|
||||
if forms.GetType() != ast.List {
|
||||
ret, err = evalForm(rt, scope, forms)
|
||||
break tco // return ret, err
|
||||
}
|
||||
|
||||
list := forms.(*List)
|
||||
|
||||
// Empty list evaluates to itself
|
||||
if list.Count() == 0 {
|
||||
ret = list
|
||||
break tco // return &Nil, nil
|
||||
}
|
||||
|
||||
rawFirst := list.First()
|
||||
sform := ""
|
||||
|
||||
// Handling special forms
|
||||
// Handling special forms by looking up the first
|
||||
// element of the list. If it is a symbol, Grab
|
||||
// the name and check it for build it forms.
|
||||
//
|
||||
// Note: If we don't care about recursion in any
|
||||
// case we can simply extract it to a function
|
||||
// for example in `def` since we are going to
|
||||
// evaluate the value separately, we don't care
|
||||
// about recursion because we're going to handle
|
||||
// it wen we're evaluating the value. But in the
|
||||
// case of let it's a different story.
|
||||
if rawFirst.GetType() == ast.Symbol {
|
||||
sform = rawFirst.(*Symbol).GetName()
|
||||
}
|
||||
|
||||
switch sform {
|
||||
|
||||
case "def":
|
||||
ret, err = Def(rt, scope, list.Rest().(*List))
|
||||
break tco // return
|
||||
|
@ -128,8 +153,13 @@ tco:
|
|||
case "fn":
|
||||
ret, err = Fn(rt, scope, list.Rest().(*List))
|
||||
break tco // return
|
||||
|
||||
// List evaluation rules:
|
||||
// * The first element of the list has to be an expression which is callable
|
||||
// * An empty list evaluates to itself.
|
||||
default:
|
||||
exprs, e := EvalForm(rt, scope, list)
|
||||
// Evaluating all the elements of the list
|
||||
exprs, e := evalForm(rt, scope, list)
|
||||
if e != nil {
|
||||
err = e
|
||||
ret = nil
|
||||
|
@ -140,6 +170,11 @@ tco:
|
|||
|
||||
switch f.GetType() {
|
||||
case ast.Fn:
|
||||
// If the first element of the evaluated list is a function
|
||||
// create a scope for it by creating the binding to the given
|
||||
// parameters in the new scope and set the parent of it to
|
||||
// the scope which the function defined in and then set the
|
||||
// `expressions` to the body of function and loop again
|
||||
fn := f.(*Function)
|
||||
if e != nil {
|
||||
err = e
|
||||
|
@ -147,7 +182,7 @@ tco:
|
|||
break body //return
|
||||
|
||||
}
|
||||
//argList, _ := args.(*List)
|
||||
|
||||
argList := exprs.(*List).Rest().(*List)
|
||||
|
||||
scope, e = MakeFnScope(fn.GetScope(), fn.GetParams(), argList)
|
||||
|
@ -171,8 +206,15 @@ tco:
|
|||
return ret, err
|
||||
}
|
||||
|
||||
// Eval the given `Block` of code with the given runtime `rt`.
|
||||
// The Important part here is that any expression that we need
|
||||
// to Eval has to be wrapped in a Block. Don't confused the
|
||||
// concept of Block with blocks from other languages which
|
||||
// specify by using `{}` or indent or what ever. Blocks in terms
|
||||
// of Serene are just arrays of expressions and nothing more.
|
||||
func Eval(rt *Runtime, forms *Block) (IExpr, error) {
|
||||
if forms.Count() == 0 {
|
||||
// Nothing is literally Nothing
|
||||
return &Nothing, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -25,16 +25,27 @@ import (
|
|||
"serene-lang.org/bootstrap/pkg/ast"
|
||||
)
|
||||
|
||||
type ICallable interface {
|
||||
Apply(rt *Runtime, scope IScope, args *List) (IExpr, error)
|
||||
}
|
||||
|
||||
// Function struct represent a user defined function.
|
||||
type Function struct {
|
||||
// Node struct holds the necessary functions to make
|
||||
// Functions locatable
|
||||
Node
|
||||
name string
|
||||
scope IScope
|
||||
|
||||
// Name of the function, it can be empty and it has to be
|
||||
// set via `def`
|
||||
name string
|
||||
|
||||
// Parent scope of the function. The scope which the function
|
||||
// is defined in
|
||||
scope IScope
|
||||
|
||||
// A collection of arguments. Why IColl? because we can use
|
||||
// Lists and Vectors for the argument lists. Maybe even
|
||||
// hashmaps in future.
|
||||
params IColl
|
||||
body *Block
|
||||
|
||||
// A reference to the body block of the function
|
||||
body *Block
|
||||
}
|
||||
|
||||
func (f *Function) GetType() ast.NodeType {
|
||||
|
@ -66,6 +77,8 @@ func (f *Function) GetBody() *Block {
|
|||
return f.body
|
||||
}
|
||||
|
||||
// MakeFunction Create a function with the given `params` and `body` in
|
||||
// the given `scope`.
|
||||
func MakeFunction(scope IScope, params IColl, body *Block) *Function {
|
||||
return &Function{
|
||||
scope: scope,
|
||||
|
@ -74,11 +87,14 @@ func MakeFunction(scope IScope, params IColl, body *Block) *Function {
|
|||
}
|
||||
}
|
||||
|
||||
// MakeFnScope a new scope for the body of a function. It binds the `bindings`
|
||||
// to the given `values`.
|
||||
func MakeFnScope(parent IScope, bindings IColl, values IColl) (*Scope, error) {
|
||||
fmt.Printf("%s %s\n", bindings, values)
|
||||
scope := MakeScope(parent.(*Scope))
|
||||
|
||||
// TODO: Implement destructuring
|
||||
|
||||
if bindings.Count() > values.Count() {
|
||||
return nil, errors.New("'binding' and 'valuse' size don't match")
|
||||
}
|
||||
|
@ -87,6 +103,10 @@ func MakeFnScope(parent IScope, bindings IColl, values IColl) (*Scope, error) {
|
|||
exprs := values.ToSlice()
|
||||
|
||||
for i := 0; i < len(binds); i += 1 {
|
||||
// If an argument started with char `&` use it to represent
|
||||
// rest of values.
|
||||
//
|
||||
// for example: `(fn (x y &z) ...)`
|
||||
if binds[i].GetType() == ast.Symbol && binds[i].(*Symbol).IsRestable() {
|
||||
scope.Insert(binds[i+1].(*Symbol).GetName(), MakeList(exprs[i:]), false)
|
||||
break
|
||||
|
|
|
@ -22,6 +22,7 @@ import "serene-lang.org/bootstrap/pkg/ast"
|
|||
|
||||
type NilType struct{}
|
||||
|
||||
// Nil is just Nil not `null` or anything
|
||||
var Nil = NilType{}
|
||||
|
||||
func (n NilType) GetType() ast.NodeType {
|
||||
|
|
|
@ -40,6 +40,8 @@ type StringParser struct {
|
|||
}
|
||||
|
||||
// Implementing IParsable for StringParser ---
|
||||
|
||||
// Returns the next character in the buffer
|
||||
func (sp *StringParser) next(skipWhitespace bool) *string {
|
||||
if sp.pos >= len(sp.buffer) {
|
||||
return nil
|
||||
|
@ -54,6 +56,7 @@ func (sp *StringParser) next(skipWhitespace bool) *string {
|
|||
return &char
|
||||
}
|
||||
|
||||
// Return the character of the buffer without consuming it
|
||||
func (sp *StringParser) peek(skipWhitespace bool) *string {
|
||||
if sp.pos >= len(sp.buffer) {
|
||||
return nil
|
||||
|
@ -67,6 +70,7 @@ func (sp *StringParser) peek(skipWhitespace bool) *string {
|
|||
return &c
|
||||
}
|
||||
|
||||
// Move the char pointer back by one character
|
||||
func (sp *StringParser) back() {
|
||||
if sp.pos > 0 {
|
||||
sp.pos = sp.pos - 1
|
||||
|
@ -78,6 +82,7 @@ func (sp *StringParser) GetLocation() int {
|
|||
}
|
||||
|
||||
// END: IParsable ---
|
||||
|
||||
func contains(s []rune, c rune) bool {
|
||||
for _, v := range s {
|
||||
if v == c {
|
||||
|
|
|
@ -24,6 +24,40 @@ import (
|
|||
"serene-lang.org/bootstrap/pkg/ast"
|
||||
)
|
||||
|
||||
// Def defines a global binding in the current namespace. The first
|
||||
// arguments in `args` has to be a symbol ( none ns qualified ) and
|
||||
// the second param should be the value of the binding
|
||||
func Def(rt *Runtime, scope IScope, args *List) (IExpr, error) {
|
||||
|
||||
// TODO: Add support for docstrings and meta
|
||||
|
||||
switch args.Count() {
|
||||
case 2:
|
||||
name := args.First()
|
||||
|
||||
if name.GetType() != ast.Symbol {
|
||||
return nil, errors.New("the first argument of 'def' has to be a symbol")
|
||||
}
|
||||
|
||||
sym := name.(*Symbol)
|
||||
|
||||
valueExpr := args.Rest().First()
|
||||
value, err := EvalForms(rt, scope, valueExpr)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns := rt.CurrentNS()
|
||||
ns.DefineGlobal(sym.GetName(), value, true)
|
||||
return sym, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("'def' form need at least 2 arguments")
|
||||
}
|
||||
|
||||
// Fn defines a function inside the given scope `scope` with the given `args`.
|
||||
// `args` contains the arugment list, docstring and body of the function.
|
||||
func Fn(rt *Runtime, scope IScope, args *List) (IExpr, error) {
|
||||
|
||||
if args.Count() < 1 {
|
||||
|
@ -46,5 +80,4 @@ func Fn(rt *Runtime, scope IScope, args *List) (IExpr, error) {
|
|||
}
|
||||
|
||||
return MakeFunction(scope, params, body), nil
|
||||
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@ You should have received a copy of the GNU General Public License
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Package types provides the type interface of Serene. All the types
|
||||
// in Serene are directly AST Nodes as well.
|
||||
package core
|
||||
|
||||
import (
|
||||
|
@ -26,14 +24,22 @@ import (
|
|||
"serene-lang.org/bootstrap/pkg/ast"
|
||||
)
|
||||
|
||||
// IPrintable is the interface which any value that wants to have a string
|
||||
// representation has to implement. The `print` family functions will use
|
||||
// this interface to convert forms to string
|
||||
type IPrintable interface {
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
// IDebuggable is the interface designed for converting forms to a string
|
||||
// form which are meant to be used as debug data
|
||||
type IDebuggable interface {
|
||||
ToDebugStr() string
|
||||
}
|
||||
|
||||
// IExpr is the most important interface in Serene which basically represents
|
||||
// a VALUE in Serene. All the forms (beside special formss) has to implement
|
||||
// this interface.
|
||||
type IExpr interface {
|
||||
ast.ILocatable
|
||||
ast.ITypable
|
||||
|
@ -41,10 +47,13 @@ type IExpr interface {
|
|||
IDebuggable
|
||||
}
|
||||
|
||||
// Node struct is simply representing a Node in the AST which provides the
|
||||
// functionalities required to trace the code based on the location.
|
||||
type Node struct {
|
||||
location int
|
||||
}
|
||||
|
||||
// GetLocation returns the location of the Node in the source input
|
||||
func (n Node) GetLocation() int {
|
||||
return n.location
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue