[Bootstrap] Use Serene itself to load and run the main function

This commit consist a series of fixes to make the reach the main
goal of the commit:

* Fix the 'MakeNodeFromExprs' behavior so it returns a pointer
  to a node and if the number of input exprs is zero then nil.

* Fix all the eval loop to return immediately in case of any error
  duh!

* Add a `errtype` field to `Error` with an `ErrType` enum type
  that indicates the type of the error, the type being syntax,
  semantic and runtime error at the moment.

* Rename some of the Error functions to match the error type. For
  Exapmle `MakeSyntaxError` and `MakeSemanticError`
This commit is contained in:
Sameer Rahmani 2021-01-02 21:04:35 +00:00
parent e895377811
commit 9be27c124a
12 changed files with 203 additions and 107 deletions

View File

@ -20,15 +20,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"bytes"
"fmt"
"html/template"
"os"
"path/filepath"
"strings"
"github.com/chzyer/readline"
"serene-lang.org/bootstrap/pkg/ast"
)
type mainRunner struct {
NS string
Args string
}
func rep(rt *Runtime, line string) {
ast, err := ReadString("*REPL*", line)
@ -113,6 +118,7 @@ func Run(flags map[string]bool, args []string) {
}
rt := MakeRuntime([]string{cwd}, flags)
rt.CreateNS("user", "REPL", true)
if len(args) == 0 {
@ -120,77 +126,51 @@ func Run(flags map[string]bool, args []string) {
os.Exit(1)
}
var buf bytes.Buffer
arguments := ""
ns := args[0]
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)
os.Exit(1)
}
rt.InsertNS(ns, loadedNS)
inserted := rt.setCurrentNS(loadedNS.GetName())
if !inserted {
err := MakeError(
rt,
loadedNS,
fmt.Sprintf(
"the namespace '%s' didn't get inserted in the runtime.",
loadedNS.GetName()),
)
PrintError(rt, err)
os.Exit(1)
}
// Evaluating the body of the loaded ns (Check for ns validation happens here)
loadedNS, err = EvalNSBody(rt, loadedNS)
if err != nil {
PrintError(rt, err)
os.Exit(1)
}
mainBinding := loadedNS.GetRootScope().Lookup(rt, "main")
if mainBinding == nil {
PrintError(rt, MakePlainError(fmt.Sprintf("can't find the 'main' function in '%s' namespace", ns)))
os.Exit(1)
}
if mainBinding.Value.GetType() != ast.Fn {
PrintError(rt, MakePlainError("'main' is not a function"))
os.Exit(1)
}
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))
arguments += "\"" + arg + "\""
}
argNode = MakeNodeFromExprs(fnArgs)
} else {
argNode = node
}
_, err = mainFn.Apply(rt, loadedNS.GetRootScope(), mainFn.Node, MakeList(argNode, fnArgs))
tmpl, e := template.New("run").Parse(
`(def run-main
(fn ()
(require '({{.NS}} n))
(n/main {{.Args}})))
(run-main)`,
)
if e != nil {
panic(e)
}
e = tmpl.Execute(&buf, &mainRunner{ns, arguments})
if e != nil {
panic(e)
}
if rt.IsDebugMode() {
fmt.Println("[DEBUG] Evaluating the following form to run the 'main' fn:")
fmt.Println(buf.String())
}
ast, err := ReadString("*RUN*", buf.String())
if err != nil {
PrintError(rt, err)
os.Exit(1)
}
_, err = Eval(rt, ast)
if err != nil {
PrintError(rt, err)
return
}
}

View File

