felise/types.go

354 lines
7.0 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 (
"fmt"
"strconv"
"strings"
)
/*
value represents any runtime value
that the interpreter can represent
*/
type value interface{}
type dict struct {
body map[string]token
}
func (d dict) String() string {
var b strings.Builder
b.WriteString("{ ")
for k, v := range d.body {
b.WriteString(fmt.Sprintf("\"%s\" %s ", k, v.String(true)))
}
b.WriteString("} ")
return b.String()
}
func (d *dict) Update(k, v token) error {
if k.kind != STRING {
return fmt.Errorf("DICT keys must be of type STRING, not %s", kindToString(k.kind))
}
d.body[k.val.(string)] = v
return nil
}
func (d dict) Get(k string) (token, error) {
if v, ok := d.body[k]; ok {
return v, nil
}
return token{}, fmt.Errorf("Invalid reference. DICT does not have key %q", k)
}
func (d *dict) DeleteKey(k string) {
delete(d.body, k)
}
func NewDict() dict {
return dict{make(map[string]token)}
}
type list struct {
body []token
}
func (l list) String() string {
var b strings.Builder
b.WriteString("[ ")
for _, t := range l.body {
b.WriteString(toString(t, true))
b.WriteRune(' ')
}
b.WriteString("] ")
return b.String()
}
func (l list) Join(d string) string {
sl := make([]string, len(l.body))
for i, t := range l.body {
sl[i] = t.String(false)
}
return strings.Join(sl, d)
}
func (l *list) Append(val token) error {
switch val.kind {
case INT, FLOAT, STRING, BOOL, TYPE, LIST, DICT:
break
default:
return fmt.Errorf("Lists cannot contain %s", kindToString(val.kind))
}
l.body = append(l.body, val)
return nil
}
func (l list) Get(ind int) (token, error) {
if ind < 0 {
ind = len(l.body) + ind + 1
}
if ind > len(l.body) || ind < 1 {
return token{}, fmt.Errorf("Invalid index %d for length %d", ind, len(l.body))
}
return l.body[ind-1], nil
}
func (l *list) Set(ind int, val token) error {
if ind < 0 {
ind = len(l.body) + ind + 1
}
if ind > len(l.body) || ind < 1 {
return fmt.Errorf("Invalid index %d for length %d", ind, len(l.body))
}
l.body[ind-1] = val
return nil
}
func NewList(kind int) list {
return list{make([]token, 0, 10)}
}
/*
try/catch is the primary error handling mechanism
in felise. It is basically a short circuiting `if`
where if an error is encountered in the try, the
catch will be run
*/
type try struct {
try []token
catch []token
}
func (t try) String() string {
var b strings.Builder
b.WriteString("TRY ")
for i := range t.try {
b.WriteString(toString(t.try[i], true))
b.WriteRune(' ')
}
if len(t.catch) > 0 {
b.WriteString("catch ")
for i := range t.catch {
b.WriteString(toString(t.catch[i], true))
b.WriteRune(' ')
}
}
b.WriteRune('.')
return b.String()
}
/*
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
the kind will always be == PROC (defined
as a constant in globals.go)
*/
type proc struct {
body []token
name string
doc string
hasArg bool
}
func (p proc) String() string {
var b strings.Builder
for i := range p.body {
b.WriteString(toString(p.body[i], true))
b.WriteRune(' ')
}
ptype := "proc"
if p.hasArg {
ptype = "proc!"
}
if p.doc != "" {
return fmt.Sprintf("%s %s\n |%s|\n %s\n.", ptype, p.name, p.doc, b.String())
}
return fmt.Sprintf("%s %s\n %s\n.", ptype, p.name, b.String())
}
func (p proc) injectToken(t token) []token {
out := make([]token, len(p.body))
copy(out, p.body)
out = replacer(out, t)
return out
}
func replacer(tokens []token, t token) []token {
tr := NewTokenReader(tokens)
for {
tok, err := tr.Read()
if err != nil {
break
}
if tok.kind == SYMBOL && tok.val.(string) == "procarg" {
tr.Update(t)
} else if tok.kind == IF {
tr.Update(token{tok.kind, condIf{replacer(tok.val.(condIf).truthy, t), replacer(tok.val.(condIf).falsy, t), tok.val.(condIf).hasFalseBranch}, tok.line, tok.file})
} else if tok.kind == WHILE {
tr.Update(token{tok.kind, while{tok.val.(while).doWhile, replacer(tok.val.(while).body, t)}, tok.line, tok.file})
}
}
return tr.Tokens()
}
/*
token is the basic unit that the interpreter
understands
*/
type token struct {
kind int
val value
line int
file string
}
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 LIST:
l := t.val.(list)
repr = l.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()
case DICT:
d := t.val.(dict)
repr = d.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 EOF")
}
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) Update(tok token) {
t.tokens[t.p-1] = tok
}
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 (t tokenReader) Tokens() []token {
return t.tokens
}
func NewTokenReader(tokens []token) *tokenReader {
return &tokenReader{tokens, 0}
}