not done yet

This commit is contained in:
Neemek 2025-03-30 10:42:28 +02:00
parent b73af5f92f
commit 49c15f6fb6
15 changed files with 473 additions and 389 deletions

5
bad.ang Normal file
View file

@ -0,0 +1,5 @@
import "lib/list.ang"
write(str(map([1, 2, 3], func(n: number) number {
return n*n
})))

View file

@ -6,6 +6,7 @@ import (
"log" "log"
"neemek.com/anglais/core" "neemek.com/anglais/core"
"os" "os"
"path"
"path/filepath" "path/filepath"
) )
@ -24,123 +25,132 @@ type WorkingDirectoryResolver struct {
workingDirectory string workingDirectory string
} }
func (r *WorkingDirectoryResolver) Resolve(path string) (core.Node, error) { func (r *WorkingDirectoryResolver) Resolve(path string) (string, error) {
pth := filepath.Join(r.workingDirectory, path) pth := filepath.Join(r.workingDirectory, path)
f, err := os.ReadFile(pth) f, err := os.ReadFile(pth)
if err != nil {
return "", err
}
return string(f), nil
}
func (r *WorkingDirectoryResolver) IsSame(a, b string) bool {
apath := filepath.Clean(filepath.Join(r.workingDirectory, a))
bpath := filepath.Clean(filepath.Join(r.workingDirectory, b))
return apath == bpath
}
func makeChunk(ctx *Context, filepath string, ignoreWarnings bool) (*core.Chunk, error) {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(filepath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
src := string(f) src := string(f)
if ctx.Debug {
log.Println("Initialized lexer")
}
l := core.NewLexer(src) l := core.NewLexer(src)
if ctx.Debug {
log.Println("Lexing all tokens")
}
tokens, err := l.Tokenize() tokens, err := l.Tokenize()
if err != nil { if err != nil {
return nil, err return nil, err
} }
p := core.NewParser(tokens) if len(tokens) <= 1 {
return nil, errors.New("empty file")
}
if ctx.Debug {
log.Printf("Lexed %d tokens", len(tokens))
}
p := core.NewParser(src, tokens)
if ctx.Debug {
log.Println("Initialized parser")
}
tree, err := p.Parse() tree, err := p.Parse()
if err != nil {
return nil, err if ctx.Debug {
log.Printf("Parsed tree, meaning:\n%s", tree)
} }
return tree, nil // if there were parsing errors, print them out
if err != nil {
print(err.(core.ParsingError).Format())
log.Fatal("Parsing had errors")
}
if ctx.Debug {
log.Println("Initialized compiler")
}
c := core.NewCompiler([]rune(src))
if ctx.Debug {
log.Println("Setting imports resolver")
}
dir, _ := path.Split(filepath)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
})
if ctx.Debug {
log.Println("Compiling parse tree")
}
err = c.Compile(tree)
if err != nil {
var e core.FormatedError
if errors.As(err, &e) {
log.Fatal(e.Format())
}
log.Fatal(err)
}
// if there were non-critical warnings, report them
if !ignoreWarnings && len(c.Warnings) != 0 {
for _, warning := range c.Warnings {
log.Println(warning.Format())
}
log.Fatal("compiler reported warning(s) (ignore warnings with the --ignore-warnings option)")
}
return c.Chunk, nil
} }
func (cmd *RunCmd) Run(ctx *Context) error { func (cmd *RunCmd) Run(ctx *Context) error {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(cmd.File)
if err != nil {
return err
}
var chunk *core.Chunk var chunk *core.Chunk
if !cmd.Bytecode { if !cmd.Bytecode {
src := string(f) c, err := makeChunk(ctx, cmd.File, cmd.IgnoreWarnings)
if ctx.Debug {
log.Println("Initialized lexer")
}
l := core.NewLexer(src)
if ctx.Debug {
log.Println("Lexing all tokens")
}
tokens, err := l.Tokenize()
if err != nil { if err != nil {
log.Fatal(err) return err
} }
chunk = c
if len(tokens) <= 1 {
log.Fatal("Empty file")
}
if ctx.Debug {
log.Printf("Lexed %d tokens", len(tokens))
}
p := core.NewParser(tokens)
if ctx.Debug {
log.Println("Initialized parser")
}
tree, err := p.Parse()
if ctx.Debug {
log.Printf("Parsed tree, meaning:\n%s", tree)
}
// if there were parsing errors, print them out
if err != nil {
print(err.(core.ParsingError).Format([]rune(src)))
log.Fatal("Parsing had errors")
}
if ctx.Debug {
log.Println("Initialized compiler")
}
c := core.NewCompiler([]rune(src))
if ctx.Debug {
log.Println("Setting imports resolver")
}
dir, _ := filepath.Split(cmd.File)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
})
if ctx.Debug {
log.Println("Compiling parse tree")
}
err = c.Compile(tree)
if err != nil {
var e core.CompilerError
if errors.As(err, &e) {
log.Fatal(e.Format())
}
log.Fatal(err)
}
// if there were non-critical warnings, report them
if !cmd.IgnoreWarnings && len(c.Warnings) != 0 {
for _, warning := range c.Warnings {
log.Println(warning.Format())
}
log.Fatal("compiler reported warning(s) (ignore warnings with the --ignore-warnings option)")
}
chunk = c.Chunk
} else { } else {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(cmd.File)
if err != nil {
return err
}
if ctx.Debug { if ctx.Debug {
log.Println("Registering GOB types") log.Println("Registering GOB types")
} }
@ -175,87 +185,17 @@ func (cmd *RunCmd) Run(ctx *Context) error {
} }
type CompileCmd struct { type CompileCmd struct {
File string `arg:"" name:"file" help:"File to compile program from" type:"existingfile"` File string `arg:"" name:"file" help:"File to compile program from" type:"existingfile"`
Output string `arg:"" name:"output" help:"File path to output bytecode to" type:"path"` Output string `arg:"" name:"output" help:"File path to output bytecode to" type:"path"`
IgnoreWarnings bool `name:"ignore-warnings" help:"Ignore warning messages"`
} }
func (cmd *CompileCmd) Run(ctx *Context) error { func (cmd *CompileCmd) Run(ctx *Context) error {
if ctx.Debug { c, err := makeChunk(ctx, cmd.File, cmd.IgnoreWarnings)
log.Println("Reading file")
}
f, err := os.ReadFile(cmd.File)
if err != nil { if err != nil {
return err return err
} }
src := string(f)
if ctx.Debug {
log.Println("Initializing lexer")
}
l := core.NewLexer(src)
if ctx.Debug {
log.Println("Lexing all tokens")
}
tokens, err := l.Tokenize()
if err != nil {
log.Fatal(err)
}
if ctx.Debug {
log.Println("Initializing parser")
}
p := core.NewParser(tokens)
if ctx.Debug {
log.Println("Parsing tree")
}
tree, err := p.Parse()
if err != nil {
log.Fatal(err.(*core.ParsingError).Format([]rune(src)))
}
if ctx.Debug {
log.Println("Initialized compiler")
}
c := core.NewCompiler([]rune(src))
if ctx.Debug {
log.Println("Setting import resolver")
}
dir, _ := filepath.Split(cmd.File)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
})
if ctx.Debug {
log.Println("Compiling parse tree")
}
err = c.Compile(tree)
if err != nil {
var e core.CompilerError
if errors.As(err, &e) {
log.Fatal(e.Format())
}
log.Fatal(err)
}
// if there were non-critical warnings, report them
if len(c.Warnings) != 0 {
for _, warning := range c.Warnings {
log.Println(warning.Format())
}
}
if ctx.Debug { if ctx.Debug {
log.Println("Registering GOB types") log.Println("Registering GOB types")
} }
@ -266,7 +206,7 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
log.Println("Serializing chunk") log.Println("Serializing chunk")
} }
serialized := c.Chunk.Serialize() serialized := c.Serialize()
if ctx.Debug { if ctx.Debug {
log.Println("Writing file") log.Println("Writing file")

View file

@ -69,6 +69,48 @@ func GetAllTestCases() map[string]AllTestCase {
}, },
}, },
}, },
"constant_list_concat": {
"a := [1, 2] + [3]",
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
0,
},
},
},
"list_concat": {
"a := [1, 2]\nb := a + [3]",
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
0,
},
&VariableValue{
"b",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
0,
},
},
},
} }
} }
@ -102,7 +144,7 @@ func TestAll(t *testing.T) {
c := NewCompiler([]rune(tc.src)) c := NewCompiler([]rune(tc.src))
t.Log("Compiling parse tree") t.Log("Compiling parse tree")
err = c.Compile(tree) err = c.compile(tree)
if err != nil { if err != nil {
t.Fatalf("Compiler had an error: %s", err) t.Fatalf("Compiler had an error: %s", err)
} }
@ -135,7 +177,7 @@ func BenchmarkAll(b *testing.B) {
tree, _ := p.Parse() tree, _ := p.Parse()
c := NewCompiler([]rune(tc.src)) c := NewCompiler([]rune(tc.src))
_ = c.Compile(tree) _ = c.compile(tree)
vm := NewVM(c.Chunk, 256, 256) vm := NewVM(c.Chunk, 256, 256)

View file

@ -2,6 +2,7 @@ package core
import ( import (
"fmt" "fmt"
"log"
"strings" "strings"
) )
@ -10,16 +11,18 @@ type Compiler struct {
ip Pos ip Pos
scope Pos scope Pos
imports map[string]Node imports []string
resolver ImportsResolver importStack *Stack[string]
source []rune resolver ImportsResolver
Warnings []CompilerError source []rune
Warnings []CompilerError
stack *Stack[LocalVariable] stack *Stack[LocalVariable]
} }
type ImportsResolver interface { type ImportsResolver interface {
Resolve(path string) (Node, error) Resolve(path string) (string, error)
IsSame(a, b string) bool
} }
type LocalVariable struct { type LocalVariable struct {
@ -106,7 +109,8 @@ func NewCompiler(source []rune) *Compiler {
NewChunk(make([]Bytecode, 0), make([]Value, 0)), NewChunk(make([]Bytecode, 0), make([]Value, 0)),
0, 0,
0, 0,
make(map[string]Node), make([]string, 0),
NewStack[string](256),
nil, nil,
source, source,
[]CompilerError{}, []CompilerError{},
@ -141,7 +145,17 @@ func (c *Compiler) addConstant(value Value) {
c.add(Bytecode(len(chunk.Constants) - 1)) c.add(Bytecode(len(chunk.Constants) - 1))
} }
func (c *Compiler) Compile(tree Node) error { func (c *Compiler) Compile(p *Program) error {
for _, i := range p.Imports {
if err := c.resolveImport(i); err != nil {
return err
}
}
return c.compile(p.Block)
}
func (c *Compiler) compile(tree Node) error {
if tree == nil { if tree == nil {
panic("compile called with nil value") panic("compile called with nil value")
} }
@ -172,7 +186,7 @@ func (c *Compiler) Compile(tree Node) error {
c.addConstant(v) c.addConstant(v)
} else { } else {
for _, n := range l.items { for _, n := range l.items {
err := c.Compile(n) err := c.compile(n)
if err != nil { if err != nil {
return err return err
} }
@ -200,7 +214,7 @@ func (c *Compiler) Compile(tree Node) error {
c.add(InstructionConstant) c.add(InstructionConstant)
c.addConstant(v) c.addConstant(v)
} else { } else {
err := c.Compile(tree.(*UnaryNode).value) err := c.compile(tree.(*UnaryNode).value)
if err != nil { if err != nil {
return err return err
} }
@ -226,7 +240,7 @@ func (c *Compiler) Compile(tree Node) error {
case BlockNodeType: case BlockNodeType:
c.addDescend() c.addDescend()
for _, n := range tree.(*BlockNode).statements { for _, n := range tree.(*BlockNode).statements {
err := c.Compile(n) err := c.compile(n)
if err != nil { if err != nil {
return err return err
} }
@ -246,7 +260,7 @@ func (c *Compiler) Compile(tree Node) error {
} }
// the stack should have whether the condition was truthful // the stack should have whether the condition was truthful
err = c.Compile(n.condition) err = c.compile(n.condition)
if err != nil { if err != nil {
return err return err
} }
@ -259,7 +273,7 @@ func (c *Compiler) Compile(tree Node) error {
c.advance(2) c.advance(2)
// this part would be executed if the value was true // this part would be executed if the value was true
err = c.Compile(n.do) err = c.compile(n.do)
if err != nil { if err != nil {
return err return err
} }
@ -277,7 +291,7 @@ func (c *Compiler) Compile(tree Node) error {
c.putU16(jumpByPos, uint16(c.ip-jumpByPos-2)) c.putU16(jumpByPos, uint16(c.ip-jumpByPos-2))
if n.otherwise != nil { if n.otherwise != nil {
err := c.Compile(n.otherwise) err := c.compile(n.otherwise)
if err != nil { if err != nil {
return err return err
} }
@ -297,7 +311,7 @@ func (c *Compiler) Compile(tree Node) error {
} }
conditionPos := c.ip conditionPos := c.ip
err = c.Compile(n.condition) err = c.compile(n.condition)
if err != nil { if err != nil {
return err return err
} }
@ -306,7 +320,7 @@ func (c *Compiler) Compile(tree Node) error {
jumpValuePos := c.ip jumpValuePos := c.ip
c.advance(2) c.advance(2)
err = c.Compile(n.do) err = c.compile(n.do)
if err != nil { if err != nil {
return err return err
} }
@ -322,13 +336,13 @@ func (c *Compiler) Compile(tree Node) error {
if n.name == "_" { if n.name == "_" {
// allow non-ish statements // allow non-ish statements
err := c.Compile(n.value) err := c.compile(n.value)
if err != nil { if err != nil {
return err return err
} }
c.add(InstructionPop) c.add(InstructionPop)
} else { } else {
if c.isVarDeclaredHere(n.name) { if n.declare && c.isVarDeclaredHere(n.name) {
return c.error(fmt.Sprintf("%s is already declared in this scope", n.name), n) return c.error(fmt.Sprintf("%s is already declared in this scope", n.name), n)
} }
@ -370,13 +384,13 @@ func (c *Compiler) Compile(tree Node) error {
return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg) return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg)
} }
err = c.Compile(arg) err = c.compile(arg)
if err != nil { if err != nil {
return err return err
} }
} }
err = c.Compile(n.source) err = c.compile(n.source)
if err != nil { if err != nil {
return err return err
} }
@ -413,6 +427,7 @@ func (c *Compiler) Compile(tree Node) error {
// reset instruction pointer (ip) // reset instruction pointer (ip)
c.ip = 0 c.ip = 0
c.descend()
for _, p := range n.parameters { for _, p := range n.parameters {
c.registerVar(p.Name, p.Signature) c.registerVar(p.Name, p.Signature)
} }
@ -421,14 +436,11 @@ func (c *Compiler) Compile(tree Node) error {
return err return err
} }
err = c.Compile(n.logic) err = c.compile(n.logic)
if err != nil { if err != nil {
return err return err
} }
c.ascend()
if n.logic.Type() != BlockNodeType {
c.stack.Pop()
}
mc.Constants[fi] = &FunctionValue{ mc.Constants[fi] = &FunctionValue{
n.name, n.name,
@ -444,7 +456,7 @@ func (c *Compiler) Compile(tree Node) error {
case AccessNodeType: case AccessNodeType:
n := tree.(*AccessNode) n := tree.(*AccessNode)
err := c.Compile(n.source) err := c.compile(n.source)
if err != nil { if err != nil {
return err return err
} }
@ -453,20 +465,8 @@ func (c *Compiler) Compile(tree Node) error {
n.property, n.property,
}) })
case ImportNodeType:
n := tree.(*ImportNode)
t := c.resolveImport(n.path).(*BlockNode)
for _, statement := range t.statements {
err := c.Compile(statement)
if err != nil {
return err
}
}
case ReturnNodeType: case ReturnNodeType:
err := c.Compile(tree.(*ReturnNode).value) err := c.compile(tree.(*ReturnNode).value)
if err != nil { if err != nil {
return err return err
} }
@ -494,11 +494,11 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
return nil return nil
} }
err := c.Compile(binary.Left) err := c.compile(binary.Left)
if err != nil { if err != nil {
return err return err
} }
err = c.Compile(binary.Right) err = c.compile(binary.Right)
if err != nil { if err != nil {
return err return err
} }
@ -512,6 +512,8 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
if res.Type() == TypeString { if res.Type() == TypeString {
c.add(InstructionStringConcatenation) c.add(InstructionStringConcatenation)
} else if res.Type() == TypeList {
c.add(InstructionConcatLists)
} else { } else {
c.add(InstructionAdd) c.add(InstructionAdd)
} }
@ -574,12 +576,16 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
if contents == nil { if contents == nil {
contents = sig contents = sig
} else { } else if !contents.Matches(sig) {
contents = &AnySignature{} contents = &AnySignature{}
break break
} }
} }
if contents == nil {
return nil, c.error("can't deduce content type", n)
}
return &ListSignature{ return &ListSignature{
contents, contents,
}, nil }, nil
@ -594,7 +600,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
return nil, err return nil, err
} }
if l != r { if !l.Matches(r) {
return nil, c.error(fmt.Sprintf("cannot perform binary %s on different types: %s and %s", n.BinaryOperation, l, r), n) return nil, c.error(fmt.Sprintf("cannot perform binary %s on different types: %s and %s", n.BinaryOperation, l, r), n)
} }
@ -611,6 +617,10 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
return &StringSignature{}, nil return &StringSignature{}, nil
case TypeNumber: case TypeNumber:
return &NumberSignature{}, nil return &NumberSignature{}, nil
case TypeList:
return &ListSignature{
l.(*ListSignature).Contents,
}, nil
default: default:
return nil, c.error(fmt.Sprintf("cannot perform binary addition on type %s", l), n) return nil, c.error(fmt.Sprintf("cannot perform binary addition on type %s", l), n)
} }
@ -774,6 +784,7 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
return err return err
} }
log.Printf("try affirming %s matches %s", v, sig)
if !sig.Matches(v) { if !sig.Matches(v) {
return c.error(fmt.Sprintf("function cannot return a value with type %s. defined to be %s", v, sig), n.value) return c.error(fmt.Sprintf("function cannot return a value with type %s. defined to be %s", v, sig), n.value)
} }
@ -857,7 +868,7 @@ func (c *Compiler) getVar(name string) {
} }
func (c *Compiler) setVar(name string, value Node, declare bool) error { func (c *Compiler) setVar(name string, value Node, declare bool) error {
err := c.Compile(value) err := c.compile(value)
if err != nil { if err != nil {
return err return err
} }
@ -915,7 +926,7 @@ func (c *Compiler) isTreeConstant(tree Node) bool {
case BinaryNodeType: case BinaryNodeType:
return c.isTreeConstant(tree.(*BinaryNode).Left) && c.isTreeConstant(tree.(*BinaryNode).Right) return c.isTreeConstant(tree.(*BinaryNode).Left) && c.isTreeConstant(tree.(*BinaryNode).Right)
case BlockNodeType, ConditionalNodeType, LoopNodeType, AssignNodeType, CallNodeType, FunctionNodeType, case BlockNodeType, ConditionalNodeType, LoopNodeType, AssignNodeType, CallNodeType, FunctionNodeType,
ReturnNodeType, AccessNodeType, BreakpointNodeType, ImportNodeType, ReferenceNodeType: ReturnNodeType, AccessNodeType, BreakpointNodeType, ReferenceNodeType:
return false return false
default: default:
panic(fmt.Sprintf("unexpected node %s", tree)) panic(fmt.Sprintf("unexpected node %s", tree))
@ -984,10 +995,42 @@ func (c *Compiler) computeBinary(n *BinaryNode) (Value, error) {
return nil, err return nil, err
} }
if l.Type() != r.Type() {
return nil, c.error(fmt.Sprintf("cannot perform binary %s on different types %s and %s", n.BinaryOperation, l.Type(), r.Type()), n)
}
// perform type check
switch n.BinaryOperation {
case BinaryAddition:
if l.Type() != StringValueType && l.Type() != NumberValueType && l.Type() != ListValueType {
return nil, c.error(fmt.Sprintf("cannot add values of type %s", l.Type()), n)
}
case BinarySubtraction, BinaryMultiplication, BinaryDivision, BinaryLess, BinaryGreater, BinaryLessEqual, BinaryGreaterEqual:
if l.Type() != NumberValueType {
return nil, c.error(fmt.Sprintf("cannot do binary %s on non-number type %s", n.BinaryOperation, l.Type()), n)
}
case BinaryAnd, BinaryOr:
if l.Type() != BoolValueType {
return nil, c.error(fmt.Sprintf("cannot do binary %s on non-boolean type %s", n.BinaryOperation, l.Type()), n)
}
case BinaryEquality, BinaryInequality:
// can compare all types with themselves
default:
}
var v interface{} var v interface{}
switch n.BinaryOperation { switch n.BinaryOperation {
case BinaryAddition: case BinaryAddition:
v = l.(*NumberValue).Number + r.(*NumberValue).Number switch l.Type() {
case NumberValueType:
v = l.(*NumberValue).Number + r.(*NumberValue).Number
case StringValueType:
v = l.(*StringValue).Text + r.(*StringValue).Text
case ListValueType:
v = append(l.(*ListValue).Items, r.(*ListValue).Items...)
default:
return nil, c.error(fmt.Sprintf("cannot perform binary add on type %s", l.Type()), n)
}
case BinarySubtraction: case BinarySubtraction:
v = l.(*NumberValue).Number - r.(*NumberValue).Number v = l.(*NumberValue).Number - r.(*NumberValue).Number
case BinaryMultiplication: case BinaryMultiplication:
@ -1058,20 +1101,47 @@ func (c *Compiler) warn(msg string, causer Node) {
c.Warnings = append(c.Warnings, c.error(msg, causer)) c.Warnings = append(c.Warnings, c.error(msg, causer))
} }
func (c *Compiler) resolveImport(path string) Node { func (c *Compiler) resolveImport(path string) error {
if chunk, ok := c.imports[path]; ok { // if already imported and available
return chunk for _, i := range c.imports {
if c.resolver.IsSame(path, i) {
return nil
}
} }
// find tree src, err := c.resolver.Resolve(path)
tree, err := c.resolver.Resolve(path)
if err != nil { if err != nil {
panic(err) return err
} }
c.imports[path] = tree l := NewLexer(src)
tokens, err := l.Tokenize()
if err != nil {
return err
}
return tree parser := NewParser(src, tokens)
p, err := parser.Parse()
if err != nil {
return err
}
oldChunk := c.Chunk
oldSrc := c.source
c.Chunk = NewChunk([]Bytecode{}, []Value{})
c.source = []rune(src)
if err := c.Compile(p); err != nil {
return err
}
c.imports[path] = c.Chunk
c.Chunk = oldChunk
c.source = oldSrc
return nil
} }
func (c *Compiler) SetImportsResolver(resolver ImportsResolver) { func (c *Compiler) SetImportsResolver(resolver ImportsResolver) {
@ -1087,7 +1157,7 @@ func (c *Compiler) addU16(v uint16) {
c.add(Bytecode(v & 0xff)) // last 8 bits c.add(Bytecode(v & 0xff)) // last 8 bits
} }
// putU16 put a unsigned 16-bit value at an arbitrary position. // putU16 put an unsigned 16-bit value at an arbitrary position.
// p is the position before the value // p is the position before the value
func (c *Compiler) putU16(p Pos, v uint16) { func (c *Compiler) putU16(p Pos, v uint16) {
// save original position // save original position

View file

@ -502,7 +502,7 @@ func TestCompile(t *testing.T) {
c := NewCompiler([]rune(testCase.tree.String())) c := NewCompiler([]rune(testCase.tree.String()))
t.Log("Compiling node tree") t.Log("Compiling node tree")
err := c.Compile(testCase.tree) err := c.compile(testCase.tree)
if err != nil { if err != nil {
t.Fatalf("Compiling failed: %v", err) t.Fatalf("Compiling failed: %v", err)
} }
@ -529,7 +529,7 @@ func BenchmarkCompile(b *testing.B) {
b.Run(name, func(b *testing.B) { b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
c := NewCompiler([]rune{}) c := NewCompiler([]rune{})
_ = c.Compile(testCase.tree) _ = c.compile(testCase.tree)
} }
}) })
} }
@ -573,7 +573,7 @@ func TestCompiler_CleanStack(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
c := NewCompiler([]rune(tc.tree.String())) c := NewCompiler([]rune(tc.tree.String()))
err := c.Compile(tc.tree) err := c.compile(tc.tree)
if err != nil { if err != nil {
t.Fatalf("Compiling failed: %v", err) t.Fatalf("Compiling failed: %v", err)
} }

View file

@ -32,7 +32,6 @@ const (
FunctionNodeType FunctionNodeType
ReturnNodeType ReturnNodeType
AccessNodeType AccessNodeType
ImportNodeType
BreakpointNodeType BreakpointNodeType
) )
@ -70,8 +69,6 @@ func (n NodeType) String() string {
return "Access" return "Access"
case BreakpointNodeType: case BreakpointNodeType:
return "Breakpoint" return "Breakpoint"
case ImportNodeType:
return "Import"
case UnaryNodeType: case UnaryNodeType:
return "Unary" return "Unary"
} }
@ -405,25 +402,6 @@ func (n BlockNode) Bounds() (Pos, Pos) {
return n.start, n.end return n.start, n.end
} }
type ImportNode struct {
path string
start Pos
end Pos
}
func (n ImportNode) Type() NodeType {
return ImportNodeType
}
func (n ImportNode) String() string {
return fmt.Sprintf("import %s", n.path)
}
func (n ImportNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ConditionalNode conditionals (if statements) // ConditionalNode conditionals (if statements)
type ConditionalNode struct { type ConditionalNode struct {
condition Node condition Node

View file

@ -8,9 +8,15 @@ import (
"strings" "strings"
) )
type FormatedError interface {
Error() string
Format() string
}
type ParsingError struct { type ParsingError struct {
Description string Description string
Causer *Token Causer *Token
Source string
} }
func (p ParsingError) Error() string { func (p ParsingError) Error() string {
@ -18,7 +24,8 @@ func (p ParsingError) Error() string {
} }
// Format Print a rich and informative error // Format Print a rich and informative error
func (p ParsingError) Format(src []rune) string { func (p ParsingError) Format() string {
src := []rune(p.Source)
builder := strings.Builder{} builder := strings.Builder{}
lineNumber := 1 lineNumber := 1
@ -62,20 +69,44 @@ func (p ParsingError) Format(src []rune) string {
} }
type Parser struct { type Parser struct {
source string
tokens []Token tokens []Token
prev *Token prev *Token
curr *Token curr *Token
pos Pos pos Pos
} }
func NewParser(tokens []Token) *Parser { func NewParser(source string, tokens []Token) *Parser {
return &Parser{ return &Parser{
source: source,
tokens: tokens, tokens: tokens,
pos: 0, pos: 0,
} }
} }
func (p *Parser) Parse() (Node, error) { type Program struct {
Imports []string
Block *BlockNode
}
func (p *Program) String() string {
builder := strings.Builder{}
builder.WriteString("=== Imports ===\n")
for _, i := range p.Imports {
builder.WriteString(i)
builder.WriteString("\n")
}
builder.WriteString("===============\n")
builder.WriteString(p.Block.String())
return builder.String()
}
func (p *Parser) Parse() (*Program, error) {
imports := make([]string, 0)
// top level statements // top level statements
statements := make([]Node, 0) statements := make([]Node, 0)
@ -83,6 +114,14 @@ func (p *Parser) Parse() (Node, error) {
p.advance() p.advance()
for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF { for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF {
if p.accept(TokenImport) {
if err := p.expect(TokenString); err != nil {
return nil, err
}
imports = append(imports, p.prev.Lexeme[1:len(p.prev.Lexeme)-1])
}
b, err := p.block(true) b, err := p.block(true)
if err != nil { if err != nil {
@ -92,8 +131,13 @@ func (p *Parser) Parse() (Node, error) {
statements = append(statements, b) statements = append(statements, b)
} }
return &BlockNode{ return &Program{
statements: statements, imports,
&BlockNode{
statements,
0,
p.curr.Start + p.curr.Length,
},
}, nil }, nil
} }
@ -141,6 +185,7 @@ func (p *Parser) error(error string, causer *Token) error {
return ParsingError{ return ParsingError{
Description: error, Description: error,
Causer: causer, Causer: causer,
Source: p.source,
} }
} }
@ -643,22 +688,6 @@ func (p *Parser) statement() (Node, error) {
return p.condition() return p.condition()
} }
case TokenImport:
p.advance()
start := p.prev.Start
if err := p.expect(TokenString); err != nil {
return nil, err
}
path := p.prev.Lexeme[1 : len(p.prev.Lexeme)-1]
return &ImportNode{
path,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenFunc: case TokenFunc:
p.advance() p.advance()
@ -867,13 +896,19 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
var s TypeSignature var s TypeSignature
if p.accept(TokenFunc) { if p.accept(TokenFunc) {
if err := p.expect(TokenCloseParenthesis); err != nil { if err := p.expect(TokenOpenParenthesis); err != nil {
return nil, err return nil, err
} }
var in []TypeSignature var in []TypeSignature
for !p.accept(TokenCloseParenthesis) && (len(in) == 0 || p.accept(TokenComma)) { for !p.accept(TokenCloseParenthesis) {
if len(in) > 0 {
if err := p.expect(TokenComma); err != nil {
return nil, err
}
}
sig, err := p.parseSignature() sig, err := p.parseSignature()
if err != nil { if err != nil {
@ -883,10 +918,6 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
in = append(in, sig) in = append(in, sig)
} }
if err := p.expect(TokenCloseParenthesis); err != nil {
return nil, err
}
out, err := p.parseSignature() out, err := p.parseSignature()
if err != nil { if err != nil {
return nil, err return nil, err
@ -896,40 +927,43 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
in, in,
out, out,
} }
} } else {
if err := p.expect(TokenName); err != nil {
if err := p.expect(TokenName); err != nil {
return nil, err
}
name := (*p.prev).Lexeme
switch name {
case "string":
s = &StringSignature{}
case "number":
s = &NumberSignature{}
case "boolean":
s = &BooleanSignature{}
case "list":
if err := p.expect(TokenOpenBracket); err != nil {
return nil, err return nil, err
} }
name := (*p.prev).Lexeme
contents, err := p.parseSignature() switch name {
if err != nil { case "string":
return nil, err s = &StringSignature{}
case "number":
s = &NumberSignature{}
case "boolean":
s = &BooleanSignature{}
case "list":
if err := p.expect(TokenOpenBracket); err != nil {
return nil, err
}
contents, err := p.parseSignature()
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseBracket); err != nil {
return nil, err
}
s = &ListSignature{
contents,
}
case "any":
s = &AnySignature{}
default:
return nil, p.error("unsupported type: "+name, p.prev)
} }
if err := p.expect(TokenCloseBracket); err != nil {
return nil, err
}
s = &ListSignature{
contents,
}
case "any":
s = &AnySignature{}
} }
if p.accept(TokenPipe) { if p.accept(TokenPipe) {
@ -943,9 +977,5 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
}, nil }, nil
} }
if s == nil {
return nil, p.error("unsupported type: "+name, p.prev)
}
return s, nil return s, nil
} }

View file

@ -68,15 +68,6 @@ func GoToValue(gov interface{}) Value {
return &StringValue{ return &StringValue{
v, v,
} }
case []interface{}:
values := make([]Value, len(v))
for i, value := range v {
values[i] = GoToValue(value)
}
return &ListValue{
values,
}
case map[string]interface{}: case map[string]interface{}:
values := map[string]Value{} values := map[string]Value{}
for key, value := range v { for key, value := range v {
@ -86,9 +77,17 @@ func GoToValue(gov interface{}) Value {
return &ObjectValue{ return &ObjectValue{
values, values,
} }
case Value:
return v
default:
if reflect.TypeOf(v).Kind() == reflect.Slice {
return &ListValue{
v.([]Value),
}
}
} }
panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov).Name())) panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov)))
} }
type Value interface { type Value interface {
@ -319,24 +318,26 @@ var StringPrototype = map[string]*BuiltinFunctionValue{
"split", "split",
&FunctionSignature{ &FunctionSignature{
[]TypeSignature{&StringSignature{}}, []TypeSignature{&StringSignature{}},
&NilSignature{}, &ListSignature{
&StringSignature{},
},
}, },
func(vm *VM, this Value, v []Value) (Value, error) { func(vm *VM, this Value, v []Value) (Value, error) {
str := this.(*StringValue).String() str := this.(*StringValue).String()
sep := v[0].(*StringValue).String() sep := v[0].(*StringValue).String()
var out []string var out []Value
tmp := strings.Builder{} tmp := strings.Builder{}
for i := 0; i < len(str)-len(sep); i++ { for i := 0; i < len(str)-len(sep); i++ {
tmp.WriteRune([]rune(str)[i]) tmp.WriteRune([]rune(str)[i])
if str[i:i+len(sep)] == sep { if str[i:i+len(sep)] == sep {
out = append(out, tmp.String()) out = append(out, &StringValue{tmp.String()})
tmp.Reset() tmp.Reset()
} }
} }
return GoToValue(out), nil return &ListValue{out}, nil
}, },
nil, nil,
true, true,
@ -460,47 +461,6 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
nil, nil,
false, false,
}, },
"map": {
"map",
&FunctionSignature{
[]TypeSignature{
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
},
&AnySignature{},
},
},
&ListSignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
list := value.(*ListValue)
v := m[0]
var f Value
switch a := v.(type) {
case *FunctionValue, *BuiltinFunctionValue:
f = a
default:
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,
false,
},
"reduce": { "reduce": {
"reduce", "reduce",
&FunctionSignature{ &FunctionSignature{

View file

@ -102,6 +102,8 @@ const (
// items to include minus one. (value of 0 => 1 item, value of 1 => 2 items, etc.) The order is reversed compared // items to include minus one. (value of 0 => 1 item, value of 1 => 2 items, etc.) The order is reversed compared
// to on the stack; the top value on the stack is the last in the list. // to on the stack; the top value on the stack is the last in the list.
InstructionFormList InstructionFormList
// InstructionConcatLists concatenate lists, producing a new list with the values of both lists. Pops two lists.
InstructionConcatLists
// InstructionBreakpoint for debugging purposes // InstructionBreakpoint for debugging purposes
InstructionBreakpoint InstructionBreakpoint
@ -187,6 +189,8 @@ func (b Bytecode) String() string {
return "APPEND" return "APPEND"
case InstructionAccessProperty: case InstructionAccessProperty:
return "ACCESS_PROPERTY" return "ACCESS_PROPERTY"
case InstructionConcatLists:
return "CONCAT_LISTS"
} }
return "UNDEFINED" return "UNDEFINED"
} }
@ -377,6 +381,21 @@ var DefaultGlobals = map[string]Value{
nil, nil,
true, true,
}, },
"byte": &BuiltinFunctionValue{
"char",
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
s := args[0].(*StringValue).Text
b := []byte(s)[0]
return &NumberValue{float64(b)}, nil
},
nil,
true,
},
"assertEq": &BuiltinFunctionValue{ "assertEq": &BuiltinFunctionValue{
"assertEq", "assertEq",
&FunctionSignature{ &FunctionSignature{
@ -710,6 +729,14 @@ func (vm *VM) Next() bool {
list.Items = append(list.Items, value) list.Items = append(list.Items, value)
vm.stack.Push(list) vm.stack.Push(list)
case InstructionConcatLists:
r := vm.stack.Pop().(*ListValue)
l := vm.stack.Pop().(*ListValue)
vm.stack.Push(&ListValue{
append(l.Items, r.Items...),
})
case InstructionDescend: case InstructionDescend:
vm.descend() vm.descend()

View file

@ -560,6 +560,37 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{5}, &NumberValue{5},
}, },
}, },
"list_concat": {
NewChunk(
[]Bytecode{
InstructionConstant, 0,
InstructionConstant, 1,
InstructionConcatLists,
},
[]Value{
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
&ListValue{
[]Value{
&NumberValue{3},
},
},
},
),
[]Value{
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
},
},
} }
} }

