[Bootstrap] Fix the order and location on the traceback

In order to fix this issue I had to make many minor tweaks
including reordering the traceback and the way we pass down
nodes.
This commit is contained in:
Sameer Rahmani 2021-01-07 19:45:07 +00:00
parent 0cc7646e3a
commit c2d4273319
14 changed files with 159 additions and 60 deletions

View File

@ -63,7 +63,7 @@ func (s *Source) GetPos(start, end int) *string {
}
func (s *Source) GetLine(linenum int) string {
lines := strings.Split(strings.Join(*s.Buffer, ""), "\n")
if linenum > 0 && linenum < len(lines) {
if linenum > 0 && linenum <= len(lines) {
return lines[linenum-1]
}
return "----"
@ -107,6 +107,21 @@ func (s *Source) LineNumberFor(pos int) int {
return result
}
var builtinSource *Source
func GetBuiltinSource() *Source {
if builtinSource == nil {
buf := strings.Split("builtin", "")
lineindex := []int{len(buf) - 1}
builtinSource = &Source{
Buffer: &buf,
Path: "Builtin",
LineIndex: &lineindex,
}
}
return builtinSource
}
type Location struct {
start int
end int
@ -125,7 +140,10 @@ func (l *Location) GetEnd() int {
}
func (l *Location) GetSource() *Source {
return &l.source
if l.IsKnownLocaiton() {
return &l.source
}
return GetBuiltinSource()
}
func (l *Location) IncStart(x int) {

View File

@ -22,10 +22,10 @@ package core
// the language which are implemented in Go
var BUILTINS = map[string]NativeFunction{
"pr": MakeNativeFn("pr", PrNativeFn),
"prn": MakeNativeFn("pr", PrnNativeFn),
"prn": MakeNativeFn("prn", PrnNativeFn),
"print": MakeNativeFn("print", PrintNativeFn),
"println": MakeNativeFn("println", PrintlnNativeFn),
"require": MakeNativeFn("print", RequireNativeFn),
"require": MakeNativeFn("require", RequireNativeFn),
"hash": MakeNativeFn("hash", HashNativeFn),
}

View File

@ -65,6 +65,10 @@ type CallStack struct {
count uint
}
func (f *Frame) String() string {
return fmt.Sprintf("<Frame: FN: %s, Count: %d Caller: \n%s\n>", f.Fn, f.Count, f.Caller)
}
func (c *CallStack) Count() uint {
return c.count
}
@ -158,7 +162,8 @@ func (c *CallStack) ToTraceBack() *TraceBack {
if item == nil {
break
}
tr = append(tr, &item.data)
// TODO: This doesn't seem efficient. Fix it.
tr = append([]*Frame{&item.data}, tr...)
item = item.prev
}

View File

@ -82,19 +82,20 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) {
return MakeNil(MakeNodeFromExpr(form)), nil
default:
var expr *Binding
ns := scope.GetNS(rt)
if sym.IsNSQualified() {
// Whether a namespace with the given alias loaded or not
if !rt.CurrentNS().hasExternal(sym.GetNSPart()) {
if !ns.hasExternal(sym.GetNSPart()) {
return nil, MakeError(rt, sym,
fmt.Sprintf("Namespace '%s' is no loaded", sym.GetNSPart()),
)
}
expr = rt.CurrentNS().LookupGlobal(rt, sym)
expr = ns.LookupGlobal(rt, sym)
nsName = sym.GetNSPart()
} else {
expr = scope.Lookup(rt, symbolName)
nsName = rt.CurrentNS().GetName()
nsName = ns.GetName()
}
if expr == nil {
@ -199,7 +200,7 @@ tco:
}
if rt.IsDebugMode() {
fmt.Printf("[DEBUG] Evaluating forms in NS: %s, Forms: %s\n", rt.CurrentNS().GetName(), forms)
fmt.Printf("[DEBUG] Evaluating forms in NS: %s, Forms: %s\n", scope.GetNS(rt).GetName(), forms)
fmt.Printf("[DEBUG] * State: I: %d, Exprs: %s\n", i, exprs)
}
@ -394,7 +395,7 @@ tco:
// * The rest of the arguments will form a block that acts as the
// body of the macro.
case "defmacro":
ret, err = DefMacro(rt, scope, list.Rest().(*List))
ret, err = DefMacro(rt, scope, list)
if err != nil {
return nil, err
}
@ -511,7 +512,7 @@ tco:
return nil, MakeError(rt, list, "'let' needs at list 1 aruments")
}
letScope := MakeScope(scope.(*Scope))
letScope := MakeScope(rt, scope.(*Scope), nil)
// Since we're using IColl for the bindings, we can use either lists
// or vectors or even hashmaps for bindings
@ -565,6 +566,7 @@ tco:
default:
// Evaluating all the elements of the list
listExprs, e := evalForm(rt, scope, list)
if e != nil {
return nil, e
}
@ -589,8 +591,8 @@ tco:
if e != nil {
return nil, e
}
rt.Stack.Push(list, fn)
body := append(
fn.GetBody().ToSlice(),
// Add the PopStack instruction to clean up the stack after
@ -607,6 +609,7 @@ tco:
fn := f.(*NativeFunction)
rt.Stack.Push(list, fn)
ret, err = fn.Apply(
rt,
scope,

View File

@ -157,7 +157,7 @@ func MakeFunction(n Node, 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(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Scope, IError) {
scope := MakeScope(parent.(*Scope))
scope := MakeScope(rt, parent.(*Scope), nil)
// TODO: Implement destructuring
binds := bindings.ToSlice()
exprs := values.ToSlice()
@ -169,6 +169,16 @@ func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Sco
if lastBinding.GetType() == ast.Symbol && lastBinding.(*Symbol).IsRestable() {
numberOfBindings = len(binds) - 1
}
if lastBinding.GetType() == ast.Symbol && !lastBinding.(*Symbol).IsRestable() && numberOfBindings < len(exprs) {
return nil, MakeSemanticError(
rt,
values.(IExpr),
errors.E0002,
fmt.Sprintf("expected '%d' arguments, got '%d'.", bindings.Count(), values.Count()),
)
}
}
if numberOfBindings > len(exprs) {
@ -201,7 +211,7 @@ func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Sco
// next.
rest := MakeEmptyList(MakeNodeFromExpr(binds[i]))
if i == len(exprs)-1 {
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)
@ -260,6 +270,7 @@ func (f *NativeFunction) Apply(rt *Runtime, scope IScope, n Node, args *List) (I
func MakeNativeFn(name string, f nativeFnHandler) NativeFunction {
return NativeFunction{
Node: MakeNodeFromLocation(ast.MakeUnknownLocation()),
name: name,
fn: f,
}

View File

@ -18,7 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import "serene-lang.org/bootstrap/pkg/ast"
// TODO:
// * Add support for `before` and `after` state in macroexpantion
// and call stack. So in case of an error. Users should be able
// to see the forms before and after expansion.
import (
"serene-lang.org/bootstrap/pkg/ast"
)
// Serene macros are in fact functions with the `isMacro` flag set to true.
// We have only normal macro implementation in bootstrap version of serene in
@ -84,6 +91,7 @@ func macroexpand(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) {
var macro *Function
var e IError
ok := false
//form := expr
for {
macro, ok = isMacroCall(rt, scope, form)

View File

@ -160,7 +160,7 @@ func requireNS(rt *Runtime, ns *Symbol) (*Namespace, IError) {
)
}
namespace := MakeNS(ns.GetName(), source)
namespace := MakeNS(rt, ns.GetName(), source)
namespace.setForms(body)
return &namespace, nil
@ -252,8 +252,9 @@ func RequireNamespace(rt *Runtime, namespace IExpr) (IExpr, IError) {
}
// MakeNS creates a new namespace with the given `name` and `source`
func MakeNS(name string, source string) Namespace {
s := MakeScope(nil)
func MakeNS(rt *Runtime, name string, source string) Namespace {
s := MakeScope(rt, nil, &name)
return Namespace{
name: name,
rootScope: *s,

View File

@ -158,7 +158,7 @@ func (d Double) F64() float64 {
return d.value
}
func MakeNumberFromStr(strValue string, isDouble bool) (INumber, error) {
func MakeNumberFromStr(n Node, strValue string, isDouble bool) (INumber, error) {
var ret INumber
if isDouble {
@ -169,6 +169,7 @@ func MakeNumberFromStr(strValue string, isDouble bool) (INumber, error) {
}
ret = Double{
Node: n,
value: v,
}
} else {
@ -178,6 +179,7 @@ func MakeNumberFromStr(strValue string, isDouble bool) (INumber, error) {
}
ret = Integer{
Node: n,
value: v,
}
}

View File

@ -341,8 +341,9 @@ func readNumber(parser IParsable, neg bool) (IExpr, IError) {
break
}
}
value, err := MakeNumberFromStr(result, isDouble)
n := MakeSinglePointNode(parser.GetSource(), parser.GetLocation())
n.location.DecStart(len(result))
value, err := MakeNumberFromStr(n, result, isDouble)
if err != nil {
return nil, makeErrorFromError(parser, err)
@ -425,6 +426,7 @@ func readList(parser IParsable) (IExpr, IError) {
node.location.DecStart(1)
node.location.IncEnd(1)
return MakeList(*node, list), nil
}

View File

@ -85,9 +85,10 @@ func printError(rt *Runtime, err IError, stage int) {
}
color.Yellow.Printf(
"%d: At '%s'\n",
"%d: At '%s':%d\n",
stage,
source.Path,
source.LineNumberFor(loc.GetStart()),
)
color.White.Printf("%s\n", lines)
@ -115,11 +116,17 @@ func printErrorWithTraceBack(rt *Runtime, err IError) {
var lines string
for i := startline; i <= endline; i++ {
fLoc := t.Fn.GetLocation()
line := fLoc.GetSource().GetLine(i)
if line != "----" {
lines += fmt.Sprintf("%d:\t%s\n", i, line)
fLoc := t.Caller.GetLocation()
if fLoc.IsKnownLocaiton() {
line := fLoc.GetSource().GetLine(i)
if line != "----" {
lines += fmt.Sprintf("%d:\t%s\n", i, line)
}
} else {
lines += "Builtin\n"
}
}
color.Yellow.Printf(
@ -137,6 +144,7 @@ func printErrorWithTraceBack(rt *Runtime, err IError) {
}
func PrintError(rt *Runtime, err IError) {
switch err.GetErrType() {
case SyntaxError, SemanticError:
printError(rt, err, 0)

View File

@ -115,7 +115,7 @@ func (r *Runtime) GetNS(ns string) (*Namespace, bool) {
// CreateNS is a helper function to create a namespace and set it to be
// the current namespace of the runtime. `MakeNS` is much preferred
func (r *Runtime) CreateNS(name string, source string, setAsCurrent bool) {
ns := MakeNS(name, source)
ns := MakeNS(r, name, source)
if setAsCurrent {
r.currentNS = name

View File

@ -23,6 +23,8 @@ import "fmt"
type IScope interface {
Lookup(rt *Runtime, k string) *Binding
Insert(k string, v IExpr, public bool)
GetNS(rt *Runtime) *Namespace
SetNS(ns *string)
}
type Binding struct {
@ -33,6 +35,7 @@ type Binding struct {
type Scope struct {
bindings map[string]Binding
parent *Scope
ns *string
}
func (s *Scope) Lookup(rt *Runtime, k string) *Binding {
@ -42,6 +45,9 @@ func (s *Scope) Lookup(rt *Runtime, k string) *Binding {
v, ok := s.bindings[k]
if ok {
if rt.IsDebugMode() {
fmt.Printf("[DEBUG] Found '%s': '%s'\n", k, v.Value.String())
}
return &v
}
@ -52,6 +58,10 @@ func (s *Scope) Lookup(rt *Runtime, k string) *Binding {
builtin := rt.LookupBuiltin(k)
if builtin != nil {
if rt.IsDebugMode() {
fmt.Printf("[DEBUG] Found builtin '%s': '%s'\n", k, builtin)
}
return &Binding{builtin, true}
}
@ -62,9 +72,36 @@ func (s *Scope) Insert(k string, v IExpr, public bool) {
s.bindings[k] = Binding{Value: v, Public: public}
}
func MakeScope(parent *Scope) *Scope {
func (s *Scope) GetNS(rt *Runtime) *Namespace {
if s.ns == nil {
panic("A scope with no namespace !!!!")
}
ns, ok := rt.GetNS(*s.ns)
if !ok {
panic(fmt.Sprintf("A scope with the wrong namespace! '%s'", s.ns))
}
return ns
}
func (s *Scope) SetNS(ns *string) {
s.ns = ns
}
func MakeScope(rt *Runtime, parent *Scope, namespace *string) *Scope {
var belongsTo *string
if parent != nil {
nsName := parent.GetNS(rt).GetName()
belongsTo = &nsName
} else if namespace == nil {
panic("When the 'parent' is nil, you have to provide the 'namespace' name.")
} else {
belongsTo = namespace
}
return &Scope{
parent: parent,
bindings: map[string]Binding{},
ns: belongsTo,
}
}

View File

@ -67,40 +67,40 @@ func DefMacro(rt *Runtime, scope IScope, args *List) (IExpr, IError) {
// TODO: Add support for docstrings and meta
switch args.Count() {
case 3:
name := args.First()
if name.GetType() != ast.Symbol {
return nil, MakeError(rt, name, "the first argument of 'defmacro' has to be a symbol")
}
sym := name.(*Symbol)
var params IColl
body := MakeEmptyBlock()
arguments := args.Rest().First()
// TODO: Add vector in here
// Or any other icoll
if arguments.GetType() == ast.List {
params = arguments.(IColl)
}
if args.Count() > 2 {
body.SetContent(args.Rest().Rest().(*List).ToSlice())
}
macro := MakeMacro(scope, sym.GetName(), params, body)
ns := rt.CurrentNS()
ns.DefineGlobal(sym.GetName(), macro, true)
return macro, nil
if args.Count() < 2 {
return nil, MakeError(rt, args, "'defmacro' form need at least 2 arguments")
}
return nil, MakeError(rt, args, "'defmacro' form need at least 2 arguments")
name := args.Rest().First()
if name.GetType() != ast.Symbol {
return nil, MakeError(rt, name, "the first argument of 'defmacro' has to be a symbol")
}
sym := name.(*Symbol)
var params IColl
body := MakeEmptyBlock()
arguments := args.Rest().Rest().First()
// TODO: Add vector in here
// Or any other icoll
if arguments.GetType() == ast.List {
params = arguments.(IColl)
}
if args.Count() > 2 {
body.SetContent(args.Rest().Rest().Rest().(*List).ToSlice())
}
macro := MakeMacro(scope, sym.GetName(), params, body)
ns := scope.GetNS(rt)
ns.DefineGlobal(sym.GetName(), macro, true)
return macro, nil
}
// Fn defines a function inside the given scope `scope` with the given `args`.

View File

@ -74,6 +74,10 @@ type IExpr interface {
IScopable
}
// TODO: Add helper functions to reach methods on Node.location. For example
// Node.location.DecStart() has to have a helper on the Node liek:
// Node.DecStartLocation
// 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 {