felise/interpret.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
}