12
lib/list.ang Normal file
View file

@ -0,0 +1,12 @@
func map(list: list[any], f: func(any)any) list[any] {
out := []
i := 0
while i < list.length() {
out.append(f(list.at(i)))
i = i + 1
}
return out
}

View file

@ -1,4 +1,4 @@
#!/bin/zsh #!/bin/bash
echo '=== Building CLI ===' echo '=== Building CLI ==='
cd cli || exit 1 cd cli || exit 1

View file

@ -22,3 +22,6 @@ while x <= 100 {
x = x + 1 x = x + 1
} }
assertEq([1, 2] + [3], [1, 2, 3])
assertEq(["Eny", "meanie"] + ["minie", "moe"], ["Eny", "meanie", "minie", "moe"])

View file

@ -19,8 +19,8 @@ while n < fibonacci_numbers.length() {
n = n + 1 n = n + 1
} }
# return to start of line # return to start of line (with carriage return \r)
print(format("%[%D", [char(0x1B), n])) print(char(0x0D))
x := 0 x := 0
while x < fibonacci_numbers.length() { while x < fibonacci_numbers.length() {

View file

@ -13,32 +13,18 @@ type JsResolver struct {
jsResolver js.Value jsResolver js.Value
} }
func (r *JsResolver) Resolve(name string) (core.Node, error) { func (r *JsResolver) Resolve(name string) (string, error) {
jsv := r.jsResolver.Invoke(name) jsv := r.jsResolver.Invoke(name)
if jsv.Type() == js.TypeUndefined { if jsv.Type() == js.TypeUndefined {
return nil, errors.New("cannot find import with name " + name) return "", errors.New("cannot find import with name " + name)
} }
if jsv.Type() != js.TypeString { if jsv.Type() != js.TypeString {
return nil, errors.New("invalid value for source: " + jsv.String()) return "", errors.New("invalid value for source: " + jsv.String())
} }
source := jsv.String() return jsv.String(), nil
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{} {
@ -68,14 +54,14 @@ func run(_ js.Value, args []js.Value) interface{} {
log.Printf("got tokens: %v", tokens) log.Printf("got tokens: %v", tokens)
parser := core.NewParser(tokens) parser := core.NewParser(source, tokens)
tree, err := parser.Parse() tree, err := parser.Parse()
if err != nil { if err != nil {
var e core.ParsingError var e core.FormatedError
if errors.As(err, &e) { if errors.As(err, &e) {
errorHandler.Invoke(e.Format([]rune(source))) errorHandler.Invoke(e.Format())
return nil return nil
} }
errorHandler.Invoke(err.Error()) errorHandler.Invoke(err.Error())
@ -86,7 +72,7 @@ func run(_ js.Value, args []js.Value) interface{} {
compiler := core.NewCompiler([]rune(source)) compiler := core.NewCompiler([]rune(source))
log.Printf("Set imports resolver", tree.String()) log.Println("Set imports resolver")
compiler.SetImportsResolver(&JsResolver{ compiler.SetImportsResolver(&JsResolver{
resolver, resolver,
@ -118,8 +104,8 @@ func run(_ js.Value, args []js.Value) interface{} {
vm.SetGlobal("write", &core.BuiltinFunctionValue{ vm.SetGlobal("write", &core.BuiltinFunctionValue{
Name: "write", Name: "write",
Signature: &core.FunctionSignature{ Signature: &core.FunctionSignature{
[]core.TypeSignature{&core.StringSignature{}}, In: []core.TypeSignature{&core.StringSignature{}},
&core.NilSignature{}, Out: &core.NilSignature{},
}, },
F: func(vm *core.VM, this core.Value, args []core.Value) (core.Value, error) { F: func(vm *core.VM, this core.Value, args []core.Value) (core.Value, error) {
s := args[0].String() s := args[0].String()