Add importing, lists and objects, and add basic functions (global and on values)

This commit is contained in:
Neemek 2025-03-10 16:23:16 +01:00
parent 449f7cd815
commit 634a4a2b61
Signed by: neemek
GPG key ID: 28360A8951CD0E9B
11 changed files with 919 additions and 60 deletions

View file

@ -5,6 +5,7 @@ import (
"log" "log"
"neemek.com/anglais/core" "neemek.com/anglais/core"
"os" "os"
"path/filepath"
) )
type Context struct { type Context struct {
@ -16,6 +17,37 @@ type RunCmd struct {
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"` File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
} }
// WorkingDirectoryResolver resolves imports relative to the working directory
type WorkingDirectoryResolver struct {
workingDirectory string
}
func (r *WorkingDirectoryResolver) Resolve(path string) (core.Node, error) {
pth := filepath.Join(r.workingDirectory, path)
f, err := os.ReadFile(pth)
if err != nil {
return nil, err
}
str := string(f)
l := core.NewLexer(str)
tokens, err := l.Tokenize()
if err != nil {
return nil, err
}
p := core.NewParser(tokens)
tree, err := p.Parse()
if err != nil {
return nil, err
}
return tree, nil
}
func (cmd *RunCmd) Run(ctx *Context) error { func (cmd *RunCmd) Run(ctx *Context) error {
if ctx.Debug { if ctx.Debug {
log.Println("Reading file") log.Println("Reading file")
@ -72,6 +104,15 @@ func (cmd *RunCmd) Run(ctx *Context) error {
} }
c := core.NewCompiler() c := core.NewCompiler()
if ctx.Debug {
log.Println("Setting imports resolver")
}
dir, _ := filepath.Split(cmd.File)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
})
if ctx.Debug { if ctx.Debug {
log.Println("Compiling parse tree") log.Println("Compiling parse tree")
} }
@ -162,8 +203,18 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
if ctx.Debug { if ctx.Debug {
log.Println("Initialized compiler") log.Println("Initialized compiler")
} }
c := core.NewCompiler() c := core.NewCompiler()
if ctx.Debug {
log.Println("Setting import resolver")
}
dir, _ := filepath.Split(cmd.File)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
})
if ctx.Debug { if ctx.Debug {
log.Println("Compiling parse tree") log.Println("Compiling parse tree")
} }

View file

@ -5,9 +5,16 @@ type Compiler struct {
ip Pos ip Pos
scope Pos scope Pos
imports map[string]Node
resolver ImportsResolver
stack *Stack[LocalVariable] stack *Stack[LocalVariable]
} }
type ImportsResolver interface {
Resolve(path string) (Node, error)
}
type LocalVariable struct { type LocalVariable struct {
name string name string
scope int scope int
@ -15,10 +22,11 @@ type LocalVariable struct {
func NewCompiler() *Compiler { func NewCompiler() *Compiler {
c := &Compiler{ c := &Compiler{
Chunk: NewChunk(make([]Bytecode, 0), make([]Value, 0)), Chunk: NewChunk(make([]Bytecode, 0), make([]Value, 0)),
ip: 0, ip: 0,
scope: 0, scope: 0,
stack: NewStack[LocalVariable](256), stack: NewStack[LocalVariable](256),
imports: make(map[string]Node),
} }
return c return c
@ -63,6 +71,14 @@ func (c *Compiler) Compile(tree Node) {
c.add(InstructionConstant) c.add(InstructionConstant)
c.addConstant(tree.(*NumberNode).value) c.addConstant(tree.(*NumberNode).value)
case ListNodeType:
v := tree.(*ListNode).items
c.add(InstructionNewList)
for _, n := range v {
c.Compile(n)
c.add(InstructionAppend)
}
case ReferenceNodeType: case ReferenceNodeType:
c.getVar(tree.(*ReferenceNode).name) c.getVar(tree.(*ReferenceNode).name)
@ -155,7 +171,7 @@ func (c *Compiler) Compile(tree Node) {
c.Compile(arg) c.Compile(arg)
} }
c.getVar(n.name) c.Compile(n.source)
c.add(InstructionCall) c.add(InstructionCall)
@ -194,12 +210,28 @@ func (c *Compiler) Compile(tree Node) {
n.name, n.name,
n.params, n.params,
c.Chunk, c.Chunk,
nil,
} }
// restore old chunk and ip // restore old chunk and ip
c.Chunk = mc c.Chunk = mc
c.ip = mip c.ip = mip
case AccessNodeType:
n := tree.(*AccessNode)
c.Compile(n.source)
c.add(InstructionAccessProperty)
c.addConstant(StringValue(n.property))
case ImportNodeType:
n := tree.(*ImportNode)
t := c.resolveImport(n.path).(*BlockNode)
for _, statement := range t.statements {
c.Compile(statement)
}
case ReturnNodeType: case ReturnNodeType:
c.Compile(tree.(*ReturnNode).value) c.Compile(tree.(*ReturnNode).value)
c.add(InstructionReturn) c.add(InstructionReturn)
@ -305,6 +337,26 @@ func (c *Compiler) descend() {
} }
} }
func (c *Compiler) resolveImport(path string) Node {
if chunk, ok := c.imports[path]; ok {
return chunk
}
// find tree
tree, err := c.resolver.Resolve(path)
if err != nil {
panic(err)
}
c.imports[path] = tree
return tree
}
func (c *Compiler) SetImportsResolver(resolver ImportsResolver) {
c.resolver = resolver
}
func (c *Compiler) advance(amount Pos) { func (c *Compiler) advance(amount Pos) {
c.ip += amount c.ip += amount
} }

View file

@ -267,6 +267,7 @@ func GetCompileTestData() map[string]CompileTestData {
StringValue("a"), StringValue("b"), StringValue("a"), StringValue("b"),
}, },
), ),
nil,
}, },
0, 0,
}, },
@ -296,7 +297,9 @@ func GetCompileTestData() map[string]CompileTestData {
true, true,
}, },
&CallNode{ &CallNode{
"a", &ReferenceNode{
"a",
},
[]Node{}, []Node{},
false, false,
}, },
@ -321,6 +324,7 @@ func GetCompileTestData() map[string]CompileTestData {
NumberValue(1), StringValue("b"), NumberValue(1), StringValue("b"),
}, },
), ),
nil,
}, },
0, 0,
}, },

