[Bootstrap] Implement a call stack to keep track of function calls

Add `ICallStack` as the interface to the call stack with a simple
FIFO implementation that tracks the recursive calls as well.
This commit is contained in:
Sameer Rahmani 2020-12-25 22:03:21 +00:00
parent 9dfe54a573
commit 1447f8ac45
7 changed files with 51 additions and 12 deletions

View File

@ -29,7 +29,7 @@ var replCmd = &cobra.Command{
Long: `Runs the local Serene's REPL to interact with Serene`, Long: `Runs the local Serene's REPL to interact with Serene`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// TODO: Get the debug value from a CLI flag // TODO: Get the debug value from a CLI flag
core.REPL(debugMode) core.REPL(makeFlags())
}, },
} }

View File

@ -25,6 +25,14 @@ import (
) )
var debugMode bool var debugMode bool
var stackDebugMode bool
func makeFlags() map[string]bool {
return map[string]bool{
"debugMode": debugMode,
"stackDebugMode": stackDebugMode,
}
}
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@ -62,4 +70,10 @@ func init() {
false, false,
"Turns on the debug mode.") "Turns on the debug mode.")
rootCmd.PersistentFlags().BoolVar(
&stackDebugMode,
"debug-stack",
false,
"Turns on the call stack debug mode.")
} }

View File

@ -28,7 +28,7 @@ var runCmd = &cobra.Command{
Short: "Evaluates the given NS and runs the main function of it", Short: "Evaluates the given NS and runs the main function of it",
Long: `Evaluates the given NS and runs the main function`, Long: `Evaluates the given NS and runs the main function`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
core.Run(debugMode, args) core.Run(makeFlags(), args)
}, },
} }

View File

@ -55,13 +55,13 @@ Replace the readline implementation with go-prompt.
// REPL executes a Read Eval Print Loop locally reading from stdin and // REPL executes a Read Eval Print Loop locally reading from stdin and
// writing to stdout // writing to stdout
func REPL(debug bool) { func REPL(flags map[string]bool) {
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
panic(err) panic(err)
} }
rt := MakeRuntime([]string{cwd}, debug) rt := MakeRuntime([]string{cwd}, flags)
rt.CreateNS("user", "REPL", true) rt.CreateNS("user", "REPL", true)
@ -105,13 +105,13 @@ for details take a look at the LICENSE file.
} }
func Run(debug bool, args []string) { func Run(flags map[string]bool, args []string) {
cwd, e := os.Getwd() cwd, e := os.Getwd()
if e != nil { if e != nil {
panic(e) panic(e)
} }
rt := MakeRuntime([]string{cwd}, debug) rt := MakeRuntime([]string{cwd}, flags)
if len(args) == 0 { if len(args) == 0 {
@ -171,10 +171,16 @@ func Run(debug bool, args []string) {
} }
} }
//rt.Stack.Push(mainFn)
_, err = mainFn.Apply(rt, loadedNS.GetRootScope(), mainFn.Node, MakeList(fnArgs)) _, err = mainFn.Apply(rt, loadedNS.GetRootScope(), mainFn.Node, MakeList(fnArgs))
if err != nil { if err != nil {
PrintError(rt, err) PrintError(rt, err)
os.Exit(1) os.Exit(1)
} }
// rt.Stack.Pop()
// if rt.Stack.Count() != 0 {
// panic("Call stack is not empty.")
// }
} }

View File

@ -40,6 +40,7 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) {
switch form.GetType() { switch form.GetType() {
case ast.Nil: case ast.Nil:
return form, nil return form, nil
case ast.Number: case ast.Number:
return form, nil return form, nil
@ -195,7 +196,19 @@ tco:
if rt.IsDebugMode() { 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", rt.CurrentNS().GetName(), forms)
fmt.Printf("[DEBUG] -> State: I: %d, Exprs: %s\n", i, exprs) fmt.Printf("[DEBUG] * State: I: %d, Exprs: %s\n", i, exprs)
}
// Evaluate any internal instruction that has to be run.
// Instructions should change the return value, but errors
// are ok
if forms.GetType() == ast.Instruction {
err := ProcessInstruction(rt, forms.(*Instruction))
if err != nil {
return nil, err
}
continue
} }
// Evaluating forms one by one // Evaluating forms one by one
@ -533,7 +546,8 @@ tco:
break body //return break body //return
} }
body := fn.GetBody().ToSlice() rt.Stack.Push(fn)
body := append(fn.GetBody().ToSlice(), MakeStackPop(rt))
changeExecutionScope(body, fnScope) changeExecutionScope(body, fnScope)
exprs = append(body, restOfExprs(exprs, i)...) exprs = append(body, restOfExprs(exprs, i)...)
goto body // rewrite goto body // rewrite
@ -542,12 +556,14 @@ tco:
// by the `NativeFunction` struct // by the `NativeFunction` struct
case ast.NativeFn: case ast.NativeFn:
fn := f.(*NativeFunction) fn := f.(*NativeFunction)
rt.Stack.Push(fn)
ret, err = fn.Apply( ret, err = fn.Apply(
rt, rt,
scope, scope,
MakeNodeFromExpr(fn), MakeNodeFromExpr(fn),
listExprs.(*List), listExprs.(*List),
) )
rt.Stack.Pop()
continue body // no rewrite continue body // no rewrite
default: default:

View File

@ -30,7 +30,7 @@ package core
// arguments names // arguments names
// - Evaluate the body of the function in context of the new scope and return // - Evaluate the body of the function in context of the new scope and return
// the result of the last expression // the result of the last expression
// * Native functions evaluates by calling the `Apply` method of the `INativeFn` // * Native functions evaluates by calling the `Apply` method of the `IFn`
// interface which is quite simple. // interface which is quite simple.
// //
// TODOs: // TODOs:
@ -47,7 +47,8 @@ import (
type nativeFnHandler = func(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) type nativeFnHandler = func(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError)
type INativeFn interface { type IFn interface {
ast.ILocatable
Apply(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) Apply(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError)
} }

View File

@ -72,6 +72,7 @@ type Runtime struct {
// languages // languages
paths []string paths []string
Stack CallStack
// A to turn on the verbose mode, FOR DEVELOPMENT USE ONLY // A to turn on the verbose mode, FOR DEVELOPMENT USE ONLY
debugMode bool debugMode bool
} }
@ -201,12 +202,13 @@ func (r *Runtime) LookupBuiltin(k string) IExpr {
// MakeRuntime creates a Runtime and returns a pointer to it. Any // MakeRuntime creates a Runtime and returns a pointer to it. Any
// runtime initialization such as adding default namespaces and vice // runtime initialization such as adding default namespaces and vice
// versa has to happen here. // versa has to happen here.
func MakeRuntime(paths []string, debug bool) *Runtime { func MakeRuntime(paths []string, flags map[string]bool) *Runtime {
rt := Runtime{ rt := Runtime{
namespaces: map[string]Namespace{}, namespaces: map[string]Namespace{},
currentNS: "", currentNS: "",
debugMode: debug, debugMode: flags["debugMode"],
paths: paths, paths: paths,
Stack: MakeCallStack(flags["stackDebugMode"]),
} }
rt.builtins = BUILTINS rt.builtins = BUILTINS