166 lines
5.1 KiB
Go
166 lines
5.1 KiB
Go
/*
|
|
Copyright (C) 2023 Brian Evans (aka sloum). All rights reserved.
|
|
|
|
This source code is available under the terms of the ffsl, or,
|
|
Floodgap Free Software License. A copy of the license has been
|
|
provided as the file 'LICENSE' in the same folder as this source
|
|
code file. If for some reason it is not present, you can find the
|
|
terms of version 1 of the FFSL at the following URL:
|
|
|
|
https://www.floodgap.com/software/ffsl/license.html
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
func interpret(tokens []token, en *env) error {
|
|
r := NewTokenReader(tokens)
|
|
for t, err := r.Read(); err == nil; t, err = r.Read() {
|
|
if globalRetStack.Depth() > 0 {
|
|
return nil
|
|
}
|
|
ReplacedSymbol:
|
|
switch t.kind {
|
|
case FLOAT, INT, STRING, BOOL, TYPE, LIST, DICT:
|
|
err := globalStack.Push(t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case PROC:
|
|
en.Add(t.val.(proc).name, t)
|
|
completions = append(completions, t.val.(proc).name)
|
|
case IF:
|
|
v, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if toBool(v).val.(bool) {
|
|
err = interpret(t.val.(condIf).truthy, en)
|
|
if err != nil {
|
|
return errors.Join(err, fmt.Errorf(" - In `if` (Line %d of %s)", t.line, t.file))
|
|
}
|
|
} else if t.val.(condIf).hasFalseBranch {
|
|
err = interpret(t.val.(condIf).falsy, en)
|
|
if err != nil {
|
|
return errors.Join(err, fmt.Errorf(" - In `else` (Line %d of %s)", t.line, t.file))
|
|
}
|
|
}
|
|
case TRY:
|
|
tr := t.val.(try)
|
|
err := interpret(tr.try, NewEnv(en))
|
|
if err != nil {
|
|
nenv := NewEnv(en)
|
|
nenv.Add("catch-error", token{STRING, err.Error(), t.line, t.file})
|
|
err = interpret(tr.catch, nenv)
|
|
if err != nil {
|
|
return errors.Join(err, fmt.Errorf(" - In `try`/`catch` (Line %d of %s)", t.line, t.file))
|
|
}
|
|
}
|
|
case WHILE:
|
|
if !t.val.(while).doWhile {
|
|
v, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cond := toBool(v).val.(bool)
|
|
for cond {
|
|
err = interpret(t.val.(while).body, NewEnv(en))
|
|
if err != nil {
|
|
return errors.Join(err, fmt.Errorf(" - In `while` (Line %d of %s)", t.line, t.file))
|
|
}
|
|
if globalRetStack.Depth() > 0 && globalRetStack.Peek().val.(string) == "break" {
|
|
globalRetStack.Pop()
|
|
break
|
|
} else if globalRetStack.Depth() > 0 {
|
|
break
|
|
}
|
|
v, err = globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cond = toBool(v).val.(bool)
|
|
}
|
|
} else {
|
|
err = interpret(t.val.(while).body, NewEnv(en))
|
|
if err != nil {
|
|
return errors.Join(err, fmt.Errorf(" - In `dowhile` (Line %d of %s)", t.line, t.file))
|
|
}
|
|
for {
|
|
v, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !toBool(v).val.(bool) {
|
|
break
|
|
}
|
|
err = interpret(t.val.(while).body, NewEnv(en))
|
|
if err != nil {
|
|
return errors.Join(err, fmt.Errorf(" - In `dowhile` (Line %d of %s)", t.line, t.file))
|
|
}
|
|
if globalRetStack.Depth() > 0 && globalRetStack.Peek().val.(string) == "break" {
|
|
globalRetStack.Pop()
|
|
break
|
|
} else if globalRetStack.Depth() > 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
case SYMBOL:
|
|
foundIn, err := en.Find(t.val.(string))
|
|
if err != nil {
|
|
return fmt.Errorf("%s (Line %d of %s)", err.Error(), t.line, t.file)
|
|
}
|
|
x := foundIn.vars[t.val.(string)]
|
|
|
|
// Run a procedure
|
|
if x.kind == PROC {
|
|
if x.val.(proc).hasArg {
|
|
procArg, err := r.Read()
|
|
if err != nil {
|
|
return errors.Join(fmt.Errorf("`%s` (Line %d of %s)", t.val.(string), t.line, t.file), err, fmt.Errorf(" - In procedure `%s` (Line %d of %s)", x.val.(proc).name, x.line, x.file))
|
|
}
|
|
newBody := x.val.(proc).injectToken(procArg)
|
|
err = interpret(newBody, NewEnv(en))
|
|
} else {
|
|
err = interpret(x.val.(proc).body, NewEnv(en))
|
|
}
|
|
if err != nil {
|
|
// This join is meant to create a callstack trace of sorts
|
|
// so that if an error was found multiple procs deep each
|
|
// _should_ adds it proc name and line to the list.
|
|
return errors.Join(err, fmt.Errorf(" - In procedure `%s` (Line %d of %s)", x.val.(proc).name, x.line, x.file), fmt.Errorf(" - At call site for `%s` (Line %d of %s)", t.val.(string), t.line, t.file))
|
|
// return errors.Join(err, fmt.Errorf(" - In procedure `%s` (Line %d of %s)", x.val.(proc).name, x.line, x.file))
|
|
}
|
|
if globalRetStack.Depth() > 0 && globalRetStack.Peek().val.(string) == "return" {
|
|
globalRetStack.Pop()
|
|
} else if globalRetStack.Depth() > 0 {
|
|
return fmt.Errorf("Issue with break/return scoping that shouldnt occur")
|
|
}
|
|
} else {
|
|
t = x
|
|
goto ReplacedSymbol
|
|
}
|
|
case KEYWORD:
|
|
// Handle early exit from a proc
|
|
if (t.val.(string) == "return" && en.parent != nil) || t.val.(string) == "break" {
|
|
err := globalRetStack.Push(t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
err := callKeyword(t, r, en)
|
|
if err != nil {
|
|
return errors.Join(err, fmt.Errorf(" - In built-in `%s` (Line %d of %s)", t.val.(string), t.line, t.file))
|
|
}
|
|
default:
|
|
return fmt.Errorf("Unknown type encountered: %s\n", kindToString(t.kind))
|
|
}
|
|
}
|
|
return nil
|
|
}
|