From 9d106d427855eb9ad0af106a29af7d56fd98b5cf Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Wed, 30 Dec 2020 17:50:00 +0000 Subject: [PATCH] Record the caller details in the stack frame --- bootstrap/examples/hello-world.srn | 2 +- bootstrap/go.mod | 1 + bootstrap/go.sum | 2 + bootstrap/pkg/ast/ast.go | 88 ++++++++++++++++++++++++++---- bootstrap/pkg/core/block.go | 2 +- bootstrap/pkg/core/builtins.go | 6 +- bootstrap/pkg/core/call_stack.go | 79 +++++++++++++++++++++------ bootstrap/pkg/core/core.go | 28 +++++++--- bootstrap/pkg/core/errors.go | 29 ++++------ bootstrap/pkg/core/eval.go | 60 +++++++++++--------- bootstrap/pkg/core/function.go | 28 ++++++++-- bootstrap/pkg/core/keyword.go | 2 +- bootstrap/pkg/core/list.go | 21 +++++-- bootstrap/pkg/core/namespace.go | 17 +++--- bootstrap/pkg/core/nothing.go | 2 +- bootstrap/pkg/core/parser.go | 71 ++++++++++++++++++------ bootstrap/pkg/core/printer.go | 34 +++++++++++- bootstrap/pkg/core/quasiquote.go | 11 +++- bootstrap/pkg/core/reader.go | 4 +- bootstrap/pkg/core/runtime.go | 9 +-- bootstrap/pkg/core/sforms.go | 26 ++++----- bootstrap/pkg/core/types.go | 28 ++++++++-- dev.org | 1 + 23 files changed, 399 insertions(+), 152 deletions(-) diff --git a/bootstrap/examples/hello-world.srn b/bootstrap/examples/hello-world.srn index 460fdd9..f206f87 100644 --- a/bootstrap/examples/hello-world.srn +++ b/bootstrap/examples/hello-world.srn @@ -9,7 +9,7 @@ (name args &body) (list 'def name (cons 'fn (cons args body)))) -(defn pp (x) +(defn pp (x y) (println x)) (def main diff --git a/bootstrap/go.mod b/bootstrap/go.mod index 24fb38b..8d2dafb 100644 --- a/bootstrap/go.mod +++ b/bootstrap/go.mod @@ -4,6 +4,7 @@ go 1.15 require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/gookit/color v1.3.5 github.com/rjeczalik/pkgconfig v0.0.0-20190903131546-94d388dab445 github.com/spf13/cobra v1.1.1 github.com/spf13/viper v1.7.1 diff --git a/bootstrap/go.sum b/bootstrap/go.sum index 04c2ecb..0a71ec0 100644 --- a/bootstrap/go.sum +++ b/bootstrap/go.sum @@ -68,6 +68,8 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gookit/color v1.3.5 h1:1nszcmDVrfti1Su5fhtuS5YBs/Xs6v8UIi0bJ/2oDHY= +github.com/gookit/color v1.3.5/go.mod h1:GqqLKF1le3EfrbHbYsYa5WdLqfc/PHMdMRbt6tMnqIc= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= diff --git a/bootstrap/pkg/ast/ast.go b/bootstrap/pkg/ast/ast.go index 298ce47..8bd421f 100644 --- a/bootstrap/pkg/ast/ast.go +++ b/bootstrap/pkg/ast/ast.go @@ -20,6 +20,11 @@ along with this program. If not, see . // Serene's AST. package ast +import ( + "sort" + "strings" +) + type NodeType int const ( @@ -40,13 +45,42 @@ const ( ) +type Source struct { + Buffer *[]string + // It can be the path to the source file or something like "*in*" + // for standard in + Path string + LineIndex *[]int +} + +func (s *Source) GetLine(linenum int) string { + lines := strings.Split(strings.Join(*s.Buffer, ""), "\n") + return lines[linenum-1] +} + +func (s *Source) LineNumberFor(pos int) int { + if pos < 0 { + return -1 + } + + return sort.Search(len(*s.LineIndex), func(i int) bool { + if i == 0 { + return pos < (*s.LineIndex)[i] + } else { + return (*s.LineIndex)[i-1] < pos && pos < (*s.LineIndex)[i] + } + }) +} + type Location struct { start int end int - source *[]string + source Source knownLocation bool } +var UnknownLocation *Location = &Location{knownLocation: false} + func (l *Location) GetStart() int { return l.start } @@ -55,8 +89,42 @@ func (l *Location) GetEnd() int { return l.end } -func (l *Location) GetSource() *[]string { - return l.source +func (l *Location) GetSource() *Source { + return &l.source +} + +func (l *Location) IncStart(x int) { + if x+l.start < len(*l.source.Buffer) { + l.start += x + } else { + l.start = len(*l.source.Buffer) - 1 + } +} + +func (l *Location) DecStart(x int) { + if l.start-x >= 0 { + l.start -= x + } else { + l.start = 0 + } + +} + +func (l *Location) IncEnd(x int) { + if x+l.end < len(*l.source.Buffer) { + l.end += x + } else { + l.end = len(*l.source.Buffer) - 1 + } + +} + +func (l *Location) DecEnd(x int) { + if l.end-x >= 0 { + l.end -= x + } else { + l.end = 0 + } } func (l *Location) IsKnownLocaiton() bool { @@ -64,12 +132,12 @@ func (l *Location) IsKnownLocaiton() bool { } type ILocatable interface { - GetLocation() Location + GetLocation() *Location } -func MakeLocation(input *[]string, start int, end int) Location { - return Location{ - source: input, +func MakeLocation(input *Source, start int, end int) *Location { + return &Location{ + source: *input, start: start, end: end, knownLocation: true, @@ -80,8 +148,6 @@ type ITypable interface { GetType() NodeType } -func MakeUnknownLocation() Location { - return Location{ - knownLocation: false, - } +func MakeUnknownLocation() *Location { + return UnknownLocation } diff --git a/bootstrap/pkg/core/block.go b/bootstrap/pkg/core/block.go index 308f9a3..8b7fef1 100644 --- a/bootstrap/pkg/core/block.go +++ b/bootstrap/pkg/core/block.go @@ -51,7 +51,7 @@ func (b *Block) ToDebugStr() string { return fmt.Sprintf("%#v", b) } -func (b *Block) GetLocation() ast.Location { +func (b *Block) GetLocation() *ast.Location { if len(b.body) > 0 { return b.body[0].GetLocation() } diff --git a/bootstrap/pkg/core/builtins.go b/bootstrap/pkg/core/builtins.go index 08cb606..f96a87d 100644 --- a/bootstrap/pkg/core/builtins.go +++ b/bootstrap/pkg/core/builtins.go @@ -53,9 +53,9 @@ func PrintlnNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IErr func RequireNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) { switch args.Count() { case 0: - return nil, MakeErrorFor(rt, args, "'require' function is missing") + return nil, MakeError(rt, args, "'require' function is missing") case 1: - return nil, MakeErrorFor(rt, args.First(), "'require' function needs at least one argument") + return nil, MakeError(rt, args.First(), "'require' function needs at least one argument") default: } @@ -74,7 +74,7 @@ func RequireNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IErr func HashNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) { if args.Count() != 2 { - return nil, MakeErrorFor(rt, args.First(), "'hash' function needs exactly one argument") + return nil, MakeError(rt, args.First(), "'hash' function needs exactly one argument") } expr := args.Rest().First() diff --git a/bootstrap/pkg/core/call_stack.go b/bootstrap/pkg/core/call_stack.go index 51b5883..d8023c7 100644 --- a/bootstrap/pkg/core/call_stack.go +++ b/bootstrap/pkg/core/call_stack.go @@ -33,38 +33,51 @@ package core // compare the stack items by their address, identity and location. // * Add support for iteration on the stack. -import "fmt" +import ( + "fmt" +) type ICallStack interface { // Push the given callable `f` to the stack Push(f IFn) IError - Pop() FnCall + Pop() *Frame + Peek() *Frame Count() uint } -type FnCall struct { - Fn IFn +type Frame struct { // Number of recursive calls to this function - count uint + Count uint + Fn IFn + Caller IExpr } +type TraceBack = []Frame + type CallStackItem struct { prev *CallStackItem - data FnCall + data Frame } type CallStack struct { + debug bool head *CallStackItem count uint - debug bool } func (c *CallStack) Count() uint { return c.count } -func (c *CallStack) Push(f IFn) IError { +func (c *CallStack) GetCurrentFn() IFn { + if c.head == nil { + return nil + } + return c.head.data.Fn +} + +func (c *CallStack) Push(caller IExpr, f IFn) IError { if c.debug { fmt.Println("[Stack] -->", f) } @@ -73,12 +86,17 @@ func (c *CallStack) Push(f IFn) IError { return MakePlainError("Can't push 'nil' pointer to the call stack.") } + if caller == nil { + return MakePlainError("Can't push 'nil' pointer to the call stack for the caller.") + } + // Empty Stack if c.head == nil { c.head = &CallStackItem{ - data: FnCall{ - Fn: f, - count: 0, + data: Frame{ + Fn: f, + Caller: caller, + Count: 0, }, } c.count++ @@ -87,15 +105,16 @@ func (c *CallStack) Push(f IFn) IError { nodeData := &c.head.data // If the same function was on top of the stack - if nodeData.Fn == f { + if nodeData.Fn == f && caller == nodeData.Caller { // TODO: expand the check here to support address and location as well - nodeData.count++ + nodeData.Count++ } else { c.head = &CallStackItem{ prev: c.head, - data: FnCall{ - Fn: f, - count: 0, + data: Frame{ + Fn: f, + Caller: caller, + Count: 0, }, } c.count++ @@ -103,7 +122,7 @@ func (c *CallStack) Push(f IFn) IError { return nil } -func (c *CallStack) Pop() *FnCall { +func (c *CallStack) Pop() *Frame { if c.head == nil { if c.debug { fmt.Println("[Stack] <-- nil") @@ -120,6 +139,32 @@ func (c *CallStack) Pop() *FnCall { return &result.data } +func (c *CallStack) Peek() *Frame { + if c.head == nil { + if c.debug { + fmt.Println("[Stack] <-- nil") + } + return nil + } + + result := c.head + return &result.data +} + +func (c *CallStack) ToTraceBack() *TraceBack { + var tr TraceBack + item := c.head + for { + if item == nil { + break + } + tr = append(tr, item.data) + item = item.prev + } + + return &tr +} + func MakeCallStack(debugMode bool) CallStack { return CallStack{ count: 0, diff --git a/bootstrap/pkg/core/core.go b/bootstrap/pkg/core/core.go index e5aa750..ac40a56 100644 --- a/bootstrap/pkg/core/core.go +++ b/bootstrap/pkg/core/core.go @@ -23,13 +23,14 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/chzyer/readline" "serene-lang.org/bootstrap/pkg/ast" ) func rep(rt *Runtime, line string) { - ast, err := ReadString(line) + ast, err := ReadString("*REPL*", line) if err != nil { PrintError(rt, err) @@ -120,7 +121,17 @@ func Run(flags map[string]bool, args []string) { } ns := args[0] - loadedNS, err := requireNS(rt, ns) + nsAsBuffer := strings.Split(ns, "") + source := &ast.Source{Buffer: &nsAsBuffer, Path: "*input-argument*"} + node := MakeNode(source, 0, len(ns)) + nsSym, err := MakeSymbol(node, ns) + + if err != nil { + PrintError(rt, err) + os.Exit(1) + } + + loadedNS, err := requireNS(rt, nsSym) if err != nil { PrintError(rt, err) @@ -133,6 +144,7 @@ func Run(flags map[string]bool, args []string) { if !inserted { err := MakeError( rt, + loadedNS, fmt.Sprintf( "the namespace '%s' didn't get inserted in the runtime.", loadedNS.GetName()), @@ -163,24 +175,22 @@ func Run(flags map[string]bool, args []string) { mainFn := mainBinding.Value.(*Function) var fnArgs []IExpr + var argNode Node if len(args) > 1 { for _, arg := range args[1:] { node := MakeNodeFromExpr(mainFn) fnArgs = append(fnArgs, MakeString(node, arg)) } + argNode = MakeNodeFromExprs(fnArgs) + } else { + argNode = node } - //rt.Stack.Push(mainFn) - _, err = mainFn.Apply(rt, loadedNS.GetRootScope(), mainFn.Node, MakeList(fnArgs)) + _, err = mainFn.Apply(rt, loadedNS.GetRootScope(), mainFn.Node, MakeList(argNode, fnArgs)) if err != nil { PrintError(rt, err) os.Exit(1) } - - // rt.Stack.Pop() - // if rt.Stack.Count() != 0 { - // panic("Call stack is not empty.") - // } } diff --git a/bootstrap/pkg/core/errors.go b/bootstrap/pkg/core/errors.go index 7e3d4aa..35f8a83 100644 --- a/bootstrap/pkg/core/errors.go +++ b/bootstrap/pkg/core/errors.go @@ -49,6 +49,7 @@ type IError interface { IRepresentable IDebuggable + GetStackTrace() *TraceBack // To wrap Golan rrrrors WithError(err error) IError @@ -62,6 +63,7 @@ type Error struct { Node WrappedErr error msg string + trace *TraceBack } func (e *Error) String() string { @@ -89,32 +91,25 @@ func (e *Error) Error() string { return e.msg } +func (e *Error) GetStackTrace() *TraceBack { + return e.trace +} + func MakePlainError(msg string) IError { return &Error{ msg: msg, } } -// MakeError creates an Error without any location. -func MakeError(rt *Runtime, msg string) IError { - return MakePlainError(msg) -} - -// MakeErrorFor creates an Error which points to the given IExpr `e` as +// MakeError creates an Error which points to the given IExpr `e` as // the root of the error. -func MakeErrorFor(rt *Runtime, e IExpr, msg string) IError { - loc := e.GetLocation() +func MakeError(rt *Runtime, e IExpr, msg string) IError { + trace := append(*rt.Stack.ToTraceBack(), Frame{0, rt.Stack.GetCurrentFn(), e}) return &Error{ - Node: MakeNodeFromLocation(loc), - msg: msg, - } -} - -//MakeRuntimeErrorf is a helper function which works like `fmt.Errorf` -func MakeRuntimeErrorf(rt *Runtime, msg string, a ...interface{}) IError { - return &Error{ - msg: fmt.Sprintf(msg, a...), + Node: MakeNodeFromExpr(e), + msg: msg, + trace: &trace, } } diff --git a/bootstrap/pkg/core/eval.go b/bootstrap/pkg/core/eval.go index a7dd0e4..b73a950 100644 --- a/bootstrap/pkg/core/eval.go +++ b/bootstrap/pkg/core/eval.go @@ -84,7 +84,7 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) { if sym.IsNSQualified() { // Whether a namespace with the given alias loaded or not if !rt.CurrentNS().hasExternal(sym.GetNSPart()) { - return nil, MakeErrorFor(rt, sym, + return nil, MakeError(rt, sym, fmt.Sprintf("Namespace '%s' is no loaded", sym.GetNSPart()), ) } @@ -97,11 +97,14 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) { } if expr == nil { - return nil, MakeRuntimeErrorf( + return nil, MakeError( rt, - "can't resolve symbol '%s' in ns '%s'", - symbolName, - nsName, + sym, + fmt.Sprintf( + "can't resolve symbol '%s' in ns '%s'", + symbolName, + nsName, + ), ) } @@ -126,11 +129,12 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) { break } } - return MakeList(result), nil + + return MakeList(MakeNodeFromExpr(lst), result), nil } // Default case - return nil, MakeError(rt, fmt.Sprintf("support for '%d' is not implemented", form.GetType())) + return nil, MakeError(rt, form, fmt.Sprintf("support for '%d' is not implemented", form.GetType())) } // EvalForms evaluates the given expr `expressions` (it can be a list, block, symbol or anything else) @@ -185,7 +189,6 @@ tco: body: for i := 0; i < len(exprs); i++ { - //for i, forms := range exprs { forms := exprs[i] executionScope := forms.GetExecutionScope() scope := scope @@ -275,7 +278,7 @@ tco: case "quote": // Including the `quote` itself if list.Count() != 2 { - return nil, MakeErrorFor(rt, list, "'quote' quote only accepts one argument.") + return nil, MakeError(rt, list, "'quote' quote only accepts one argument.") } ret = list.Rest().First() err = nil @@ -310,12 +313,18 @@ tco: result := []IExpr{} for _, lst := range lists { if lst.GetType() != ast.List { - return nil, MakeErrorFor(rt, lst, fmt.Sprintf("don't know how to concat '%s'", lst.String())) + return nil, MakeError(rt, lst, fmt.Sprintf("don't know how to concat '%s'", lst.String())) } result = append(result, lst.(*List).ToSlice()...) } - ret, err = MakeList(result), nil + + node := MakeNodeFromExpr(list) + if len(result) > 0 { + node = MakeNodeFromExprs(result) + } + + ret, err = MakeList(node, result), nil continue body // no rewrite // TODO: Implement `list` in serene itself when we have destructuring available @@ -324,7 +333,7 @@ tco: // given in the second argument. case "cons": if list.Count() != 3 { - return nil, MakeErrorFor(rt, list, "'cons' needs exactly 3 arguments") + return nil, MakeError(rt, list, "'cons' needs exactly 3 arguments") } evaledForms, err := evalForm(rt, scope, list.Rest().(*List)) @@ -335,7 +344,7 @@ tco: coll, ok := evaledForms.(*List).Rest().First().(IColl) if !ok { - return nil, MakeErrorFor(rt, list, "second arg of 'cons' has to be a collection") + return nil, MakeError(rt, list, "second arg of 'cons' has to be a collection") } ret, err = coll.Cons(evaledForms.(*List).First()), nil @@ -366,7 +375,7 @@ tco: // as a macro and returns the expanded forms. case "macroexpand": if list.Count() != 2 { - return nil, MakeErrorFor(rt, list, "'macroexpand' needs exactly one argument.") + return nil, MakeError(rt, list, "'macroexpand' needs exactly one argument.") } evaledForm, e := evalForm(rt, scope, list.Rest().(*List)) @@ -381,7 +390,7 @@ tco: // * It needs at least a collection of arguments // * Defines an anonymous function. case "fn": - ret, err = Fn(rt, scope, list.Rest().(*List)) + ret, err = Fn(rt, scope, list) continue body // no rewrite // `if` evaluation rules: @@ -392,7 +401,7 @@ tco: case "if": args := list.Rest().(*List) if args.Count() != 3 { - return nil, MakeError(rt, "'if' needs exactly 3 aruments") + return nil, MakeError(rt, args, "'if' needs exactly 3 aruments") } pred, err := EvalForms(rt, scope, args.First()) @@ -436,7 +445,7 @@ tco: // the result. case "eval": if list.Count() != 2 { - return nil, MakeErrorFor(rt, list, "'eval' needs exactly 1 arguments") + return nil, MakeError(rt, list, "'eval' needs exactly 1 arguments") } form, err := evalForm(rt, scope, list.Rest().(*List)) if err != nil { @@ -457,7 +466,7 @@ tco: // which is the result of the last expre in `BODY` case "let": if list.Count() < 2 { - return nil, MakeError(rt, "'let' needs at list 1 aruments") + return nil, MakeError(rt, list, "'let' needs at list 1 aruments") } letScope := MakeScope(scope.(*Scope)) @@ -470,7 +479,7 @@ tco: body := list.Rest().Rest().(*List).ToSlice() if bindings.Count()%2 != 0 { - return nil, MakeError(rt, "'let' bindings has to have even number of forms.") + return nil, MakeError(rt, list.Rest().First(), "'let' bindings has to have even number of forms.") } for { @@ -485,7 +494,7 @@ tco: // TODO: We need to destruct the bindings here and remove this check // for the symbol type if name.GetType() != ast.Symbol { - err := MakeErrorFor(rt, name, "'let' doesn't support desbbtructuring yet, use a symbol.") + err := MakeError(rt, name, "'let' doesn't support desbbtructuring yet, use a symbol.") return nil, err } @@ -546,7 +555,7 @@ tco: break body //return } - rt.Stack.Push(fn) + rt.Stack.Push(list, fn) body := append( fn.GetBody().ToSlice(), // Add the PopStack instruction to clean up the stack after @@ -561,7 +570,8 @@ tco: // by the `NativeFunction` struct case ast.NativeFn: fn := f.(*NativeFunction) - rt.Stack.Push(fn) + + rt.Stack.Push(list, fn) ret, err = fn.Apply( rt, scope, @@ -572,7 +582,7 @@ tco: continue body // no rewrite default: - err = MakeError(rt, "don't know how to execute anything beside function") + err = MakeError(rt, f, "don't know how to execute anything beside function") ret = nil break tco } @@ -613,7 +623,7 @@ func EvalNSBody(rt *Runtime, ns *Namespace) (*Namespace, IError) { exprs := body.ToSlice() if len(exprs) == 0 { - return nil, MakeError(rt, fmt.Sprintf("the 'ns' form is missing from '%s'", ns.GetName())) + return nil, MakeError(rt, ns, fmt.Sprintf("the 'ns' form is missing from '%s'", ns.GetName())) } if exprs[0].GetType() == ast.List { @@ -627,5 +637,5 @@ func EvalNSBody(rt *Runtime, ns *Namespace) (*Namespace, IError) { } } - return nil, MakeError(rt, fmt.Sprintf("the 'ns' form is missing from '%s'", ns.GetName())) + return nil, MakeError(rt, ns, fmt.Sprintf("the 'ns' form is missing from '%s'", ns.GetName())) } diff --git a/bootstrap/pkg/core/function.go b/bootstrap/pkg/core/function.go index b183b1b..da357a4 100644 --- a/bootstrap/pkg/core/function.go +++ b/bootstrap/pkg/core/function.go @@ -48,8 +48,9 @@ import ( type nativeFnHandler = func(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) type IFn interface { - ast.ILocatable + IExpr Apply(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) + GetName() string } // Function struct represent a user defined function. @@ -142,8 +143,9 @@ func (f *Function) Apply(rt *Runtime, scope IScope, n Node, args *List) (IExpr, // MakeFunction Create a function with the given `params` and `body` in // the given `scope`. -func MakeFunction(scope IScope, params IColl, body *Block) *Function { +func MakeFunction(n Node, scope IScope, params IColl, body *Block) *Function { return &Function{ + Node: n, scope: scope, params: params, body: body, @@ -173,7 +175,8 @@ func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Sco fmt.Printf("[DEBUG] Mismatch on bindings and values: Bindings: %s, Values: %s\n", bindings, values) } - return nil, MakeError(rt, + fmt.Println("3333333", values.(IExpr).GetLocation(), bindings.(IExpr).GetLocation()) + return nil, MakeError(rt, values.(IExpr), fmt.Sprintf("expected '%d' arguments, got '%d'.", bindings.Count(), values.Count())) } @@ -185,20 +188,29 @@ func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Sco if binds[i].GetType() == ast.Symbol && binds[i].(*Symbol).IsRestable() { if i != len(binds)-1 { - return nil, MakeErrorFor(rt, binds[i], "The function argument with '&' has to be the last argument.") + return nil, MakeError(rt, binds[i], "The function argument with '&' has to be the last argument.") } // if the number of values are one less than the number of bindings // but the last binding is a Restable (e.g &x) the the last bindings // has to be an empty list. Note the check for number of vlaues comes // next. - rest := MakeEmptyList() + rest := MakeEmptyList(MakeNodeFromExpr(binds[i])) if i == len(exprs)-1 { // If the number of values matches the number of bindings // or it is more than that create a list from them // to pass it to the last argument that has to be Restable (e.g &x) - rest = MakeList(exprs[i:]) + elements := exprs[i:] + var node Node + + if len(elements) > 0 { + node = MakeNodeFromExprs(elements) + } else { + node = MakeNodeFromExpr(binds[i]) + } + + rest = MakeList(node, elements) } scope.Insert(binds[i].(*Symbol).GetName()[1:], rest, false) @@ -215,6 +227,10 @@ func (f *NativeFunction) GetType() ast.NodeType { return ast.NativeFn } +func (f *NativeFunction) GetName() string { + return f.name +} + func (f *NativeFunction) String() string { return fmt.Sprintf("", f.name, f) } diff --git a/bootstrap/pkg/core/keyword.go b/bootstrap/pkg/core/keyword.go index 629bebd..d2a1c39 100644 --- a/bootstrap/pkg/core/keyword.go +++ b/bootstrap/pkg/core/keyword.go @@ -159,7 +159,7 @@ func (k *Keyword) Eval(rt *Runtime, scope IScope) (*Keyword, IError) { } if aliasedNS == nil { - return nil, MakeErrorFor(rt, k, fmt.Sprintf("can't find the alias '%s' in the current namespace.", k.nsName)) + return nil, MakeError(rt, k, fmt.Sprintf("can't find the alias '%s' in the current namespace.", k.nsName)) } k.ns = aliasedNS return k, nil diff --git a/bootstrap/pkg/core/list.go b/bootstrap/pkg/core/list.go index e1b744e..1e3c2fc 100644 --- a/bootstrap/pkg/core/list.go +++ b/bootstrap/pkg/core/list.go @@ -70,9 +70,16 @@ func (l *List) First() IExpr { func (l *List) Rest() ISeq { if l.Count() < 2 { - return MakeEmptyList() + return MakeEmptyList(l.Node) } - return MakeList(l.exprs[1:]) + + rest := l.exprs[1:] + node := l.Node + if len(rest) > 0 { + node = MakeNodeFromExprs(rest) + } + + return MakeList(node, rest) } func (l *List) Hash() uint32 { @@ -97,8 +104,8 @@ func (l *List) ToSlice() []IExpr { } func (l *List) Cons(e IExpr) IExpr { - elems := l.ToSlice() - return MakeList(append([]IExpr{e}, elems...)) + elements := append([]IExpr{e}, l.ToSlice()...) + return MakeList(MakeNodeFromExprs(elements), elements) } // END: IColl --- @@ -118,14 +125,16 @@ func ListStartsWith(l *List, sym string) bool { return false } -func MakeList(elements []IExpr) *List { +func MakeList(n Node, elements []IExpr) *List { return &List{ + Node: n, exprs: elements, } } -func MakeEmptyList() *List { +func MakeEmptyList(n Node) *List { return &List{ + Node: n, exprs: []IExpr{}, } } diff --git a/bootstrap/pkg/core/namespace.go b/bootstrap/pkg/core/namespace.go index a185ab9..0a2b48e 100644 --- a/bootstrap/pkg/core/namespace.go +++ b/bootstrap/pkg/core/namespace.go @@ -56,7 +56,7 @@ func (n *Namespace) GetType() ast.NodeType { return ast.Namespace } -func (n *Namespace) GetLocation() ast.Location { +func (n *Namespace) GetLocation() *ast.Location { return ast.MakeUnknownLocation() } @@ -141,7 +141,7 @@ func (n *Namespace) getForms() *Block { // requireNS finds and loads the namespace addressed by the given // `ns` string. -func requireNS(rt *Runtime, ns string) (*Namespace, IError) { +func requireNS(rt *Runtime, ns *Symbol) (*Namespace, IError) { // TODO: use a hashing algorithm to avoid reloading an unchanged namespace loadedForms, err := rt.LoadNS(ns) @@ -155,11 +155,12 @@ func requireNS(rt *Runtime, ns string) (*Namespace, IError) { if body.Count() == 0 { return nil, MakeError( rt, + body, fmt.Sprintf("The '%s' ns source code doesn't start with an 'ns' form.", ns), ) } - namespace := MakeNS(ns, source) + namespace := MakeNS(ns.GetName(), source) namespace.setForms(body) return &namespace, nil @@ -188,21 +189,21 @@ func RequireNamespace(rt *Runtime, namespace IExpr) (IExpr, IError) { first := list.First() if first.GetType() != ast.Symbol { - return nil, MakeErrorFor(rt, first, "The first element has to be a symbol") + return nil, MakeError(rt, first, "The first element has to be a symbol") } second := list.Rest().First() if second.GetType() != ast.Symbol { - return nil, MakeErrorFor(rt, first, "The second element has to be a symbol") + return nil, MakeError(rt, first, "The second element has to be a symbol") } ns = first.(*Symbol) alias = second.(*Symbol).GetName() default: - return nil, MakeErrorFor(rt, ns, "Don't know how to load the given namespace") + return nil, MakeError(rt, ns, "Don't know how to load the given namespace") } - loadedNS, err := requireNS(rt, ns.GetName()) + loadedNS, err := requireNS(rt, ns) if err != nil { return nil, err @@ -217,6 +218,7 @@ func RequireNamespace(rt *Runtime, namespace IExpr) (IExpr, IError) { if !inserted { return nil, MakeError( rt, + loadedNS, fmt.Sprintf( "the namespace '%s' didn't get inserted in the runtime.", loadedNS.GetName()), @@ -231,6 +233,7 @@ func RequireNamespace(rt *Runtime, namespace IExpr) (IExpr, IError) { if !inserted { return nil, MakeError( rt, + loadedNS, fmt.Sprintf( "can't set the current ns back to '%s' from '%s'.", prevNS.GetName(), diff --git a/bootstrap/pkg/core/nothing.go b/bootstrap/pkg/core/nothing.go index cb33351..118ed7f 100644 --- a/bootstrap/pkg/core/nothing.go +++ b/bootstrap/pkg/core/nothing.go @@ -36,7 +36,7 @@ func (n NothingType) Hash() uint32 { return hash.HashOf(append([]byte{byte(ast.Block)}, bytes...)) } -func (n NothingType) GetLocation() ast.Location { +func (n NothingType) GetLocation() *ast.Location { return ast.MakeUnknownLocation() } diff --git a/bootstrap/pkg/core/parser.go b/bootstrap/pkg/core/parser.go index 10a9554..59234c9 100644 --- a/bootstrap/pkg/core/parser.go +++ b/bootstrap/pkg/core/parser.go @@ -33,6 +33,8 @@ package core import ( "strings" "unicode" + + "serene-lang.org/bootstrap/pkg/ast" ) // An array of the valid characters that be be used in a symbol @@ -57,14 +59,17 @@ type IParsable interface { // Returns the current position in the buffer GetLocation() int + GetSource() *ast.Source Buffer() *[]string } // StringParser is an implementation of the IParsable that operates on strings. // To put it simply it parses input strings type StringParser struct { - buffer []string - pos int + buffer []string + pos int + source string + lineIndex []int } // Implementing IParsable for StringParser --- @@ -75,6 +80,12 @@ func (sp *StringParser) next(skipWhitespace bool) *string { return nil } char := sp.buffer[sp.pos] + + if char == "\n" { + // Including the \n itself + sp.lineIndex = append(sp.lineIndex, sp.pos+1) + } + sp.pos = sp.pos + 1 if skipWhitespace && isSeparator(&char) { @@ -127,6 +138,14 @@ func (sp *StringParser) GetLocation() int { return sp.pos } +func (sp *StringParser) GetSource() *ast.Source { + return &ast.Source{ + Buffer: &sp.buffer, + Path: sp.source, + LineIndex: &sp.lineIndex, + } +} + func (sp *StringParser) Buffer() *[]string { return &sp.buffer } @@ -136,7 +155,7 @@ func (sp *StringParser) Buffer() *[]string { // makeErrorAtPoint is a helper function which generates an `IError` that // points at the current position of the buffer. func makeErrorAtPoint(p IParsable, msg string, a ...interface{}) IError { - n := MakeSinglePointNode(p.Buffer(), p.GetLocation()) + n := MakeSinglePointNode(p.GetSource(), p.GetLocation()) return MakeParsetimeErrorf(n, msg, a...) } @@ -207,7 +226,7 @@ func readRawSymbol(parser IParsable) (IExpr, IError) { } } - node := MakeNode(parser.Buffer(), parser.GetLocation()-len(symbol), parser.GetLocation()) + node := MakeNode(parser.GetSource(), parser.GetLocation()-len(symbol), parser.GetLocation()) sym, err := MakeSymbol(node, symbol) if err != nil { @@ -228,7 +247,7 @@ func readString(parser IParsable) (IExpr, IError) { } if *c == "\"" { - node := MakeNode(parser.Buffer(), parser.GetLocation()-len(str), parser.GetLocation()) + node := MakeNode(parser.GetSource(), parser.GetLocation()-len(str), parser.GetLocation()) return MakeString(node, str), nil } @@ -367,7 +386,10 @@ func readList(parser IParsable) (IExpr, IError) { } } - return MakeList(list), nil + node := MakeNodeFromExprs(list) + node.location.DecStart(1) + node.location.IncEnd(1) + return MakeList(node, list), nil } func readComment(parser IParsable) (IExpr, IError) { @@ -387,7 +409,7 @@ func readQuotedExpr(parser IParsable) (IExpr, IError) { return nil, err } - symNode := MakeNode(parser.Buffer(), parser.GetLocation(), parser.GetLocation()) + symNode := MakeNode(parser.GetSource(), parser.GetLocation(), parser.GetLocation()) sym, err := MakeSymbol(symNode, "quote") if err != nil { @@ -395,10 +417,15 @@ func readQuotedExpr(parser IParsable) (IExpr, IError) { return nil, err } - return MakeList([]IExpr{ + listElems := []IExpr{ sym, expr, - }), nil + } + + listNode := MakeNodeFromExprs(listElems) + listNode.location.DecStart(1) + listNode.location.IncStart(1) + return MakeList(listNode, listElems), nil } // readUnquotedExpr reads different unquoting expressions from their short representaions. @@ -417,7 +444,7 @@ func readUnquotedExpr(parser IParsable) (IExpr, IError) { var err IError var expr IExpr - node := MakeNode(parser.Buffer(), parser.GetLocation(), parser.GetLocation()) + node := MakeNode(parser.GetSource(), parser.GetLocation(), parser.GetLocation()) if *c == "@" { parser.next(true) @@ -441,7 +468,11 @@ func readUnquotedExpr(parser IParsable) (IExpr, IError) { return nil, err } - return MakeList([]IExpr{sym, expr}), nil + listElems := []IExpr{sym, expr} + listNode := MakeNodeFromExprs(listElems) + listNode.location.DecStart(1) + listNode.location.IncStart(1) + return MakeList(listNode, listElems), nil } // readQuasiquotedExpr reads the backquote and replace it with a call @@ -452,16 +483,19 @@ func readQuasiquotedExpr(parser IParsable) (IExpr, IError) { return nil, err } - node := MakeNode(parser.Buffer(), parser.GetLocation(), parser.GetLocation()) + node := MakeNode(parser.GetSource(), parser.GetLocation(), parser.GetLocation()) sym, err := MakeSymbol(node, "quasiquote") if err != nil { err.SetNode(&node) return nil, err } - return MakeList([]IExpr{ - sym, - expr, - }), nil + + listElems := []IExpr{sym, expr} + listNode := MakeNodeFromExprs(listElems) + listNode.location.DecStart(1) + listNode.location.IncStart(1) + + return MakeList(listNode, listElems), nil } // readExpr reads one expression from the input. This function is the most @@ -516,13 +550,14 @@ loop: // by itself is not something available to the language. It's // just anbstraction for a ordered collection of expressions. // It doesn't have anything to do with the concept of blocks -// from other programming languages -func ParseToAST(input string) (*Block, IError) { +// from other programming languages. +func ParseToAST(source string, input string) (*Block, IError) { var ast Block parser := StringParser{ buffer: strings.Split(input, ""), pos: 0, + source: source, } for { diff --git a/bootstrap/pkg/core/printer.go b/bootstrap/pkg/core/printer.go index 462ed08..97fd7a9 100644 --- a/bootstrap/pkg/core/printer.go +++ b/bootstrap/pkg/core/printer.go @@ -21,6 +21,8 @@ package core import ( "fmt" "strings" + + "github.com/gookit/color" ) func toRepresanbleString(ast ...IRepresentable) string { @@ -63,6 +65,36 @@ func Println(rt *Runtime, ast ...IRepresentable) { } func PrintError(rt *Runtime, err IError) { + trace := err.GetStackTrace() + + for i, f := range *trace { + loc := f.Caller.GetLocation() + fmt.Println("===============") + fmt.Println(f.Fn.GetLocation()) + fmt.Println(loc) + source := loc.GetSource() + // if loc.GetSource().Buffer != nil { + // fmt.Println(loc.GetSource().LineIndex) + // source = *loc.GetSource().Buffer + // } + startline := source.LineNumberFor(loc.GetStart()) - 1 + endline := source.LineNumberFor(loc.GetEnd()) + 1 + + var lines string + for i := startline; i <= endline; i++ { + lines += fmt.Sprintf("%d:\t%s\n", i, source.GetLine(i)) + } + + color.Yellow.Printf( + "%d: In function '%s' at '%s'\n", + i, + f.Fn.GetName(), + loc.GetSource().Path, + ) + color.White.Printf("%s\n", lines) + } loc := err.GetLocation() - fmt.Printf("Error: %s\nAt: %d to %d\n", err.String(), loc.GetStart(), loc.GetEnd()) + + errTag := color.Red.Sprint("ERROR") + fmt.Printf("%s: %s\nAt: %d to %d\n", errTag, err.String(), loc.GetStart(), loc.GetEnd()) } diff --git a/bootstrap/pkg/core/quasiquote.go b/bootstrap/pkg/core/quasiquote.go index ed402cf..53c679e 100644 --- a/bootstrap/pkg/core/quasiquote.go +++ b/bootstrap/pkg/core/quasiquote.go @@ -98,10 +98,15 @@ func qqProcess(rt *Runtime, e IExpr) (IExpr, IError) { // newErr.stack(err) return nil, err } - return MakeList([]IExpr{ + elems := []IExpr{ sym, e, - }), nil + } + + return MakeList( + MakeNodeFromExprs(elems), + elems, + ), nil case ast.List: list := e.(*List) @@ -125,7 +130,7 @@ func qqProcess(rt *Runtime, e IExpr) (IExpr, IError) { } // ??? if isUnquoteSplicing(first) { - return nil, MakeErrorFor(rt, first, "'unquote-splicing' is not allowed out of a collection.") + return nil, MakeError(rt, first, "'unquote-splicing' is not allowed out of a collection.") } // p := list diff --git a/bootstrap/pkg/core/reader.go b/bootstrap/pkg/core/reader.go index 3c638d9..040ba88 100644 --- a/bootstrap/pkg/core/reader.go +++ b/bootstrap/pkg/core/reader.go @@ -18,6 +18,6 @@ along with this program. If not, see . package core -func ReadString(input string) (*Block, IError) { - return ParseToAST(input) +func ReadString(src string, input string) (*Block, IError) { + return ParseToAST(src, input) } diff --git a/bootstrap/pkg/core/runtime.go b/bootstrap/pkg/core/runtime.go index 9aab2cf..bca2701 100644 --- a/bootstrap/pkg/core/runtime.go +++ b/bootstrap/pkg/core/runtime.go @@ -147,8 +147,8 @@ func nsNameToPath(ns string) string { // LoadNS looks up the namespace specified by the given name `ns` // and reads the content as expressions (parse it) and returns the // expressions. -func (r *Runtime) LoadNS(ns string) (*loadedForms, IError) { - nsFile := nsNameToPath(ns) +func (r *Runtime) LoadNS(ns *Symbol) (*loadedForms, IError) { + nsFile := nsNameToPath(ns.GetName()) for _, loadPath := range r.paths { possibleFile := path.Join(loadPath, nsFile) @@ -167,13 +167,14 @@ func (r *Runtime) LoadNS(ns string) (*loadedForms, IError) { if err != nil { readError := MakeError( r, + ns, fmt.Sprintf("error while reading the file at %s", possibleFile), ) readError.WithError(err) return nil, readError } - body, e := ReadString(string(data)) + body, e := ReadString(possibleFile, string(data)) if e != nil { return nil, e } @@ -182,7 +183,7 @@ func (r *Runtime) LoadNS(ns string) (*loadedForms, IError) { } // TODO: Add the load paths to the error message here - return nil, MakeError(r, fmt.Sprintf("Can't find the namespace '%s' in any of load paths.", ns)) + return nil, MakeError(r, ns, fmt.Sprintf("Can't find the namespace '%s' in any of load paths.", ns)) } func (r *Runtime) InsertNS(nsName string, ns *Namespace) { diff --git a/bootstrap/pkg/core/sforms.go b/bootstrap/pkg/core/sforms.go index b5d2585..aaa806f 100644 --- a/bootstrap/pkg/core/sforms.go +++ b/bootstrap/pkg/core/sforms.go @@ -34,7 +34,7 @@ func Def(rt *Runtime, scope IScope, args *List) (IExpr, IError) { name := args.First() if name.GetType() != ast.Symbol { - return nil, MakeError(rt, "the first argument of 'def' has to be a symbol") + return nil, MakeError(rt, name, "the first argument of 'def' has to be a symbol") } sym := name.(*Symbol) @@ -55,7 +55,7 @@ func Def(rt *Runtime, scope IScope, args *List) (IExpr, IError) { return sym, nil } - return nil, MakeError(rt, "'def' form need at least 2 arguments") + return nil, MakeError(rt, args, "'def' form need at least 2 arguments") } // Def defines a macro in the current namespace. The first @@ -72,7 +72,7 @@ func DefMacro(rt *Runtime, scope IScope, args *List) (IExpr, IError) { name := args.First() if name.GetType() != ast.Symbol { - return nil, MakeError(rt, "the first argument of 'defmacro' has to be a symbol") + return nil, MakeError(rt, name, "the first argument of 'defmacro' has to be a symbol") } sym := name.(*Symbol) @@ -100,21 +100,21 @@ func DefMacro(rt *Runtime, scope IScope, args *List) (IExpr, IError) { return macro, nil } - return nil, MakeError(rt, "'defmacro' form need at least 2 arguments") + return nil, MakeError(rt, args, "'defmacro' 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, IError) { - if args.Count() < 1 { - return nil, MakeError(rt, "'fn' needs at least an arguments list") + if args.Count() < 2 { + return nil, MakeError(rt, args, "'fn' needs at least an arguments list") } var params IColl body := MakeEmptyBlock() - arguments := args.First() + arguments := args.Rest().First() // TODO: Add vector in here // Or any other icoll @@ -123,26 +123,26 @@ func Fn(rt *Runtime, scope IScope, args *List) (IExpr, IError) { } if args.Count() > 1 { - body.SetContent(args.Rest().(*List).ToSlice()) + body.SetContent(args.Rest().Rest().(*List).ToSlice()) } - return MakeFunction(scope, params, body), nil + return MakeFunction(MakeNodeFromExpr(args.First()), scope, params, body), nil } func NSForm(rt *Runtime, scope IScope, args *List) (IExpr, IError) { if args.Count() == 1 { - return nil, MakeErrorFor(rt, args, "namespace's name is missing") + return nil, MakeError(rt, args, "namespace's name is missing") } name := args.Rest().First() if name.GetType() != ast.Symbol { - return nil, MakeErrorFor(rt, name, "the first argument to the 'ns' has to be a symbol") + return nil, MakeError(rt, name, "the first argument to the 'ns' has to be a symbol") } nsName := name.(*Symbol).GetName() if nsName != rt.CurrentNS().GetName() { - return nil, MakeErrorFor( + return nil, MakeError( rt, args, fmt.Sprintf("the namespace '%s' doesn't match the file name.", nsName), @@ -151,7 +151,7 @@ func NSForm(rt *Runtime, scope IScope, args *List) (IExpr, IError) { ns, ok := rt.GetNS(nsName) if !ok { - return nil, MakeErrorFor(rt, name, fmt.Sprintf("can't find the namespace '%s'. Is it the same as the file name?", nsName)) + return nil, MakeError(rt, name, fmt.Sprintf("can't find the namespace '%s'. Is it the same as the file name?", nsName)) } return ns, nil diff --git a/bootstrap/pkg/core/types.go b/bootstrap/pkg/core/types.go index a176d52..78ac526 100644 --- a/bootstrap/pkg/core/types.go +++ b/bootstrap/pkg/core/types.go @@ -81,8 +81,8 @@ type Node struct { } // GetLocation returns the location of the Node in the source input -func (n Node) GetLocation() ast.Location { - return n.location +func (n Node) GetLocation() *ast.Location { + return &n.location } type ExecutionScope struct { @@ -123,9 +123,9 @@ func toRepresentables(ast IColl) []IRepresentable { } // MakeNodeFromLocation creates a new Node for the given Location `loc` -func MakeNodeFromLocation(loc ast.Location) Node { +func MakeNodeFromLocation(loc *ast.Location) Node { return Node{ - location: loc, + location: *loc, } } @@ -136,14 +136,30 @@ func MakeNodeFromExpr(e IExpr) Node { return MakeNodeFromLocation(e.GetLocation()) } +// MakeNodeFromExprs creates a new Node from the given slice of `IExpr`s. +// We use the Node to pass it to other IExpr constructors to +// keep the reference to the original form in the input string +func MakeNodeFromExprs(es []IExpr) Node { + if len(es) == 0 { + // TODO: This is temporary, fix it. + panic("can't create a node from empty elements.") + } + + firstLoc := es[0].GetLocation() + endLoc := es[len(es)-1].GetLocation() + loc := ast.MakeLocation(firstLoc.GetSource(), firstLoc.GetStart(), endLoc.GetEnd()) + + return MakeNodeFromLocation(loc) +} + // MakeNode creates a new Node in the the given `input` that points to a // range of characters starting from the `start` till the `end`. -func MakeNode(input *[]string, start int, end int) Node { +func MakeNode(input *ast.Source, start int, end int) Node { return MakeNodeFromLocation(ast.MakeLocation(input, start, end)) } // MakeSinglePointNode creates a not the points to a single char in the // input -func MakeSinglePointNode(input *[]string, point int) Node { +func MakeSinglePointNode(input *ast.Source, point int) Node { return MakeNode(input, point, point) } diff --git a/dev.org b/dev.org index f6e9ede..0d387f0 100644 --- a/dev.org +++ b/dev.org @@ -35,6 +35,7 @@ https://www.reddit.com/r/ProgrammingLanguages/comments/8ggx2n/is_llvm_a_good_bac - Official LLVM tutorial C++ :: https://llvm.org/docs/tutorial/ - Interactive C++ with Cling :: https://blog.llvm.org/posts/2020-11-30-interactive-cpp-with-cling/ - My First LLVM Compiler :: https://www.wilfred.me.uk/blog/2015/02/21/my-first-llvm-compiler/ +- A Complete Guide to LLVM for Programming Language Creators :: https://mukulrathi.co.uk/create-your-own-programming-language/llvm-ir-cpp-api-tutorial/ ** Data structures - Pure functional datastructures papaer :: https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf - Dynamic typing: syntax and proof theory :: https://reader.elsevier.com/reader/sd/pii/0167642394000042?token=CEFF5C5D1B03FD680762FC4889A14C0CA2BB28FE390EC51099984536E12AC358F3D28A5C25C274296ACBBC32E5AE23CD