Add some documentation for the core package

This commit is contained in:
Sameer Rahmani 2020-11-20 16:43:07 +00:00
parent 468b446a4f
commit a2725ba412
11 changed files with 161 additions and 83 deletions

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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")
}

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}