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
}