View file

@ -34,6 +34,8 @@ const (
TokenOpenParenthesis TokenOpenParenthesis
TokenCloseParenthesis TokenCloseParenthesis
TokenOpenBracket
TokenCloseBracket
TokenOpenBrace TokenOpenBrace
TokenCloseBrace TokenCloseBrace
@ -47,6 +49,7 @@ const (
TokenVar TokenVar
TokenIf TokenIf
TokenElse TokenElse
TokenImport
TokenComma TokenComma
TokenDot TokenDot
@ -144,6 +147,10 @@ func (t TokenType) String() string {
return "double ampersand" return "double ampersand"
case TokenDoublePipe: case TokenDoublePipe:
return "double pipe" return "double pipe"
case TokenOpenBracket:
return "open bracket"
case TokenCloseBracket:
return "close bracket"
} }
return "UNDEFINED TOKENTYPE STRING CONVERSION" return "UNDEFINED TOKENTYPE STRING CONVERSION"
@ -210,6 +217,10 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenOpenParenthesis), nil return l.makeToken(TokenOpenParenthesis), nil
case ')': case ')':
return l.makeToken(TokenCloseParenthesis), nil return l.makeToken(TokenCloseParenthesis), nil
case '[':
return l.makeToken(TokenOpenBracket), nil
case ']':
return l.makeToken(TokenCloseBracket), nil
case '{': case '{':
return l.makeToken(TokenOpenBrace), nil return l.makeToken(TokenOpenBrace), nil
case '}': case '}':
@ -309,6 +320,8 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenBreakpoint), nil return l.makeToken(TokenBreakpoint), nil
case "return": case "return":
return l.makeToken(TokenReturn), nil return l.makeToken(TokenReturn), nil
case "import":
return l.makeToken(TokenImport), nil
default: default:
return l.makeToken(TokenName), nil return l.makeToken(TokenName), nil
} }

View file

@ -122,6 +122,12 @@ func GetLexerTestData() map[string]LexerTestData {
TokenOpenBrace, TokenReturn, TokenName, TokenPlus, TokenName, TokenCloseBrace, TokenOpenBrace, TokenReturn, TokenName, TokenPlus, TokenName, TokenCloseBrace,
}, },
}, },
"list": {
"data := [3, 1, 4, 1]",
[]TokenType{
TokenName, TokenDeclare, TokenOpenBracket, TokenNumber, TokenComma, TokenNumber, TokenComma, TokenNumber, TokenComma, TokenNumber, TokenCloseBracket,
},
},
} }
} }

View file

