Record the caller details in the stack frame

This commit is contained in:
Sameer Rahmani 2020-12-30 17:50:00 +00:00
parent 3904b050bb
commit 9d106d4278
23 changed files with 399 additions and 152 deletions

View File

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

View File

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

View File

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

View File

@ -20,6 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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("<NativeFn: %s at %p>", f.name, f)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
func ReadString(input string) (*Block, IError) {
return ParseToAST(input)
func ReadString(src string, input string) (*Block, IError) {
return ParseToAST(src, input)
}

View File

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

View File

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

View File

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

View File

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