Lots of progress.

This commit is contained in:
sloum 2023-06-01 15:16:17 -07:00
parent a239c6f632
commit 1202139b89
11 changed files with 723 additions and 248 deletions

View File

@ -1,29 +1,25 @@
package main
const (
stackErrFmt string = "ERROR | Line %d\n %s\n"
STACK_SIZE int = 256
// Begin type enums
INT int = iota
INT_a
FLOAT
FLOAT_a
STRING
STRING_a
BOOL
BOOL_a
// CHAR
// BYTE
SYMBOL
KEYWORD
TYPE
IDENT
PROC
PROC_a
WHILE
IF
END
DOCSTRING
MAXTYPE
MAXTYPE // Just here to get a number range
)
var (

9
go.mod
View File

@ -1,3 +1,10 @@
module git.rawtext.club/sloum/nimf2
module git.rawtext.club/sloum/felise
go 1.20
require github.com/peterh/liner v1.2.2
require (
github.com/mattn/go-runewidth v0.0.3 // indirect
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect
)

6
go.sum Normal file
View File

@ -0,0 +1,6 @@
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -10,39 +10,29 @@ import (
func kindToString(k int) string {
switch k {
case INT:
return "int"
case INT_a:
return "[]int"
return "INT"
case FLOAT:
return "float"
case FLOAT_a:
return "[]float"
return "FLOAT"
case STRING:
return "string"
case STRING_a:
return "[]string"
return "STRING"
case BOOL:
return "bool"
case BOOL_a:
return "[]bool"
// case CHAR:
// return "char"
// case BYTE:
// return "byte"
return "BOOL"
case SYMBOL:
return "symbol"
return "SYMBOL"
case KEYWORD:
return "keyword"
case IDENT:
return "ident"
return "KEYWORD"
// case IDENT:
// return "ident"
case TYPE:
return "type"
return "TYPE"
case PROC:
return "proc"
case PROC_a:
return "[]proc"
return "PROC"
case WHILE:
return "WHILE"
case IF:
return "IF"
case DOCSTRING:
return "docstring"
return "DOCSTRING"
case END:
return "."
default:
@ -51,21 +41,46 @@ func kindToString(k int) string {
}
// Returns the kind, initial value, and error
func sigilToKind(v string) (int, value, error) {
if len(v) < 2 {
return -1, nil, fmt.Errorf("Invalid identifier (hint: sigil + at least one alpha-numeric char)")
}
switch v[0] {
case '$':
return STRING, "", nil
case '+':
return INT, 0, nil
case '%':
return FLOAT, 0.0, nil
case '?':
return BOOL, false, nil
// func sigilToKind(v string) (int, value, error) {
// if len(v) < 2 {
// return -1, nil, fmt.Errorf("Invalid identifier (hint: sigil + at least one alpha-numeric char)")
// }
// switch v[0] {
// case '$':
// return STRING, "", nil
// case '+':
// return INT, 0, nil
// case '%':
// return FLOAT, 0.0, nil
// case '?':
// return BOOL, false, nil
// default:
// return 0, nil, fmt.Errorf("Unsupported sigil: %q", v[0])
// }
// }
func typeInit(kind int) value {
switch kind {
case INT:
return 0
case FLOAT:
return 0.0
case BOOL:
return false
case STRING:
return ""
case TYPE:
return TYPE
case WHILE:
return while{}
case IF:
return condIf{}
case PROC:
return proc{}
case DOCSTRING:
return ""
default:
return 0, nil, fmt.Errorf("Unsupported sigil: %q", v[0])
panic("Unknown type given to typeInit")
}
}
@ -98,7 +113,9 @@ func isKeyword(s string) bool {
switch s {
case "var", "set", "+", "-", "*", "/",
"length", "ref", "return", "print", "println", "dup",
"drop", "over", "swap", "cast", "type":
"drop", "over", "swap", "cast", "type", "while", "dowhile",
"if", "else", "&&", "||", ">", "<", "=", "stackdump",
"clearstack":
return true
default:
return false
@ -135,6 +152,21 @@ func toString(t token, codeRepresentation bool) string {
}
}
func toBoolRaw(t token) bool {
switch t.kind {
case BOOL:
return t.val.(bool)
case INT:
return !(t.val.(int) == 0)
case STRING:
return !(t.val.(string) == "")
case FLOAT:
return !(t.val.(float64) == 0.0)
default:
return true
}
}
func toBool(t token) token {
switch t.kind {
case BOOL:

View File

@ -1,6 +1,7 @@
package main
import (
"errors"
"fmt"
)
@ -13,29 +14,89 @@ func interpret(tokens []token, en *env) error {
ReplacedSymbol:
switch t.kind {
case FLOAT, INT, STRING, BOOL, TYPE:
globalStack.Push(t)
err := globalStack.Push(t)
if err != nil {
return fmt.Errorf(stackErrFmt, t.line, err.Error())
}
case PROC:
en.Add(t.val.(proc).name, t)
case IF:
v, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, t.line, err.Error())
}
if toBool(v).val.(bool) {
err = interpret(t.val.(condIf).truthy, en)
if err != nil {
return err
}
} else if t.val.(condIf).hasFalseBranch {
err = interpret(t.val.(condIf).falsy, en)
if err != nil {
return err
}
}
case WHILE:
if !t.val.(while).doWhile {
v, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, t.line, err.Error())
}
cond := toBool(v).val.(bool)
if cond {
for {
err = interpret(t.val.(while).body, NewEnv(en))
if err != nil {
return err
}
v, err = globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, t.line, err.Error())
}
if !toBool(v).val.(bool) {
break
}
}
}
} else {
err = interpret(t.val.(while).body, NewEnv(en))
if err != nil {
return err
}
for {
v, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, t.line, err.Error())
}
if !toBool(v).val.(bool) {
break
}
err = interpret(t.val.(while).body, NewEnv(en))
if err != nil {
return err
}
}
}
case SYMBOL:
foundIn, err := en.Find(t.val.(string))
if err != nil {
return fmt.Errorf("ERROR | Line %d\n | Could not find symbol `%s`\n", t.line, t.val.(string))
return fmt.Errorf("ERROR │ Line %d\n │ Could not find symbol `%s`\n", t.line, t.val.(string))
}
t = foundIn.vars[t.val.(string)]
if t.kind == PROC {
err = interpret(t.val.(proc).body, NewEnv(en))
if err != nil {
// TODO there is opportunity here to create a
// stack trace of sorts by combining the
// error with the current proc name/line
return err
// 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` on line %d", t.val.(proc).name, t.line))
}
} else {
goto ReplacedSymbol
}
case KEYWORD:
// Handle early exit from a proc
if t.val.(string) == "return" && en.parent != nil {
if (t.val.(string) == "return" && en.parent != nil) || t.val.(string) == "break" {
return nil
}
err := callKeyword(t, r, en)
@ -43,7 +104,7 @@ ReplacedSymbol:
return err
}
default:
return fmt.Errorf("ERROR | Line %d\n | Unknown type encountered: %s\n", t.line, kindToString(t.kind))
return fmt.Errorf("ERROR │ Line %d\n │ Unknown type encountered: %s\n", t.line, kindToString(t.kind))
}
}
return nil

View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"os"
"strconv"
"strings"
)
@ -31,18 +32,38 @@ func callKeyword(kw token, r *tokenReader, en *env) error {
case "over":
return libOver()
case "drop":
return libDrop()
return libDrop(kw.line)
case "cast":
return libConvertType(kw.line)
case "&&":
return libLogicalAnd(kw.line)
case "||":
return libLogicalOr(kw.line)
case "=":
return libEqual(kw.line)
case ">":
return libGreaterThan(kw.line)
case "<":
return libLessThan(kw.line)
case "stackdump":
return libStackDump()
case "clearstack":
return libClearStack()
default:
return fmt.Errorf("Unknown keyword: %s", kw.val.(string))
return fmt.Errorf("ERROR | Line %d\n Unknown keyword: `%s`\n", kw.val.(string))
}
}
func libAdd(line int) error {
v2 := globalStack.Pop()
v1 := globalStack.Pop()
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if v1.kind == STRING && v2.kind == STRING {
globalStack.Push(token{STRING, v1.val.(string) + v2.val.(string), line})
} else if v1.kind == INT && v2.kind == INT {
@ -51,15 +72,21 @@ func libAdd(line int) error {
globalStack.Push(token{FLOAT, v1.val.(float64) + v2.val.(float64), line})
} else {
fmt.Println("STACK-DUMP: ", globalStack)
return fmt.Errorf("ERROR | Line %d\n Cannot `-` %s and %s\n", line, kindToString(v1.kind), kindToString(v2.kind))
return fmt.Errorf("ERROR | Line %d\n Cannot `-` %s and %s\n", line, kindToString(v1.kind), kindToString(v2.kind))
}
// TODO array operations, once arrays are added
return nil
}
func libSubtract(line int) error {
v2 := globalStack.Pop()
v1 := globalStack.Pop()
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if v1.kind == STRING && v2.kind == STRING {
s := strings.Replace(v1.val.(string), v2.val.(string), "", -1)
globalStack.Push(token{STRING, s, line})
@ -68,15 +95,21 @@ func libSubtract(line int) error {
} else if v1.kind == FLOAT && v2.kind == FLOAT {
globalStack.Push(token{FLOAT, v1.val.(float64) - v2.val.(float64), line})
} else {
return fmt.Errorf("ERROR | Line %d\n Cannot `-` %s and %s\n", kindToString(v1.kind), kindToString(v2.kind))
return fmt.Errorf("ERROR | Line %d\n Cannot `-` %s and %s\n", kindToString(v1.kind), kindToString(v2.kind))
}
// TODO array operations, once arrays are added
return nil
}
func libMultiply(line int) error {
v2 := globalStack.Pop()
v1 := globalStack.Pop()
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if v1.kind == STRING && v2.kind == INT {
s := strings.Repeat(v1.val.(string), v2.val.(int))
globalStack.Push(token{STRING, s, line})
@ -85,62 +118,86 @@ func libMultiply(line int) error {
} else if v1.kind == FLOAT && v2.kind == FLOAT {
globalStack.Push(token{FLOAT, v1.val.(float64) * v2.val.(float64), line})
} else {
return fmt.Errorf("ERROR | Line %d\n Cannot `*` %s and %s\n", kindToString(v1.kind), kindToString(v2.kind))
return fmt.Errorf("ERROR | Line %d\n Cannot `*` %s and %s\n", kindToString(v1.kind), kindToString(v2.kind))
}
// TODO array operations, once arrays are added
return nil
}
func libDivide(line int) error {
v2 := globalStack.Pop()
v1 := globalStack.Pop()
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if v1.kind == INT && v2.kind == INT {
globalStack.Push(token{INT, v1.val.(int) / v2.val.(int), line})
} else if v1.kind == FLOAT && v2.kind == FLOAT {
globalStack.Push(token{FLOAT, v1.val.(float64) / v2.val.(float64), line})
} else {
return fmt.Errorf("ERROR | Line %d\n Cannot `/` %s and %s\n", kindToString(v1.kind), kindToString(v2.kind))
return fmt.Errorf("ERROR | Line %d\n Cannot `/` %s and %s\n", kindToString(v1.kind), kindToString(v2.kind))
}
// TODO array operations, once arrays are added
return nil
}
func libVar(line int, r *tokenReader, en *env) error {
typeToken, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if typeToken.kind != TYPE {
return fmt.Errorf("ERROR | Line %d\n `var` expects a type on the top of the stack\n ", line)
}
newToken := token{}
newToken.kind = typeToken.val.(int)
newToken.line = line
t, err := r.Read()
if err != nil {
return fmt.Errorf("ERROR | Line %d\n Unexpectedly reached EOF\n", line)
return fmt.Errorf("ERROR | Line %d\n Unexpectedly reached EOF\n", line)
}
if t.kind != SYMBOL {
return fmt.Errorf("ERROR | Line %d\n A non-symbol value was given as an identifier to var\n", line)
return fmt.Errorf("ERROR | Line %d\n A non-symbol value was given as an identifier to var\n", line)
}
kind, val, err := sigilToKind(t.val.(string))
if err != nil {
return fmt.Errorf("ERROR | Line %d\n %s\n", line, err.Error())
}
newToken := token{kind, val, line}
newToken.val = typeInit(newToken.kind)
en.Add(t.val.(string), newToken)
fmt.Println(*en)
return nil
}
func libSet(line int, r *tokenReader, en *env) error {
t, err := r.Read()
if err != nil {
return fmt.Errorf("ERROR | Line %d\n Unexpectedly reached EOF\n", line)
return fmt.Errorf("ERROR | Line %d\n Unexpectedly reached EOF\n", line)
}
if t.kind != SYMBOL {
return fmt.Errorf("ERROR | Line %d\n A non-symbol value was given as an identifier to set\n", line)
return fmt.Errorf("ERROR | Line %d\n A non-symbol value was given as an identifier to set\n", line)
}
e, err := en.Find(t.val.(string))
if err != nil {
return fmt.Errorf("ERROR | Line %d\n %s\n", t.line, err.Error())
return fmt.Errorf("ERROR | Line %d\n %s\n", t.line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
current := e.vars[t.val.(string)]
if v1.kind != current.kind {
return fmt.Errorf("ERROR | Line %d\n Cannot assign %s to %s var\n", t.line, kindToString(v1.kind), kindToString(current.kind))
}
v1 := globalStack.Pop()
e.vars[t.val.(string)] = v1
return nil
}
func libPrint(newline bool) error {
v1 := globalStack.Pop()
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if newline {
fmt.Println(toString(v1, false))
} else {
@ -155,33 +212,175 @@ func libDuplicate() error {
}
func libSwap() error {
v2 := globalStack.Pop()
v1 := globalStack.Pop()
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
globalStack.Push(v2)
globalStack.Push(v1)
return nil
}
func libOver() error {
v2 := globalStack.Pop()
v1 := globalStack.Pop()
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
globalStack.Push(v1)
globalStack.Push(v2)
globalStack.Push(v1)
return nil
}
func libDrop() error {
globalStack.Pop()
func libDrop(line int) error {
_, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
return nil
}
func libLogicalAnd(line int) error {
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if toBoolRaw(v1) && toBoolRaw(v2) {
globalStack.Push(token{BOOL, true, line})
} else {
globalStack.Push(token{BOOL, false, line})
}
return nil
}
func libLogicalOr(line int) error {
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if toBoolRaw(v1) || toBoolRaw(v2) {
globalStack.Push(token{BOOL, true, line})
} else {
globalStack.Push(token{BOOL, false, line})
}
return nil
}
func libEqual(line int) error {
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if v1.kind == v2.kind && v1.val == v2.val{
globalStack.Push(token{BOOL, true, line})
} else {
globalStack.Push(token{BOOL, false, line})
}
return nil
}
func libGreaterThan(line int) error {
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if v1.kind == v2.kind {
switch v1.kind {
case STRING:
globalStack.Push(token{BOOL, v1.val.(string) > v1.val.(string), line})
case INT:
globalStack.Push(token{BOOL, v1.val.(int) > v1.val.(int), line})
case FLOAT:
globalStack.Push(token{BOOL, v1.val.(float64) > v1.val.(float64), line})
default:
return fmt.Errorf("ERROR | Line %d\n %s is not a comparable type\n", line, kindToString(v1.kind))
}
} else if (v1.kind == INT && v2.kind == FLOAT) {
globalStack.Push(token{BOOL, float64(v1.val.(int)) > v1.val.(float64), line})
} else if (v1.kind == FLOAT && v2.kind == INT) {
globalStack.Push(token{BOOL, v1.val.(float64) > float64(v1.val.(int)), line})
} else {
return fmt.Errorf("ERROR | Line %d\n %s and %s cannot be compared\n", line, kindToString(v1.kind), kindToString(v2.kind))
}
return nil
}
func libLessThan(line int) error {
v2, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
v1, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if v1.kind == v2.kind {
switch v1.kind {
case STRING:
globalStack.Push(token{BOOL, v1.val.(string) < v1.val.(string), line})
case INT:
globalStack.Push(token{BOOL, v1.val.(int) < v1.val.(int), line})
case FLOAT:
globalStack.Push(token{BOOL, v1.val.(float64) < v1.val.(float64), line})
default:
return fmt.Errorf("ERROR | Line %d\n %s is not a comparable type\n", line, kindToString(v1.kind))
}
} else if (v1.kind == INT && v2.kind == FLOAT) {
globalStack.Push(token{BOOL, float64(v1.val.(int)) < v1.val.(float64), line})
} else if (v1.kind == FLOAT && v2.kind == INT) {
globalStack.Push(token{BOOL, v1.val.(float64) < float64(v1.val.(int)), line})
} else {
return fmt.Errorf("ERROR | Line %d\n %s and %s cannot be compared\n", line, kindToString(v1.kind), kindToString(v2.kind))
}
return nil
}
func libStackDump() error {
fmt.Fprintf(os.Stderr, "\n%s\n", globalStack.String())
return nil
}
func libClearStack() error {
globalStack.sp = 0
return nil
}
func libConvertType(line int) error {
typeToken := globalStack.Pop()
typeToken, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
if typeToken.kind != TYPE {
return fmt.Errorf("ERROR | Line %d\n `cast` expected a TYPE on top of stack\n", line)
}
fromToken := globalStack.Pop()
fromToken, err := globalStack.Pop()
if err != nil {
return fmt.Errorf(stackErrFmt, line, err.Error())
}
toKind, fromKind := typeToken.val.(int), fromToken.kind
if fromKind == toKind {

100
lexer.go
View File

@ -1,99 +1,11 @@
package main
import (
"fmt"
"strconv"
"strings"
"unicode"
)
type token struct {
kind int
val value
line int
}
type tokenReader struct {
tokens []token
p int
}
func (t *tokenReader) Read() (token, error) {
t.p++
if t.p - 1 >= len(t.tokens) {
t.p--
return token{}, fmt.Errorf("Token overflow")
}
return t.tokens[t.p-1], nil
}
func (t *tokenReader) Unread() (error) {
t.p--
if t.p < 0 {
return fmt.Errorf("Token underflow")
}
return nil
}
func (t tokenReader) Current() int {
return t.p
}
func (t *tokenReader) Jump(i int) {
if i < 0 || i > len(t.tokens) {
panic("Token jump error")
}
t.p = i
}
func NewTokenReader(tokens []token) *tokenReader {
return &tokenReader{tokens, 0}
}
func (t token) String(litRep bool) string {
var repr string
switch t.kind {
case INT:
repr = strconv.Itoa(t.val.(int))
case INT_a:
repr = fmt.Sprintf("%v", t.val.([]int))
case FLOAT:
repr = fmt.Sprintf("%f", t.val.(float64))
case FLOAT_a:
repr = fmt.Sprintf("%v", t.val.([]float64))
case BOOL:
if t.val.(bool) {
repr = "true"
} else {
repr = "false"
}
case BOOL_a:
repr = fmt.Sprintf("%v", t.val.([]bool))
case STRING:
if litRep {
repr = fmt.Sprintf(`"%s"`, t.val.(string))
} else {
repr = fmt.Sprintf("%s", t.val.(string))
}
case STRING_a:
repr = fmt.Sprintf("%v", t.val.([]string))
case KEYWORD, IDENT:
repr = fmt.Sprintf("%s", t.val.(string))
case PROC:
repr = t.val.(proc).String()
default:
panic("Issue while stringifying token")
}
if debug {
return fmt.Sprintf("TOKEN %-10s Ln: %-3d %-10s\n", kindToString(t.kind), t.line, repr)
} else {
return repr
}
}
func lex(f string ) []token {
var reader *strings.Reader
data := f
@ -237,6 +149,18 @@ func eatWordNumber(r *strings.Reader, currentLine *int) token {
*currentLine,
}
if tokenString == "false" {
out.val = false
out.kind = BOOL
return out
}
if tokenString == "true" {
out.val = true
out.kind = BOOL
return out
}
// Check for hex string
if strings.HasPrefix(tokenString, "0x") {
i, err := strconv.ParseInt(tokenString[2:], 16, 0)

151
main.go
View File

@ -1,47 +1,32 @@
package main
import (
// "fmt"
"flag"
"fmt"
"os"
"strings"
ln "github.com/peterh/liner"
)
const (
version float64 = 0.1
name string = "felise"
)
var line *ln.State
var initialTerm ln.ModeApplier = nil
var linerTerm ln.ModeApplier = nil
/*
Variable sigil system:
- $myString (string)
- +myInt (int)
- %myFloat (float)
- ?myBool (bool)
- $:myString ([]string)
- #:myInt ([]int)
- %:myFloat ([]float)
- ?:myBool ([]bool)
- @myHandle (io-handle)
- *$myString (ref to string)
---
Special forms:
var [varname]
The above creates a var initialized to a value,
it will be set to a type based on the sigil
proc [procname] | This is a docstring | [body...] .
Procs should not start with a sigil value as they
become words in the language. Procs get their
own env during execution, similar to scheme. This
allows for nested procs that only live in the given
scope
set [varname/procname]
The above will set the var to TOS (and pop off TOS),
it will be typechecked and an error will be thrown
if it is not the correct type
Ideas:
del [varname/procname]
Will remove the symbol and value from the symbol table
// XXX note sure about these array ops
-> [varname]
Will append a value to the given array
@ -54,14 +39,8 @@ length [varname]
or an array
ref [ident/symbol]
Put the symbol on TOS
stack >|
ref $mystring
stack >$mystring|
var
Put the symbol on TOS, to reference something that would
otherwise be executed immediately (if, while, proc, etc)
---
@ -69,7 +48,7 @@ Stack ops:
When the interpreter encounters a symbol it will add it
to the stack if it is a value type. Otherwise, it will
produce a call to the proc.
produce a call to the proc, if, or while
---
@ -86,38 +65,98 @@ Syntactic sugar:
---
Builtins:
Builtins on radar:
- `+`, `-`, `*`, `/`, `%`
- `open`
- Opens a connection or file and returns a handle
- `close`
- Closes a connection or file
- `write`
- writes to a given handle
- `read`, `readline`, `readchar`, `readbyte`
- Read from the given handle to the stack
- `append`
- Appends TOS to a string, file, array
- Appends TOS to a string, file, array, or open connection
*/
func lexParseInterpret(s string) {
func prompt(l *ln.State, cont bool) string {
p := "> "
if cont {
p = "+ "
}
val, err := l.Prompt(p)
if err == ln.ErrPromptAborted {
return ""
}
l.AppendHistory(val)
return val
}
func repl() {
fmt.Printf("%s version %.2f\n(`bye` or `exit` to quit)\n\n", name, version)
initialTerm, _ = ln.TerminalMode()
line = ln.NewLiner()
linerTerm, _ = ln.TerminalMode()
defer line.Close()
var text strings.Builder
var cont bool
baseEnv := NewEnv(nil)
for {
if linerTerm != nil {
linerTerm.ApplyMode()
}
in := prompt(line, cont)
if initialTerm != nil {
initialTerm.ApplyMode()
}
if s := strings.TrimSpace(in); s == "" {
continue
} else if s == "bye" || s == "exit" {
break
}
if strings.HasSuffix(in, "\\") {
cont = true
text.WriteString(in[:len(in)-1])
text.WriteRune('\n')
} else {
cont = false
text.WriteString(in)
}
if !cont {
lexParseInterpret(text.String(), baseEnv)
text.Reset()
}
}
}
func lexParseInterpret(s string, en *env) {
lexedTokens := lex(s)
r := NewTokenReader(lexedTokens)
parsedTokens := parse(r)
err := interpret(parsedTokens, NewEnv(nil))
err := interpret(parsedTokens, en)
if err != nil {
panic(err)
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
globalStack.sp = 0
} else {
fmt.Println("\n\033[0mOk.")
}
}
func main() {
codeString := `
proc square
| takes an INT from the stack and squares it, putting the result back on the stack |
dup *
.
verFlag := flag.Bool("v", false, "Print version information and exit")
flag.BoolVar(&debug, "debug", false, "Turns on debug mode")
flag.Parse()
3
5 swap square
println`
if *verFlag {
fmt.Printf("%.2f\n", version)
return
}
lexParseInterpret(codeString)
repl()
}

View File

@ -5,6 +5,9 @@ import (
"os"
)
var ifDepth int
var whileDepth int
func parse(r *tokenReader) []token {
out := make([]token, 0, len(r.tokens))
for {
@ -15,21 +18,38 @@ func parse(r *tokenReader) []token {
switch t.kind {
case END:
return out
case SYMBOL:
case KEYWORD:
if t.val.(string) == "proc" {
out = append(out, eatProcedure(r, t.line))
} else if t.val.(string) == "break" {
if whileDepth <= 0 {
panic(fmt.Sprintf("`break` found outside of `while`/`dowhile` ... `.` construct on line %d", t.line))
}
out = append(out, t)
} else if t.val.(string) == "else" {
if ifDepth <= 0 {
panic(fmt.Sprintf("`else` found outside of `if` .. `.` construct on line %d", t.line))
}
out = append(out, t)
return out
} else if t.val.(string) == "if" {
out = append(out, eatIf(r, t.line))
} else if t.val.(string) == "while" {
out = append(out, eatWhile(r, t.line, false))
} else if t.val.(string) == "dowhile" {
out = append(out, eatWhile(r, t.line, true))
} else {
out = append(out, t)
}
case INT, FLOAT, STRING, BOOL, KEYWORD, TYPE:
case INT, FLOAT, STRING, BOOL, TYPE, SYMBOL:
out = append(out, t)
case DOCSTRING:
if debug {
fmt.Fprintf(os.Stderr, "WARN | Line %d\n | docstring found outside of proc body\n", t.line)
fmt.Fprintf(os.Stderr, "WARN │ Line %d\n │ docstring found outside of proc body\n", t.line)
}
default:
if debug {
fmt.Fprintf(os.Stderr, "WARN | Line %d\n | Unimplemented type found: %s\n", kindToString(t.kind))
fmt.Fprintf(os.Stderr, "WARN │ Line %d\n │ Unimplemented type found: %s\n", kindToString(t.kind))
}
out = append(out, t)
}
@ -37,9 +57,39 @@ func parse(r *tokenReader) []token {
return out
}
func eatIf(r *tokenReader, line int) token {
ifDepth++
i := condIf{}
i.hasFalseBranch = false
truthy := parse(r)
if len(truthy) != 0 {
last := truthy[len(truthy)-1]
if last.kind == KEYWORD && last.val.(string) == "else" {
i.hasFalseBranch = true
truthy = truthy[:len(truthy)-1]
i.falsy = parse(r)
last := i.falsy[len(truthy)-1]
if last.kind == KEYWORD && last.val.(string) == "else" {
panic(fmt.Sprintf("Extra call to `else` found on line %d", last.line))
}
}
}
i.truthy = truthy
ifDepth--
return token{IF, i, line}
}
func eatWhile(r *tokenReader, line int, doWhile bool) token {
whileDepth++
w := while{}
w.doWhile = doWhile
w.body = parse(r)
whileDepth--
return token{WHILE, w, line}
}
func eatProcedure(r *tokenReader, line int) token {
p := proc{}
p.kind = PROC
name, err := r.Read()
if name.kind != SYMBOL || err != nil {
panic(fmt.Sprintf("proc expected a name on line %d", name.line))

View File

@ -1,24 +1,31 @@
package main
import (
"errors"
"fmt"
"strings"
)
type stack struct {
s []token
sp int
}
func (p *stack) Push(t token) {
func (p *stack) Push(t token) error {
p.s[p.sp] = t
p.sp++
if p.sp >= len(p.s) {
panic("Stack overflow")
return errors.New("Stack overflow")
}
return nil
}
func (p *stack) Pop() token {
func (p *stack) Pop() (token, error) {
p.sp--
if p.sp < 0 {
panic("Stack underflow")
return token{}, errors.New("Stack underflow")
}
return p.s[p.sp]
return p.s[p.sp], nil
}
func (p stack) Peek() token {
@ -32,6 +39,23 @@ func (p stack) Depth() int {
return p.sp
}
func (p stack) String() string {
var b strings.Builder
b.WriteString(`
============
STACK DUMP
============
TOS->BOS:
`)
for i := p.sp-1; i >= 0; i-- {
b.WriteString(fmt.Sprintf("| %s ", kindToString(p.s[i].kind)))
}
b.WriteString("|\n")
return b.String()
}
func NewStack() stack {
return stack{s: make([]token, STACK_SIZE, STACK_SIZE)}
}

141
types.go
View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"strconv"
"strings"
)
@ -11,6 +12,52 @@ import (
*/
type value interface{}
/*
conditionals are implemented here as
a special form handled by the interpreter
and using the type system to provide
that feature
*/
type condIf struct {
truthy []token
falsy []token
hasFalseBranch bool
}
func (ci condIf) String() string {
var b strings.Builder
b.WriteString("if ")
for i := range ci.truthy {
b.WriteString(toString(ci.truthy[i], true))
b.WriteRune(' ')
}
if len(ci.falsy) > 0 {
b.WriteString("then ")
for i := range ci.falsy {
b.WriteString(toString(ci.falsy[i], true))
b.WriteRune(' ')
}
}
b.WriteRune('.')
return b.String()
}
type while struct {
doWhile bool // When true, functions as `do ... while` rather than `while ... do`
body []token
}
func (w while) String() string {
var b strings.Builder
b.WriteString("while ")
for i := range w.body {
b.WriteString(toString(w.body[i], true))
b.WriteRune(' ')
}
b.WriteRune('.')
return b.String()
}
/*
proc represents a procedure
@ -21,7 +68,6 @@ type proc struct {
body []token
name string
doc string
kind int
}
func (p proc) String() string {
@ -30,7 +76,98 @@ func (p proc) String() string {
b.WriteString(toString(p.body[i], true))
b.WriteRune(' ')
}
return fmt.Sprintf("proc %s |%s| %s .", p.name, p.doc, b.String())
if p.doc != "" {
return fmt.Sprintf("proc %s\n |%s|\n %s\n.", p.name, p.doc, b.String())
}
return fmt.Sprintf("proc %s\n %s\n.", p.name, b.String())
}
/*
token is the basic unit that the interpreter
understands
*/
type token struct {
kind int
val value
line int
}
func (t token) String(litRep bool) string {
var repr string
switch t.kind {
case INT:
repr = strconv.Itoa(t.val.(int))
case FLOAT:
repr = fmt.Sprintf("%f", t.val.(float64))
case BOOL:
if t.val.(bool) {
repr = "true"
} else {
repr = "false"
}
case STRING:
if litRep {
repr = fmt.Sprintf(`"%s"`, t.val.(string))
} else {
repr = fmt.Sprintf("%s", t.val.(string))
}
case IF:
repr = t.val.(condIf).String()
case WHILE:
repr = t.val.(while).String()
case KEYWORD:
repr = fmt.Sprintf("%s", t.val.(string))
case PROC:
repr = t.val.(proc).String()
default:
panic("Issue while stringifying token")
}
if debug {
return fmt.Sprintf("TOKEN %-10s Ln: %-3d %-10s\n", kindToString(t.kind), t.line, repr)
} else {
return repr
}
}
/*
tokenReader creates a readable token stream
that can be moved in both directions
*/
type tokenReader struct {
tokens []token
p int
}
func (t *tokenReader) Read() (token, error) {
t.p++
if t.p - 1 >= len(t.tokens) {
t.p--
return token{}, fmt.Errorf("Token overflow")
}
return t.tokens[t.p-1], nil
}
func (t *tokenReader) Unread() (error) {
t.p--
if t.p < 0 {
return fmt.Errorf("Token underflow")
}
return nil
}
func (t tokenReader) Current() int {
return t.p
}
func (t *tokenReader) Jump(i int) {
if i < 0 || i > len(t.tokens) {
panic("Token jump error")
}
t.p = i
}
func NewTokenReader(tokens []token) *tokenReader {
return &tokenReader{tokens, 0}
}