@ -41,6 +41,14 @@ import (
"serene-lang.org/bootstrap/pkg/errors"
)
type ErrType uint8
const (
SyntaxError ErrType = iota
SemanticError
RuntimeError
)
// IError defines the necessary functionality of the internal errors.
type IError interface {
// In order to point to a specific point in the input
@ -50,6 +58,7 @@ type IError interface {
IRepresentable
IDebuggable
GetErrType() ErrType
GetDescription() *string
GetStackTrace() *TraceBack
// To wrap Golan rrrrors
@ -63,6 +72,7 @@ type IError interface {
type Error struct {
Node
errtype ErrType
errno errors.Errno
WrappedErr error
msg string
@ -81,6 +91,10 @@ func (e *Error) ToDebugStr() string {
return fmt.Sprintf("%s:\n\t%s", e.msg, e.WrappedErr.Error())
}
func (e *Error) GetErrType() ErrType {
return e.errtype
}
func (e *Error) WithError(err error) IError {
e.WrappedErr = err
return e
@ -117,19 +131,22 @@ func MakePlainError(msg string) IError {
// MakeError creates an Error which points to the given IExpr `e` as
// the root of the error.
func MakeError(rt *Runtime, e IExpr, msg string) IError {
trace := append(*rt.Stack.ToTraceBack(), &Frame{0, rt.Stack.GetCurrentFn(), e})
frame := MakeFrame(e, rt.Stack.GetCurrentFn(), 1)
trace := append(*rt.Stack.ToTraceBack(), frame)
return &Error{
Node: MakeNodeFromExpr(e),
msg: msg,
trace: &trace,
Node: MakeNodeFromExpr(e),
errtype: RuntimeError,
msg: msg,
trace: &trace,
}
}
func MakeParsetimeErrorf(n Node, msg string, a ...interface{}) IError {
func MakeSyntaxErrorf(n Node, msg string, a ...interface{}) IError {
return &Error{
Node: n,
msg: fmt.Sprintf(msg, a...),
Node: n,
errtype: SyntaxError,
msg: fmt.Sprintf(msg, a...),
}
}
@ -141,9 +158,10 @@ func MakeSemanticError(rt *Runtime, e IExpr, errno errors.Errno, msg string) IEr
}
return &Error{
Node: MakeNodeFromExpr(e),
errno: errno,
msg: msg,
trace: frames,
Node: MakeNodeFromExpr(e),
errtype: SemanticError,
errno: errno,
msg: msg,
trace: frames,
}
}

View File

@ -218,6 +218,10 @@ tco:
// Evaluating forms one by one
if forms.GetType() != ast.List {
ret, err = evalForm(rt, scope, forms)
if err != nil {
return nil, err
}
continue body
}
@ -271,6 +275,10 @@ tco:
// TODO: decide on the syntax and complete the docs
case "ns":
ret, err = NSForm(rt, scope, list)
if err != nil {
return nil, err
}
continue body // no rewrite
// `quote` evaluation rules:
@ -298,6 +306,10 @@ tco:
// Creates a new list form it's arguments.
case "list":
ret, err = evalForm(rt, scope, list.Rest().(*List))
if err != nil {
return nil, err
}
continue body // no rewrite
// TODO: Implement `concat` in serene itself when we have protocols available
@ -320,12 +332,19 @@ tco:
result = append(result, lst.(*List).ToSlice()...)
}
node := MakeNodeFromExpr(list)
if len(result) > 0 {
node = MakeNodeFromExprs(result)
n := MakeNodeFromExprs(result)
if n == nil {
n = &list.Node
}
node := *n
ret, err = MakeList(node, result), nil
if err != nil {
return nil, err
}
continue body // no rewrite
// TODO: Implement `list` in serene itself when we have destructuring available
@ -349,6 +368,10 @@ tco:
}
ret, err = coll.Cons(evaledForms.(*List).First()), nil
if err != nil {
return nil, err
}
continue body // no rewrite
// `def` evaluation rules
@ -359,6 +382,10 @@ tco:
// the symbol name binded to the value
case "def":
ret, err = Def(rt, scope, list.Rest().(*List))
if err != nil {
return nil, err
}
continue body // no rewrite
// `defmacro` evaluation rules:
@ -368,6 +395,10 @@ tco:
// body of the macro.
case "defmacro":
ret, err = DefMacro(rt, scope, list.Rest().(*List))
if err != nil {
return nil, err
}
continue body // no rewrite
// `macroexpand` evaluation rules:
@ -385,6 +416,9 @@ tco:
}
ret, err = macroexpand(rt, scope, evaledForm.(*List).First())
if err != nil {
return nil, err
}
continue body // no rewrite
// `fn` evaluation rules:
@ -392,6 +426,9 @@ tco:
// * Defines an anonymous function.
case "fn":
ret, err = Fn(rt, scope, list)
if err != nil {
return nil, err
}
continue body // no rewrite
// `if` evaluation rules:
@ -454,6 +491,10 @@ tco:
}
ret, err = EvalForms(rt, scope, form)
if err != nil {
return nil, err
}
continue body // no rewrite
// `let` evaluation rules:
@ -525,9 +566,7 @@ tco:
// Evaluating all the elements of the list
listExprs, e := evalForm(rt, scope, list)
if e != nil {
err = e
ret = nil
break tco //return
return nil, e
}
f := listExprs.(*List).First()
@ -541,19 +580,14 @@ tco:
// `expressions` to the body of function and loop again
fn := f.(*Function)
if e != nil {
err = e
ret = nil
break body //return
return nil, e
}
argList := listExprs.(*List).Rest().(*List)
fnScope, e := MakeFnScope(rt, fn.GetScope(), fn.GetParams(), argList)
if e != nil {
err = e
ret = nil
break body //return
return nil, e
}
rt.Stack.Push(list, fn)
@ -579,6 +613,11 @@ tco:
MakeNodeFromExpr(fn),
listExprs.(*List),
)
if err != nil {
return nil, err
}
rt.Stack.Pop()
continue body // no rewrite

View File

@ -209,7 +209,13 @@ func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Sco
var node Node
if len(elements) > 0 {
node = MakeNodeFromExprs(elements)
n := MakeNodeFromExprs(elements)
if n == nil {
n = &values.(*List).Node
}
node = *n
} else {
node = MakeNodeFromExpr(binds[i])
}

View File

@ -181,11 +181,11 @@ func extractParts(s string) (string, string) {
func MakeKeyword(n Node, name string) (*Keyword, IError) {
if strings.Count(name, ":") > 2 {
return nil, MakeParsetimeErrorf(n, "can't parse the keyword with more that two colons: '%s'", name)
return nil, MakeSyntaxErrorf(n, "can't parse the keyword with more that two colons: '%s'", name)
}
if strings.Count(name, "/") > 1 {
return nil, MakeParsetimeErrorf(n, "illegal namespace path for the given keyword: '%s'", name)
return nil, MakeSyntaxErrorf(n, "illegal namespace path for the given keyword: '%s'", name)
}
var nsName string

View File

@ -76,7 +76,12 @@ func (l *List) Rest() ISeq {
rest := l.exprs[1:]
node := l.Node
if len(rest) > 0 {
node = MakeNodeFromExprs(rest)
// n won't be nil here but we should check anyway
n := MakeNodeFromExprs(rest)
if n == nil {
panic("'MakeNodeFromExprs' has returned nil for none empty array of exprs")
}
node = *n
}
return MakeList(node, rest)
@ -105,7 +110,14 @@ func (l *List) ToSlice() []IExpr {
func (l *List) Cons(e IExpr) IExpr {
elements := append([]IExpr{e}, l.ToSlice()...)
return MakeList(MakeNodeFromExprs(elements), elements)
node := MakeNodeFromExprs(elements)
// Since 'elements' is not empty node won't be nil but we should
// check anyway
if node == nil {
node = &l.Node
}
return MakeList(*node, elements)
}
// END: IColl ---

View File

@ -199,6 +199,7 @@ func RequireNamespace(rt *Runtime, namespace IExpr) (IExpr, IError) {
ns = first.(*Symbol)
alias = second.(*Symbol).GetName()
default:
return nil, MakeError(rt, ns, "Don't know how to load the given namespace")
}

View File

@ -176,7 +176,7 @@ func (sp *StringParser) Buffer() *[]string {
// points at the current position of the buffer.
func makeErrorAtPoint(p IParsable, msg string, a ...interface{}) IError {
n := MakeSinglePointNode(p.GetSource(), p.GetLocation())
return MakeParsetimeErrorf(n, msg, a...)
return MakeSyntaxErrorf(n, msg, a...)
}
// makeErrorFromError is a function which wraps a Golang error in an IError
@ -407,9 +407,14 @@ func readList(parser IParsable) (IExpr, IError) {
}
node := MakeNodeFromExprs(list)
if node == nil {
n := MakeSinglePointNode(parser.GetSource(), parser.GetLocation())
node = &n
}
node.location.DecStart(1)
node.location.IncEnd(1)
return MakeList(node, list), nil
return MakeList(*node, list), nil
}
func readComment(parser IParsable) (IExpr, IError) {
@ -443,9 +448,14 @@ func readQuotedExpr(parser IParsable) (IExpr, IError) {
}
listNode := MakeNodeFromExprs(listElems)
if listNode == nil {
n := MakeSinglePointNode(parser.GetSource(), parser.GetLocation())
listNode = &n
}
listNode.location.DecStart(1)
listNode.location.IncStart(1)
return MakeList(listNode, listElems), nil
return MakeList(*listNode, listElems), nil
}
// readUnquotedExpr reads different unquoting expressions from their short representaions.
@ -489,10 +499,19 @@ func readUnquotedExpr(parser IParsable) (IExpr, IError) {
}
listElems := []IExpr{sym, expr}
listNode := MakeNodeFromExprs(listElems)
// listNode won't be nil in this case but it doesn't
// mean we shouldn't check
if listNode == nil {
n := MakeSinglePointNode(parser.GetSource(), parser.GetLocation())
listNode = &n
}
listNode.location.DecStart(1)
listNode.location.IncStart(1)
return MakeList(listNode, listElems), nil
return MakeList(*listNode, listElems), nil
}
// readQuasiquotedExpr reads the backquote and replace it with a call
@ -512,10 +531,17 @@ func readQuasiquotedExpr(parser IParsable) (IExpr, IError) {
listElems := []IExpr{sym, expr}
listNode := MakeNodeFromExprs(listElems)
// listNode won't be nil in this case but it doesn't
// mean we shouldn't check
if listNode == nil {
n := MakeSinglePointNode(parser.GetSource(), parser.GetLocation())
listNode = &n
}
listNode.location.DecStart(1)
listNode.location.IncStart(1)
return MakeList(listNode, listElems), nil
return MakeList(*listNode, listElems), nil
}
// readExpr reads one expression from the input. This function is the most

View File

@ -65,9 +65,11 @@ func Println(rt *Runtime, ast ...IRepresentable) {
}
func PrintError(rt *Runtime, err IError) {
trace := err.GetStackTrace()
trace := err.GetStackTrace()
fmt.Println(err)
for i, t := range *trace {
fmt.Println(*t)
caller := t.Caller
callerLoc := caller.GetLocation()
callerSource := callerLoc.GetSource()
@ -82,7 +84,11 @@ func PrintError(rt *Runtime, err IError) {
var lines string
for i := startline; i <= endline; i++ {
lines += fmt.Sprintf("%d:\t%s\n", i, t.Fn.GetLocation().GetSource().GetLine(i))
fmt.Println(">>>>>>>>>> ", err)
fLoc := t.Fn.GetLocation()
fmt.Println(">>>>>>>>>> ", fLoc, fLoc.GetSource())
lines += fmt.Sprintf("%d:\t%s\n", i, fLoc.GetSource().GetLine(i))
}
color.Yellow.Printf(

View File

@ -103,8 +103,12 @@ func qqProcess(rt *Runtime, e IExpr) (IExpr, IError) {
e,
}
n := MakeNodeFromExprs(elems)
if n == nil {
n = &sym.Node
}
return MakeList(
MakeNodeFromExprs(elems),
*n,
elems,
), nil

View File

@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"strings"
"serene-lang.org/bootstrap/pkg/ast"
@ -86,7 +85,7 @@ func MakeSymbol(n Node, s string) (*Symbol, IError) {
name = parts[1]
nsPart = parts[0]
default:
return nil, MakePlainError(fmt.Sprintf("can't create a symbol from '%s'. More that on '/' is illegal.", s))
return nil, MakeSyntaxErrorf(n, "can't create a symbol from '%s'. More that on '/' is illegal.", s)
}
return &Symbol{

View File

@ -122,6 +122,12 @@ func toRepresentables(ast IColl) []IRepresentable {
return params
}
// TODO: I don't like the current interface and signatures of these
// 'Make*Node*' functions. Refactor them to have the same interface
// For instance if we need to return a pointer to a Node all of them
// has to return a pointer not just one of them. The return type
// should be predictable
// MakeNodeFromLocation creates a new Node for the given Location `loc`
func MakeNodeFromLocation(loc *ast.Location) Node {
return Node{
@ -139,17 +145,16 @@ func MakeNodeFromExpr(e IExpr) Node {
// 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 {
func MakeNodeFromExprs(es []IExpr) *Node {
if len(es) == 0 {
// TODO: This is temporary, fix it.
panic("can't create a node from empty elements.")
return nil
}
firstLoc := es[0].GetLocation()
endLoc := es[len(es)-1].GetLocation()
loc := ast.MakeLocation(firstLoc.GetSource(), firstLoc.GetStart(), endLoc.GetEnd())
return MakeNodeFromLocation(loc)
n := MakeNodeFromLocation(loc)
return &n
}
// MakeNode creates a new Node in the the given `input` that points to a