@ -19,6 +19,7 @@ const (
ReferenceNodeType ReferenceNodeType
BooleanNodeType BooleanNodeType
NilNodeType NilNodeType
ListNodeType
BinaryNodeType BinaryNodeType
BlockNodeType BlockNodeType
ConditionalNodeType ConditionalNodeType
@ -27,6 +28,8 @@ const (
CallNodeType CallNodeType
FunctionNodeType FunctionNodeType
ReturnNodeType ReturnNodeType
AccessNodeType
ImportNodeType
BreakpointNodeType BreakpointNodeType
) )
@ -58,6 +61,14 @@ func (n NodeType) String() string {
return "Function" return "Function"
case ReturnNodeType: case ReturnNodeType:
return "Return" return "Return"
case ListNodeType:
return "List"
case AccessNodeType:
return "Access"
case BreakpointNodeType:
return "Breakpoint"
case ImportNodeType:
return "Import"
} }
return "Invalid Node Type" return "Invalid Node Type"
} }
@ -101,6 +112,41 @@ func (n NumberNode) String() string {
return strconv.FormatFloat(float64(n.value), 'g', -1, NumberSize) return strconv.FormatFloat(float64(n.value), 'g', -1, NumberSize)
} }
// ListNode a list or sequence of values (items)
type ListNode struct {
items []Node
}
func (n ListNode) Type() NodeType {
return ListNodeType
}
func (n ListNode) String() string {
sb := strings.Builder{}
sb.WriteString("[")
for i, item := range n.items {
sb.WriteString(item.String())
if i > 0 {
sb.WriteString(", ")
}
}
sb.WriteString("]")
return sb.String()
}
type AccessNode struct {
source Node
property string
}
func (n AccessNode) Type() NodeType {
return AccessNodeType
}
func (n AccessNode) String() string {
return fmt.Sprintf("(%s from %s)", n.property, n.source)
}
type BinaryOperation uint type BinaryOperation uint
func (n BinaryOperation) String() string { func (n BinaryOperation) String() string {
@ -125,6 +171,10 @@ func (n BinaryOperation) String() string {
return "less or equal" return "less or equal"
case BinaryGreaterEqual: case BinaryGreaterEqual:
return "greater or equal" return "greater or equal"
case BinaryAnd:
return "and"
case BinaryOr:
return "or"
} }
return "undefined arithmetic operation" return "undefined arithmetic operation"
@ -207,6 +257,18 @@ func (n BlockNode) String() string {
return builder.String() return builder.String()
} }
type ImportNode struct {
path string
}
func (n ImportNode) Type() NodeType {
return ImportNodeType
}
func (n ImportNode) String() string {
return fmt.Sprintf("import %s", n.path)
}
// ConditionalNode conditionals (if statements) // ConditionalNode conditionals (if statements)
type ConditionalNode struct { type ConditionalNode struct {
condition Node condition Node
@ -253,9 +315,9 @@ func (n AssignNode) String() string {
// function call // function call
type CallNode struct { type CallNode struct {
name string source Node
args []Node args []Node
keep bool keep bool
} }
func (n CallNode) Type() NodeType { func (n CallNode) Type() NodeType {
@ -263,7 +325,7 @@ func (n CallNode) Type() NodeType {
} }
func (n CallNode) String() string { func (n CallNode) String() string {
return fmt.Sprintf("call %s with args (%s)", n.name, n.args) return fmt.Sprintf("call %s with args (%s)", n.source.String(), n.args)
} }
// definition of function // definition of function

View file

@ -176,6 +176,30 @@ func (p *Parser) factor() (Node, error) {
p.advance() p.advance()
return &NilNode{}, nil return &NilNode{}, nil
case TokenOpenBracket:
p.advance()
var values []Node
for !p.accept(TokenCloseBracket) {
if len(values) > 0 {
if err := p.expect(TokenComma); err != nil {
return nil, err
}
}
value, err := p.condition()
if err != nil {
return nil, err
}
values = append(values, value)
}
return &ListNode{
values,
}, nil
// unary minus // unary minus
case TokenMinus: case TokenMinus:
p.advance() p.advance()
@ -200,7 +224,9 @@ func (p *Parser) factor() (Node, error) {
} }
return &CallNode{ return &CallNode{
name, &ReferenceNode{
name,
},
args, args,
true, true,
}, nil }, nil
@ -247,8 +273,44 @@ func (p *Parser) factor() (Node, error) {
} }
} }
func (p *Parser) prop() (Node, error) {
v, err := p.factor()
if err != nil {
return nil, err
}
// parse chains of prop-getting ( "".split().join().length.round() )
for p.accept(TokenDot) {
if err := p.expect(TokenName); err != nil {
return nil, err
}
property := (*p.prev).Lexeme
v = &AccessNode{
v,
property,
}
// if called, also add
if (*p.curr).Type == TokenOpenParenthesis {
args, err := p.parseArgs()
if err != nil {
return nil, err
}
v = &CallNode{
v,
args,
true,
}
}
}
return v, nil
}
func (p *Parser) product() (Node, error) { func (p *Parser) product() (Node, error) {
left, err := p.factor() left, err := p.prop()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -260,7 +322,7 @@ func (p *Parser) product() (Node, error) {
op = BinaryDivision op = BinaryDivision
} }
f, err := p.factor() f, err := p.prop()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -414,14 +476,49 @@ func (p *Parser) statement() (Node, error) {
p.advance() p.advance()
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
if p.curr.Type == TokenOpenParenthesis { if (*p.curr).Type == TokenDot {
var v Node = &ReferenceNode{
name,
}
// parse chains of prop-getting ( "".split().join().length.round() )
for p.accept(TokenDot) {
if err := p.expect(TokenName); err != nil {
return nil, err
}
property := (*p.prev).Lexeme
v = &AccessNode{
v,
property,
}
// if called, also add
if (*p.curr).Type == TokenOpenParenthesis {
args, err := p.parseArgs()
if err != nil {
return nil, err
}
v = &CallNode{
v,
args,
(*p.curr).Type == TokenDot, // if the chain is continued, keep the value.
}
}
}
return v, nil
} else if p.curr.Type == TokenOpenParenthesis {
args, err := p.parseArgs() args, err := p.parseArgs()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &CallNode{ return &CallNode{
name, &ReferenceNode{
name,
},
args, args,
false, false,
}, nil }, nil
@ -441,6 +538,19 @@ func (p *Parser) statement() (Node, error) {
return p.condition() return p.condition()
} }
case TokenImport:
p.advance()
if err := p.expect(TokenString); err != nil {
return nil, err
}
path := p.prev.Lexeme[1 : len(p.prev.Lexeme)-1]
return &ImportNode{
path,
}, nil
case TokenFunc: case TokenFunc:
p.advance() p.advance()

View file

@ -426,6 +426,91 @@ func GetTokenTestData() map[string]TokenTestData {
}, },
}, },
}, },
"prop_getting": {
[]Token{
NewToken(TokenName, 0, 1, 0, "p"),
NewToken(TokenDeclare, 1, 2, 0, ":="),
NewToken(TokenName, 3, 1, 0, "a"),
NewToken(TokenDot, 4, 1, 0, "."),
NewToken(TokenName, 5, 1, 0, "b"),
NewToken(TokenEOF, 23, 0, 2, ""),
},
&BlockNode{
[]Node{
&AssignNode{
"p",
&AccessNode{
&ReferenceNode{
"a",
},
"b",
},
true,
},
},
},
},
"list_init": {
[]Token{
NewToken(TokenName, 0, 4, 0, "data"),
NewToken(TokenDeclare, 4, 2, 0, ":="),
NewToken(TokenOpenBracket, 6, 1, 0, "["),
NewToken(TokenName, 8, 1, 0, "a"),
NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenNumber, 8, 1, 0, "3.141"),
NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenString, 6, 1, 0, "\"Hello world!\""),
NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenTrue, 6, 1, 0, "true"),
NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenOpenBracket, 6, 1, 0, "["),
NewToken(TokenNumber, 6, 1, 0, "2"),
NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenNumber, 6, 1, 0, "3"),
NewToken(TokenCloseBracket, 6, 1, 0, "]"),
NewToken(TokenCloseBracket, 6, 1, 0, "]"),
NewToken(TokenEOF, 23, 0, 2, ""),
},
&BlockNode{
[]Node{
&AssignNode{
"data",
&ListNode{
[]Node{
&ReferenceNode{
"a",
},
&NumberNode{
3.141,
},
&StringNode{
"Hello world!",
"\"Hello world!\"",
},
&BooleanNode{
true,
},
&ListNode{
[]Node{
&NumberNode{2}, &NumberNode{3},
},
},
},
},
true,
},
},
},
},
} }
} }
@ -533,11 +618,7 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
n := n1.(*CallNode) n := n1.(*CallNode)
m := n2.(*CallNode) m := n2.(*CallNode)
if n.name != m.name { NodeEquality(t, n.source, m.source)
t.Errorf("Call node names don't match (%s and %s)", n.name, m.name)
} else {
t.Logf("Call node names match (%s)", n.name)
}
if n.keep == m.keep { if n.keep == m.keep {
t.Logf("Call node keep modifier doesn't match (%v and %v)", n.keep, m.keep) t.Logf("Call node keep modifier doesn't match (%v and %v)", n.keep, m.keep)

View file

@ -1,8 +1,12 @@
package core package core
import ( import (
"errors"
"fmt" "fmt"
"reflect"
"slices"
"strconv" "strconv"
"strings"
) )
type ValueType int type ValueType int
@ -12,6 +16,8 @@ const (
BoolValueType BoolValueType
NumberValueType NumberValueType
StringValueType StringValueType
ListValueType
ObjectValueType
FunctionValueType FunctionValueType
BuiltinFunctionValueType BuiltinFunctionValueType
VariableValueType VariableValueType
@ -23,10 +29,14 @@ func (v ValueType) String() string {
return "nil" return "nil"
case BoolValueType: case BoolValueType:
return "bool" return "bool"
case ObjectValueType:
return "object"
case NumberValueType: case NumberValueType:
return "number" return "number"
case StringValueType: case StringValueType:
return "string" return "string"
case ListValueType:
return "list"
case FunctionValueType: case FunctionValueType:
return "function" return "function"
case BuiltinFunctionValueType: case BuiltinFunctionValueType:
@ -38,30 +48,55 @@ func (v ValueType) String() string {
return "undefined" return "undefined"
} }
func GetType(v string) ValueType { // GoToValue convert go values to anglais VM-values. Works for some values (nil, bool, float64, string, slices, maps)
switch v { func GoToValue(gov interface{}) Value {
case "nil": switch v := gov.(type) {
return NilValueType case nil:
case "bool": return NilValue{}
return BoolValueType case bool:
case "number": return BoolValue(v)
return NumberValueType case float64:
case "string": return NumberValue(v)
return StringValueType case string:
case "function": return StringValue(v)
return FunctionValueType case []interface{}:
case "builtin": values := make([]Value, len(v))
return BuiltinFunctionValueType for i, value := range v {
case "variable": values[i] = GoToValue(value)
return VariableValueType }
return ListValue{
values,
}
case map[string]interface{}:
values := map[string]Value{}
for key, value := range v {
values[key] = GoToValue(value)
}
return ObjectValue{
values,
}
} }
return 0 panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov).Name()))
} }
type Value interface { type Value interface {
// Type get the type of the value (a ValueType)
Type() ValueType Type() ValueType
// String Convert this value to a string fit for human consumption
String() string String() string
// DebugString get a debug string of this value. Used in lists.
DebugString() string
// Equals Check if two values are equal
Equals(Value) bool
// Get a member from the value. An error is returned if the member does not exist
Get(string) (Value, error)
} }
type NilValue struct{} type NilValue struct{}
@ -74,6 +109,18 @@ func (v NilValue) String() string {
return "nil" return "nil"
} }
func (v NilValue) DebugString() string {
return v.String()
}
func (v NilValue) Equals(other Value) bool {
return other.Type() == NilValueType
}
func (v NilValue) Get(key string) (Value, error) {
return nil, errors.New("nil has no properties")
}
type BoolValue bool type BoolValue bool
func (v BoolValue) Type() ValueType { func (v BoolValue) Type() ValueType {
@ -88,6 +135,58 @@ func (v BoolValue) String() string {
} }
} }
func (v BoolValue) DebugString() string {
return v.String()
}
func (v BoolValue) Equals(other Value) bool {
return other.Type() == BoolValueType && bool(other.(BoolValue)) == bool(v)
}
func (v BoolValue) Get(key string) (Value, error) {
return nil, errors.New("booleans have no properties")
}
// ObjectValue An object with any number of members (key-value pairs)
type ObjectValue struct {
members map[string]Value
}
func (v ObjectValue) Type() ValueType {
return ObjectValueType
}
func (v ObjectValue) String() string {
out := "{"
for key, value := range v.members {
if out != "{" {
out += ", "
}
out += fmt.Sprintf("%q=%s", key, value.String())
}
out += "}"
return out
}
func (v ObjectValue) DebugString() string {
return v.String()
}
func (v ObjectValue) Equals(other Value) bool {
// TODO implement object equality check
return false
}
func (v ObjectValue) Get(key string) (Value, error) {
if member := v.members[key]; member == nil {
return nil, errors.New("no property found with name \"" + key + "\"")
} else {
return member, nil
}
}
// NumberValue Integer or floating-point values // NumberValue Integer or floating-point values
type NumberValue float64 type NumberValue float64
@ -101,6 +200,19 @@ func (v NumberValue) String() string {
return strconv.FormatFloat(float64(v), 'g', -1, NumberSize) return strconv.FormatFloat(float64(v), 'g', -1, NumberSize)
} }
func (v NumberValue) DebugString() string {
return v.String()
}
func (v NumberValue) Equals(other Value) bool {
return other.Type() == NumberValueType && float64(other.(NumberValue)) == float64(v)
}
func (v NumberValue) Get(key string) (Value, error) {
// TODO maybe add standard functions for number values?
return nil, errors.New("numbers have no properties")
}
type StringValue string type StringValue string
func (v StringValue) Type() ValueType { func (v StringValue) Type() ValueType {
@ -111,10 +223,179 @@ func (v StringValue) String() string {
return string(v) return string(v)
} }
func (v StringValue) DebugString() string {
return "\"" + v.String() + "\""
}
func (v StringValue) Equals(other Value) bool {
return other.Type() == StringValueType && string(other.(StringValue)) == string(v)
}
var StringPrototype = map[string]BuiltinFunctionValue{
"split": {
"split",
[]string{"seperator"},
func(vm *VM, this Value, m map[string]Value) (Value, error) {
str := this.(StringValue).String()
sep := m["seperator"].(StringValue).String()
var out []string
tmp := strings.Builder{}
for i := 0; i < len(str)-len(sep); i++ {
tmp.WriteRune([]rune(str)[i])
if str[i:i+len(sep)] == sep {
out = append(out, tmp.String())
tmp.Reset()
}
}
return GoToValue(out), nil
},
nil,
},
}
func (v StringValue) Get(key string) (Value, error) {
if prop, ok := StringPrototype[key]; ok {
return prop, nil
}
return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key))
}
// ListValue a dynamic list of values
type ListValue struct {
items []Value
}
func (v ListValue) Type() ValueType {
return ListValueType
}
func (v ListValue) String() string {
out := "["
for i, item := range v.items {
if i != 0 {
out += ", "
}
out += item.DebugString()
}
out += "]"
return out
}
func (v ListValue) DebugString() string {
return v.String()
}
func (v ListValue) Equals(other Value) bool {
return other.Type() == ListValueType &&
slices.Equal(v.items, other.(ListValue).items)
}
var ListPrototype = map[string]BuiltinFunctionValue{
"append": {
"append",
[]string{"item"},
func(_ *VM, this Value, p map[string]Value) (Value, error) {
return ListValue{
append(this.(ListValue).items, p["item"]),
}, nil
},
nil,
},
"at": {
"at",
[]string{"index"},
func(_ *VM, this Value, p map[string]Value) (Value, error) {
index := int(p["index"].(NumberValue))
items := this.(ListValue).items
if index >= len(items) {
return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
}
return items[index], nil
},
nil,
},
"length": {
"length",
[]string{},
func(_ *VM, this Value, p map[string]Value) (Value, error) {
return NumberValue(len(this.(ListValue).items)), nil
},
nil,
},
"map": {
"map",
[]string{"f"},
func(vm *VM, value Value, m map[string]Value) (Value, error) {
list := value.(ListValue)
v := m["f"]
var f Value
f, ok := v.(FunctionValue)
if !ok {
f, ok = v.(BuiltinFunctionValue)
if !ok {
return nil, errors.New(fmt.Sprintf("not a function to apply: %s", v))
}
}
for i, item := range list.items {
var err error
list.items[i], err = vm.Call(f, []Value{
item,
})
if err != nil {
return nil, err
}
}
return list, nil
},
nil,
},
"reduce": {
"reduce",
[]string{"f", "start"},
func(vm *VM, value Value, m map[string]Value) (Value, error) {
list := value.(ListValue)
f := m["f"]
sum := m["start"]
for _, v := range list.items {
result, err := vm.Call(f, []Value{sum, v})
if err != nil {
return nil, err
}
sum = result
}
return sum, nil
},
nil,
},
}
func (v ListValue) Get(key string) (Value, error) {
if prop, ok := ListPrototype[key]; ok {
return prop, nil
}
return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key))
}
type FunctionValue struct { type FunctionValue struct {
Name string Name string
Params []string Params []string
Chunk *Chunk Chunk *Chunk
Parent Value
} }
func (v FunctionValue) Type() ValueType { func (v FunctionValue) Type() ValueType {
@ -125,10 +406,25 @@ func (v FunctionValue) String() string {
return fmt.Sprintf("<function name=%s>", v.Name) return fmt.Sprintf("<function name=%s>", v.Name)
} }
func (v FunctionValue) DebugString() string {
return v.String()
}
func (v FunctionValue) Equals(other Value) bool {
return other.Type() == FunctionValueType &&
v.Name == other.(FunctionValue).Name &&
v.Chunk == other.(FunctionValue).Chunk
}
func (v FunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties")
}
type BuiltinFunctionValue struct { type BuiltinFunctionValue struct {
Name string Name string
Parameters []string Parameters []string
F func(map[string]Value) Value F func(*VM, Value, map[string]Value) (Value, error)
Parent Value
} }
func (v BuiltinFunctionValue) Type() ValueType { func (v BuiltinFunctionValue) Type() ValueType {
@ -139,6 +435,19 @@ func (v BuiltinFunctionValue) String() string {
return fmt.Sprintf("<function name=%s builtin>", v.Name) return fmt.Sprintf("<function name=%s builtin>", v.Name)
} }
func (v BuiltinFunctionValue) DebugString() string {
return v.String()
}
func (v BuiltinFunctionValue) Equals(other Value) bool {
return other.Type() == BuiltinFunctionValueType &&
v.Name == other.(BuiltinFunctionValue).Name
}
func (v BuiltinFunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties")
}
// VariableValue a value wrapper for variables kept on the stack // VariableValue a value wrapper for variables kept on the stack
type VariableValue struct { type VariableValue struct {
name string name string
@ -157,6 +466,16 @@ func (v VariableValue) String() string {
//panic("tried getting string value of a unreachable value") //panic("tried getting string value of a unreachable value")
} }
func (v VariableValue) equals(other VariableValue) bool { func (v VariableValue) DebugString() string {
return v.name == other.name && v.value == other.value return v.String()
}
func (v VariableValue) Equals(other Value) bool {
return other.Type() == VariableValueType &&
v.name == other.(VariableValue).name &&
v.value.Equals(other.(VariableValue).value)
}
func (v VariableValue) Get(_ string) (Value, error) {
return nil, errors.New("variables have no properties")
} }

View file

@ -41,6 +41,8 @@ const (
// InstructionGreaterOrEqual pops two from stack, pushes whether the lowest is greater or equal than the highest // InstructionGreaterOrEqual pops two from stack, pushes whether the lowest is greater or equal than the highest
InstructionGreaterOrEqual InstructionGreaterOrEqual
// InstructionAccessProperty gets a property from a value, and pops it onto the stack
InstructionAccessProperty
// InstructionCall pops a function object from the stack and begins execution of the chunk // InstructionCall pops a function object from the stack and begins execution of the chunk
InstructionCall InstructionCall
@ -89,6 +91,14 @@ const (
// InstructionNil Push a nil literal to the stack // InstructionNil Push a nil literal to the stack
InstructionNil InstructionNil
// InstructionNewList Push a new (empty) list to the stack
InstructionNewList
// InstructionAppend Append to a list. stack: (... > list > item) => (... > list)
InstructionAppend
// InstructionFormList Form items on the stack into a list. The 2 bytes after the instructions are the amount of
// items to include minus one. (value of 0 => 1 item, value of 1 => 2 items, etc.)
InstructionFormList
// InstructionBreakpoint for debugging purposes // InstructionBreakpoint for debugging purposes
InstructionBreakpoint InstructionBreakpoint
) )
@ -161,8 +171,16 @@ func (b Bytecode) String() string {
return "AND" return "AND"
case InstructionOr: case InstructionOr:
return "OR" return "OR"
case InstructionFormList:
return "FORM_LIST"
case InstructionBreakpoint: case InstructionBreakpoint:
return "BREAKPOINT" return "BREAKPOINT"
case InstructionNewList:
return "NEW_LIST"
case InstructionAppend:
return "APPEND"
case InstructionAccessProperty:
return "ACCESS_PROPERTY"
} }
return "UNDEFINED" return "UNDEFINED"
} }
@ -268,18 +286,32 @@ var DefaultGlobals = map[string]Value{
"write": BuiltinFunctionValue{ "write": BuiltinFunctionValue{
"write", // always remember where you come from... "write", // always remember where you come from...
[]string{"value"}, []string{"value"},
func(v map[string]Value) Value { func(_ *VM, this Value, v map[string]Value) (Value, error) {
println(v["value"].String()) println(v["value"].String())
return nil return nil, nil
}, },
nil,
}, },
"print": BuiltinFunctionValue{ "print": BuiltinFunctionValue{
"print", "print",
[]string{"value"}, []string{"value"},
func(v map[string]Value) Value { func(_ *VM, this Value, v map[string]Value) (Value, error) {
print(v["value"].String()) print(v["value"].String())
return nil return nil, nil
}, },
nil,
},
"assert": BuiltinFunctionValue{
"assert",
[]string{"condition"},
func(vm *VM, this Value, params map[string]Value) (Value, error) {
if !params["condition"].(BoolValue) {
return nil, errors.New("assertion failed")
}
return NilValue{}, nil
},
nil,
}, },
} }
@ -300,7 +332,7 @@ func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
func (vm *VM) Next() bool { func (vm *VM) Next() bool {
switch vm.NextByte() { switch vm.NextByte() {
case InstructionReturn: case InstructionReturn:
if vm.call.Current <= 0 { if vm.call.Current == 0 {
return false return false
} else { } else {
v := vm.stack.Pop() v := vm.stack.Pop()
@ -351,16 +383,14 @@ func (vm *VM) Next() bool {
vm.stack.Push(l / r) vm.stack.Push(l / r)
case InstructionEquals: case InstructionEquals:
r := vm.stack.Pop().(NumberValue) vm.stack.Push(
l := vm.stack.Pop().(NumberValue) BoolValue(vm.stack.Pop().Equals(vm.stack.Pop())),
)
vm.stack.Push(BoolValue(l == r))
case InstructionNotEqual: case InstructionNotEqual:
r := vm.stack.Pop().(NumberValue) vm.stack.Push(
l := vm.stack.Pop().(NumberValue) BoolValue(!vm.stack.Pop().Equals(vm.stack.Pop())),
)
vm.stack.Push(BoolValue(l != r))
case InstructionNot: case InstructionNot:
b := vm.stack.Pop().(BoolValue) b := vm.stack.Pop().(BoolValue)
@ -412,7 +442,6 @@ func (vm *VM) Next() bool {
scope: vm.scope, scope: vm.scope,
}) })
// TODO Fix the variables sometimes being wrongly assigned
for i := len(f.Params) - 1; i >= 0; i-- { for i := len(f.Params) - 1; i >= 0; i-- {
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i) p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
vm.stack.items[p] = &VariableValue{ vm.stack.items[p] = &VariableValue{
@ -422,6 +451,10 @@ func (vm *VM) Next() bool {
} }
} }
if f.Parent != nil {
vm.addVar("this", f.Parent)
}
vm.variableEnd = vm.stack.Current vm.variableEnd = vm.stack.Current
vm.chunk = f.Chunk vm.chunk = f.Chunk
@ -433,9 +466,14 @@ func (vm *VM) Next() bool {
args[f.Parameters[i]] = vm.stack.Pop() args[f.Parameters[i]] = vm.stack.Pop()
} }
vm.stack.Push(f.F(args)) v, err := f.F(vm, f.Parent, args)
if err != nil {
vm.error(err.Error())
}
vm.stack.Push(v)
default: default:
vm.error(fmt.Sprintf("value called is not a function (%s)", v.String())) vm.error(fmt.Sprintf("value called is not a function (%s)", v.DebugString()))
return false return false
} }
@ -495,6 +533,17 @@ func (vm *VM) Next() bool {
case InstructionNil: case InstructionNil:
vm.stack.Push(NilValue{}) vm.stack.Push(NilValue{})
case InstructionFormList:
case InstructionNewList:
vm.stack.Push(ListValue{[]Value{}})
case InstructionAppend:
value := vm.stack.Pop()
list := vm.stack.Pop().(ListValue)
list.items = append(list.items, value)
vm.stack.Push(list)
case InstructionDescend: case InstructionDescend:
vm.descend() vm.descend()
@ -517,6 +566,28 @@ func (vm *VM) Next() bool {
vm.stack.Push(r, l) vm.stack.Push(r, l)
case InstructionAccessProperty:
source := vm.stack.Pop()
property := vm.ReadConstant()
member, err := source.Get(property.(StringValue).String())
if err != nil {
vm.error(err.Error())
}
// add parent if function with a little switcheroo
if member.Type() == FunctionValueType {
f := member.(FunctionValue)
f.Parent = source
member = f
} else if member.Type() == BuiltinFunctionValueType {
f := member.(BuiltinFunctionValue)
f.Parent = source
member = f
}
vm.stack.Push(member)
case InstructionBreakpoint: case InstructionBreakpoint:
default: default:
@ -526,6 +597,52 @@ func (vm *VM) Next() bool {
return true return true
} }
func (vm *VM) Call(v Value, args []Value) (Value, error) {
switch f := v.(type) {
case FunctionValue:
vm.call.Push(Call{
chunk: vm.chunk,
ip: vm.ip,
stackEnd: vm.stack.Current,
variableEnd: vm.variableEnd,
scope: vm.scope,
})
for i := 0; i < len(f.Params); i++ {
vm.addVar(f.Params[i], args[i])
}
if f.Parent != nil {
vm.addVar("this", f.Parent)
}
vm.variableEnd = vm.stack.Current
vm.chunk = f.Chunk
vm.ip = 0
for vm.chunk.Bytecode[vm.ip] != InstructionReturn && vm.Next() {
}
if vm.HasNext() {
vm.Next()
}
return vm.stack.Pop(), nil
case BuiltinFunctionValue:
argies := map[string]Value{}
for i, arg := range args {
argies[f.Parameters[i]] = arg
}
return f.F(vm, f.Parent, argies)
}
return nil, errors.New(fmt.Sprintf("value is not a function (%s)", v.DebugString()))
}
func (vm *VM) TryNextByte() (Bytecode, error) { func (vm *VM) TryNextByte() (Bytecode, error) {
if !vm.HasNext() { if !vm.HasNext() {
return 0, errors.New("there are no more instructions") return 0, errors.New("there are no more instructions")

View file

@ -3,11 +3,44 @@
package main package main
import ( import (
"errors"
"log" "log"
"neemek.com/anglais/core" "neemek.com/anglais/core"
"syscall/js" "syscall/js"
) )
type JsResolver struct {
jsResolver js.Value
}
func (r *JsResolver) Resolve(name string) (core.Node, error) {
jsv := r.jsResolver.Invoke(name)
if jsv.Type() == js.TypeUndefined {
return nil, errors.New("cannot find import with name " + name)
}
if jsv.Type() != js.TypeString {
return nil, errors.New("invalid value for source: " + jsv.String())
}
source := jsv.String()
l := core.NewLexer(source)
tokens, err := l.Tokenize()
if err != nil {
return nil, err
}
p := core.NewParser(tokens)
tree, err := p.Parse()
if err != nil {
return nil, err
}
return tree, nil
}
func jsError(err error) interface{} { func jsError(err error) interface{} {
return jsErrorOfString(err.Error()) return jsErrorOfString(err.Error())
} }
@ -22,6 +55,7 @@ func jsErrorOfString(err string) interface{} {
func run(this js.Value, args []js.Value) interface{} { func run(this js.Value, args []js.Value) interface{} {
source := args[0].String() source := args[0].String()
outputHandler := args[1] outputHandler := args[1]
resolver := args[2]
log.Printf("got source: %s", source) log.Printf("got source: %s", source)
lexer := core.NewLexer(source) lexer := core.NewLexer(source)
@ -45,6 +79,16 @@ func run(this js.Value, args []js.Value) interface{} {
compiler := core.NewCompiler() compiler := core.NewCompiler()
compiler.SetImportsResolver(&JsResolver{
resolver,
})
defer func() {
if err := recover(); err != nil {
log.Printf("panic recovered: %v", err)
}
}()
compiler.Compile(tree) compiler.Compile(tree)
log.Printf("Compiled tree (into %i instructions)", len(compiler.Chunk.Bytecode)) log.Printf("Compiled tree (into %i instructions)", len(compiler.Chunk.Bytecode))
@ -55,19 +99,19 @@ func run(this js.Value, args []js.Value) interface{} {
vm.SetGlobal("write", core.BuiltinFunctionValue{ vm.SetGlobal("write", core.BuiltinFunctionValue{
Name: "write", Name: "write",
Parameters: []string{"value"}, Parameters: []string{"value"},
F: func(v map[string]core.Value) core.Value { F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) {
log.Printf("Writing value: %s", v["value"].String()) log.Printf("Writing value: %s", v["value"].String())
outputHandler.Invoke(js.ValueOf(v["value"].String() + "\n")) outputHandler.Invoke(js.ValueOf(v["value"].String() + "\n"))
return nil return nil, nil
}, },
}) })
vm.SetGlobal("print", core.BuiltinFunctionValue{ vm.SetGlobal("print", core.BuiltinFunctionValue{
Name: "print", Name: "print",
Parameters: []string{"value"}, Parameters: []string{"value"},
F: func(v map[string]core.Value) core.Value { F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) {
log.Printf("Printing value: %s", v["value"].String()) log.Printf("Printing value: %s", v["value"].String())
outputHandler.Invoke(js.ValueOf(v["value"].String())) outputHandler.Invoke(js.ValueOf(v["value"].String()))
return nil return nil, nil
}, },
}) })