diff --git a/bootstrap/README.org b/bootstrap/README.org index 924e6de..e3de6a8 100644 --- a/bootstrap/README.org +++ b/bootstrap/README.org @@ -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 diff --git a/bootstrap/pkg/core/block.go b/bootstrap/pkg/core/block.go index 0a93723..160b7c5 100644 --- a/bootstrap/pkg/core/block.go +++ b/bootstrap/pkg/core/block.go @@ -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, diff --git a/bootstrap/pkg/core/coll.go b/bootstrap/pkg/core/coll.go index e1bb4ad..640fa65 100644 --- a/bootstrap/pkg/core/coll.go +++ b/bootstrap/pkg/core/coll.go @@ -18,6 +18,7 @@ along with this program. If not, see . 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 diff --git a/bootstrap/pkg/core/core.go b/bootstrap/pkg/core/core.go index d9991e0..8d172da 100644 --- a/bootstrap/pkg/core/core.go +++ b/bootstrap/pkg/core/core.go @@ -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) diff --git a/bootstrap/pkg/core/def.go b/bootstrap/pkg/core/def.go deleted file mode 100644 index 2d50f16..0000000 --- a/bootstrap/pkg/core/def.go +++ /dev/null @@ -1,52 +0,0 @@ -/* - Serene --- Yet an other Lisp - -Copyright (c) 2020 Sameer Rahmani - -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 . -*/ - -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") -} diff --git a/bootstrap/pkg/core/eval.go b/bootstrap/pkg/core/eval.go index 02269e8..e1fbbcc 100644 --- a/bootstrap/pkg/core/eval.go +++ b/bootstrap/pkg/core/eval.go @@ -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 } diff --git a/bootstrap/pkg/core/function.go b/bootstrap/pkg/core/function.go index d2ca82b..0e5c12f 100644 --- a/bootstrap/pkg/core/function.go +++ b/bootstrap/pkg/core/function.go @@ -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 diff --git a/bootstrap/pkg/core/nil.go b/bootstrap/pkg/core/nil.go index 1a9dca0..0eb3fa0 100644 --- a/bootstrap/pkg/core/nil.go +++ b/bootstrap/pkg/core/nil.go @@ -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 { diff --git a/bootstrap/pkg/core/parser.go b/bootstrap/pkg/core/parser.go index 143632f..6ea67d9 100644 --- a/bootstrap/pkg/core/parser.go +++ b/bootstrap/pkg/core/parser.go @@ -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 { diff --git a/bootstrap/pkg/core/sforms.go b/bootstrap/pkg/core/sforms.go index dd8c387..5851014 100644 --- a/bootstrap/pkg/core/sforms.go +++ b/bootstrap/pkg/core/sforms.go @@ -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 - } diff --git a/bootstrap/pkg/core/types.go b/bootstrap/pkg/core/types.go index cde07bf..7313014 100644 --- a/bootstrap/pkg/core/types.go +++ b/bootstrap/pkg/core/types.go @@ -16,8 +16,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -// 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 }