main #1

Merged
neemek merged 10 commits from main into types 2025-09-16 17:41:16 +00:00
42 changed files with 3635 additions and 904 deletions
Showing only changes of commit f53ab30dbe - Show all commits

40
bad.ang Normal file
View file

@ -0,0 +1,40 @@
import "lib/math.ang"
primes := [2]
func is_prime(x: number) boolean {
i := 0
while i < primes.length() && primes.at(i)*primes.at(i) < x {
if mod(x, primes.at(i)) == 0 {
return false
}
i = i + 1
}
return true
}
n := 1
max := 100000
while n < max {
n = n + 2
if is_prime(n) {
primes.append(n)
# Update counter
print(char(0x0D))
print(str(n))
print("/")
print(str(max))
print(char(0x09))
print(str(roundd(n/max*100, 2)))
print("%")
print(char(0x09))
print(str(primes.length()))
print(" primes")
}
}
write(str(primes))

15
chars.ang Normal file
View file

@ -0,0 +1,15 @@
MAX_WIDTH := 16
print(" ")
w := 1
n := 0x21
while n < 0xA0 {
print(char(n))
n = n + 1
w = w + 1
if w >= MAX_WIDTH {
write("")
w = 0
}
}

View file

@ -1,10 +1,13 @@
package main package main
import ( import (
"bufio"
"errors"
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
"log" "log"
"neemek.com/anglais/core" "neemek.com/anglais/core"
"os" "os"
"path"
"path/filepath" "path/filepath"
) )
@ -13,6 +16,7 @@ type Context struct {
} }
type RunCmd struct { type RunCmd struct {
IgnoreWarnings bool `name:"ignore-warnings" help:"Ignore warning messages"`
Bytecode bool `name:"bytecode" short:"c" help:"Run file as if it's bytecode"` Bytecode bool `name:"bytecode" short:"c" help:"Run file as if it's bytecode"`
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"`
} }
@ -22,45 +26,34 @@ 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 { if err != nil {
return nil, err return "", err
} }
str := string(f) return string(f), nil
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 (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, fpath string, ignoreWarnings bool) (*core.Chunk, error) {
if ctx.Debug { if ctx.Debug {
log.Println("Reading file") log.Println("Reading file")
} }
f, err := os.ReadFile(cmd.File) f, err := os.ReadFile(fpath)
if err != nil { if err != nil {
return err return nil, err
} }
var chunk *core.Chunk
if !cmd.Bytecode {
src := string(f) src := string(f)
if ctx.Debug { if ctx.Debug {
@ -74,41 +67,50 @@ func (cmd *RunCmd) Run(ctx *Context) error {
tokens, err := l.Tokenize() tokens, err := l.Tokenize()
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
if len(tokens) <= 1 { if len(tokens) <= 1 {
log.Fatal("Empty file") return nil, errors.New("empty file")
} }
if ctx.Debug { if ctx.Debug {
log.Printf("Lexed %d tokens", len(tokens)) log.Printf("Lexed %d tokens", len(tokens))
} }
p := core.NewParser(tokens) p := core.NewParser(src, tokens)
if ctx.Debug { if ctx.Debug {
log.Println("Initialized parser") log.Println("Initialized parser")
} }
tree, err := p.Parse() pathat, err := filepath.Abs(fpath)
if err != nil {
return nil, err
}
tree, err := p.Parse(pathat)
if ctx.Debug {
log.Printf("Parsed tree, meaning:\n%s", tree)
}
// if there were parsing errors, print them out // if there were parsing errors, print them out
if err != nil { if err != nil {
print(err.(*core.ParsingError).Format([]rune(src))) print(err.(core.ParsingError).Format())
log.Fatal("Parsing had errors") log.Fatal("Parsing had errors")
} }
if ctx.Debug { if ctx.Debug {
log.Println("Initialized compiler") log.Println("Initialized compiler")
} }
c := core.NewCompiler() c := core.NewCompiler([]rune(src))
if ctx.Debug { if ctx.Debug {
log.Println("Setting imports resolver") log.Println("Setting imports resolver")
} }
dir, _ := filepath.Split(cmd.File) dir, _ := path.Split(fpath)
c.SetImportsResolver(&WorkingDirectoryResolver{ c.SetImportsResolver(&WorkingDirectoryResolver{
dir, dir,
}) })
@ -117,12 +119,44 @@ func (cmd *RunCmd) Run(ctx *Context) error {
log.Println("Compiling parse tree") log.Println("Compiling parse tree")
} }
err = c.Compile(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 {
var chunk *core.Chunk
if !cmd.Bytecode {
c, err := makeChunk(ctx, cmd.File, cmd.IgnoreWarnings)
if err != nil {
return err
}
chunk = c
} else {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(cmd.File)
if err != nil { if err != nil {
return err return err
} }
chunk = c.Chunk
} else {
if ctx.Debug { if ctx.Debug {
log.Println("Registering GOB types") log.Println("Registering GOB types")
} }
@ -159,70 +193,11 @@ 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 {
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 {
print(err.(*core.ParsingError).Format([]rune(src)))
}
if ctx.Debug {
log.Println("Initialized compiler")
}
c := core.NewCompiler()
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 { if err != nil {
return err return err
} }
@ -237,7 +212,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")
@ -252,11 +227,59 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
return nil return nil
} }
type ReplCmd struct {
}
func (cmd *ReplCmd) Run(ctx *Context) error {
c := core.NewCompiler([]rune(""))
vm := core.NewVM(core.NewChunk([]core.Bytecode{}, []core.Value{}), 256, 256)
reader := bufio.NewReader(os.Stdin)
for {
print("> ")
src, err := reader.ReadString('\n')
if err != nil {
return err
}
l := core.NewLexer(src)
tokens, err := l.Tokenize()
if err != nil {
log.Println(err)
continue
}
p := core.NewParser(src, tokens)
prog, err := p.Parse("REPL")
if err != nil {
var e core.FormatedError
if errors.As(err, &e) {
log.Print(e.Format())
}
continue
}
c.SetSource(src)
if err = c.Compile(prog); err != nil {
var e core.FormatedError
if errors.As(err, &e) {
log.Print(e.Format())
}
continue
}
vm.SetChunk(c.Chunk)
for vm.Next() {
}
}
}
var cli struct { var cli struct {
Debug bool `short:"D" name:"debug" help:"Enable debug mode."` Debug bool `short:"D" name:"debug" help:"Enable debug mode."`
Run RunCmd `cmd:"" name:"run" help:"Run program."` Run RunCmd `cmd:"" name:"run" help:"Run program."`
CompileCmd CompileCmd `cmd:"" name:"compile" help:"Compile program to bytecode."` Compile CompileCmd `cmd:"" name:"compile" help:"Compile program to bytecode."`
Repl ReplCmd `cmd:"" name:"repl" help:"Start a REPL loop."`
} }
func main() { func main() {

24
codegen.ang Normal file
View file

@ -0,0 +1,24 @@
passphrase := "Hello world!".split("")
start := [0, 0, 0]
modulus := 10
base := byte("!")
i := 0
n := 0
while n < passphrase.length() {
b := byte(passphrase.at(n))
v = start.at(i) + b - base
while v >= modulus {
v = v - modulus
}
start.put(i, v)
if i >= 3 {
i = 0
}
n = n + 1
}

View file

@ -22,13 +22,22 @@ func GetAllTestCases() map[string]AllTestCase {
}, },
}, },
"func": { "func": {
"func sum(a, b) {\n\treturn a + b\n}\nsum(1, 2)", "func sum(a: number, b: number) number {\n\treturn a + b\n}\n_ = sum(1, 2)",
[]Value{ []Value{
&VariableValue{ &VariableValue{
"sum", "sum",
&FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: &Chunk{ Chunk: &Chunk{
Bytecode: []Bytecode{ Bytecode: []Bytecode{
InstructionDescend, InstructionDescend,
@ -45,6 +54,63 @@ func GetAllTestCases() map[string]AllTestCase {
}, },
}, },
}, },
"list": {
"a := [1, 2]",
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
0,
},
},
},
"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,
},
},
},
} }
} }
@ -64,18 +130,18 @@ func TestAll(t *testing.T) {
} }
t.Log("Initializing parser") t.Log("Initializing parser")
p := NewParser(tokens) p := NewParser(tc.src, tokens)
t.Log("Parsing tokens") t.Log("Parsing tokens")
tree, err := p.Parse() tree, err := p.Parse(tc.src)
if err != nil { if err != nil {
print(err.(*ParsingError).Format([]rune(tc.src))) print(err.(ParsingError).Format())
t.Fatalf("parser had an error") t.Fatalf("parser had an error")
} }
t.Log("Initializing compiler") t.Log("Initializing compiler")
c := NewCompiler() c := NewCompiler([]rune(tc.src))
t.Log("Compiling parse tree") t.Log("Compiling parse tree")
err = c.Compile(tree) err = c.Compile(tree)
@ -107,10 +173,10 @@ func BenchmarkAll(b *testing.B) {
l := NewLexer(tc.src) l := NewLexer(tc.src)
tokens, _ := l.Tokenize() tokens, _ := l.Tokenize()
p := NewParser(tokens) p := NewParser(tc.src, tokens)
tree, _ := p.Parse() tree, _ := p.Parse(tc.src)
c := NewCompiler() c := NewCompiler([]rune(tc.src))
_ = c.Compile(tree) _ = c.Compile(tree)
vm := NewVM(c.Chunk, 256, 256) vm := NewVM(c.Chunk, 256, 256)

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ import (
) )
func TestNewCompiler(t *testing.T) { func TestNewCompiler(t *testing.T) {
c := NewCompiler() c := NewCompiler([]rune{})
if c == nil { if c == nil {
t.Fatal("NewCompiler returned nil") t.Fatal("NewCompiler returned nil")
@ -23,39 +23,63 @@ func TestNewCompiler(t *testing.T) {
func BenchmarkNewCompiler(b *testing.B) { func BenchmarkNewCompiler(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = NewCompiler() _ = NewCompiler([]rune{})
} }
} }
type CompileTestData struct { type CompileTestData struct {
tree Node program *Program
expectedStack []Value expectedStack []Value
} }
func GetCompileTestData() map[string]CompileTestData { func GetCompileTestData() map[string]CompileTestData {
return map[string]CompileTestData{ return map[string]CompileTestData{
"constant_string": { "constant_string": {
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&StringNode{ &StringNode{
"Hello world!", "Hello world!",
"\"Hello world!\"", "\"Hello world!\"",
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
"",
}, },
[]Value{ []Value{
&VariableValue{
"a",
&StringValue{"Hello world!"}, &StringValue{"Hello world!"},
0,
},
}, },
}, },
"conditional_false": { "conditional_false": {
&Program{
[]string{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", "a",
&NumberNode{ &NumberNode{
0, 0,
0, 0,
}, },
true, true,
0, 0,
}, },
&ConditionalNode{ &ConditionalNode{
&BooleanNode{ &BooleanNode{
false, false,
0, 0,
}, },
&BlockNode{ &BlockNode{
[]Node{ []Node{
@ -63,14 +87,21 @@ func GetCompileTestData() map[string]CompileTestData {
"a", "a",
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
nil, nil,
0, 0,
}, },
}, },
0, 0,
},
"",
}, },
[]Value{ []Value{
&VariableValue{ &VariableValue{
@ -81,18 +112,23 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
}, },
"conditional_true": { "conditional_true": {
&Program{
[]string{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", "a",
&NumberNode{ &NumberNode{
0, 0,
0, 0,
}, },
true, true,
0, 0,
}, },
&ConditionalNode{ &ConditionalNode{
&BooleanNode{ &BooleanNode{
true, true,
0, 0,
}, },
&BlockNode{ &BlockNode{
[]Node{ []Node{
@ -100,14 +136,21 @@ func GetCompileTestData() map[string]CompileTestData {
"a", "a",
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
nil, nil,
0, 0,
}, },
}, },
0, 0,
},
"",
}, },
[]Value{ []Value{
&VariableValue{ &VariableValue{
@ -118,18 +161,23 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
}, },
"conditional_else_false": { "conditional_else_false": {
&Program{
[]string{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", "a",
&NumberNode{ &NumberNode{
0, 0,
0, 0,
}, },
true, true,
0, 0,
}, },
&ConditionalNode{ &ConditionalNode{
&BooleanNode{ &BooleanNode{
false, false,
0, 0,
}, },
&BlockNode{ &BlockNode{
[]Node{ []Node{
@ -137,10 +185,13 @@ func GetCompileTestData() map[string]CompileTestData {
"a", "a",
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
&BlockNode{ &BlockNode{
[]Node{ []Node{
@ -148,13 +199,20 @@ func GetCompileTestData() map[string]CompileTestData {
"a", "a",
&NumberNode{ &NumberNode{
2, 2,
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
},
0, 0,
}, },
}, },
0, 0,
}, },
"",
}, },
[]Value{ []Value{
&VariableValue{ &VariableValue{
@ -165,18 +223,23 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
}, },
"conditional_else_true": { "conditional_else_true": {
&Program{
[]string{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", "a",
&NumberNode{ &NumberNode{
0, 0,
0, 0,
}, },
true, true,
0, 0,
}, },
&ConditionalNode{ &ConditionalNode{
&BooleanNode{ &BooleanNode{
true, true,
0, 0,
}, },
&BlockNode{ &BlockNode{
[]Node{ []Node{
@ -184,10 +247,13 @@ func GetCompileTestData() map[string]CompileTestData {
"a", "a",
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
&BlockNode{ &BlockNode{
[]Node{ []Node{
@ -195,13 +261,20 @@ func GetCompileTestData() map[string]CompileTestData {
"a", "a",
&NumberNode{ &NumberNode{
2, 2,
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
},
0, 0,
}, },
}, },
0, 0,
}, },
"",
}, },
[]Value{ []Value{
&VariableValue{ &VariableValue{
@ -212,41 +285,89 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
}, },
"addition": { "addition": {
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
&NumberNode{ &NumberNode{
2, 2,
0, 0,
}, },
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
"",
}, },
[]Value{ []Value{
&VariableValue{
"a",
&NumberValue{3}, &NumberValue{3},
0,
}, },
}, },
"sum_function": {&BlockNode{ },
"sum_function": {
&Program{
[]string{},
&BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"sum", "sum",
&FunctionNode{ &FunctionNode{
"sum", "sum",
[]string{"a", "b"}, []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&ReferenceNode{"a"}, &ReferenceNode{
&ReferenceNode{"b"}, "a",
}, 0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
0, 0,
}, },
}, },
0, 0,
}, },
0, 0,
}, },
true, true,
0, 0,
}, },
}, },
0, 0,
},
"",
}, },
[]Value{ []Value{
&VariableValue{ &VariableValue{
@ -254,7 +375,17 @@ func GetCompileTestData() map[string]CompileTestData {
&FunctionValue{ &FunctionValue{
"sum", "sum",
[]string{"a", "b"}, []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
NewChunk( NewChunk(
[]Bytecode{ []Bytecode{
InstructionDescend, InstructionDescend,
@ -275,43 +406,63 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
}, },
"remove_func_vars": { "remove_func_vars": {
&Program{
[]string{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", "a",
&FunctionNode{ &FunctionNode{
"a", "a",
[]string{}, []FunctionParameter{},
&NumberSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", "b",
&NumberNode{1}, &NumberNode{
1,
0, 0,
},
true, true,
0, 0,
}, },
&ReturnNode{ &ReturnNode{
&ReferenceNode{"b"}, &ReferenceNode{
"b",
0, 0,
},
0, 0,
}, },
}, },
0, 0,
}, },
0, 0,
}, },
true, true,
0, 0,
}, },
&CallNode{ &CallNode{
&ReferenceNode{ &ReferenceNode{
"a", "a",
0, 0,
}, },
[]Node{}, []Node{},
false, false,
0, 0,
}, },
}, },
0, 0,
},
"",
}, },
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
&FunctionValue{ &FunctionValue{
"a", "a",
[]string{}, []FunctionParameter{},
&NumberSignature{},
NewChunk( NewChunk(
[]Bytecode{ []Bytecode{
InstructionDescend, InstructionDescend,
@ -331,6 +482,58 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
}, },
}, },
"two_lists": {
program: &Program{
[]string{},
&BlockNode{
statements: []Node{
&AssignNode{
name: "a",
value: &ListNode{
items: []Node{
&NumberNode{value: 1},
&NumberNode{value: 2},
},
},
declare: true,
},
&AssignNode{
name: "b",
value: &ListNode{
items: []Node{
&StringNode{value: "Hello"},
&StringNode{value: "world"},
},
},
declare: true,
},
},
},
"",
},
expectedStack: []Value{
&VariableValue{
name: "a",
value: &ListValue{
Items: []Value{
&NumberValue{1},
&NumberValue{2},
},
},
scope: 0,
},
&VariableValue{
name: "b",
value: &ListValue{
Items: []Value{
&StringValue{"Hello"},
&StringValue{"world"},
},
},
scope: 0,
},
},
},
} }
} }
@ -343,7 +546,7 @@ func printChunk(t *testing.T, name string, chunk *Chunk) {
t.Logf("=-= constants =-=") t.Logf("=-= constants =-=")
for i, ct := range chunk.Constants { for i, ct := range chunk.Constants {
t.Logf("c=%d \t%s", i, ct) t.Logf("c=%d \t%s", i, ct.DebugString())
f, ok := ct.(*FunctionValue) f, ok := ct.(*FunctionValue)
if ok { if ok {
@ -360,10 +563,10 @@ func TestCompile(t *testing.T) {
for name, testCase := range data { for name, testCase := range data {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Log("Initializing compiler") t.Log("Initializing compiler")
c := NewCompiler() c := NewCompiler([]rune(testCase.program.String()))
t.Log("Compiling node tree") t.Log("Compiling node tree")
err := c.Compile(testCase.tree) err := c.Compile(testCase.program)
if err != nil { if err != nil {
t.Fatalf("Compiling failed: %v", err) t.Fatalf("Compiling failed: %v", err)
} }
@ -389,8 +592,8 @@ func BenchmarkCompile(b *testing.B) {
for name, testCase := range data { for name, testCase := range data {
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() c := NewCompiler([]rune{})
_ = c.Compile(testCase.tree) _ = c.Compile(testCase.program)
} }
}) })
} }
@ -399,7 +602,7 @@ func BenchmarkCompile(b *testing.B) {
func TestCompiler_AddU16(t *testing.T) { func TestCompiler_AddU16(t *testing.T) {
for i := 0; i <= 0xffff; i++ { for i := 0; i <= 0xffff; i++ {
t.Run(fmt.Sprint(i), func(t *testing.T) { t.Run(fmt.Sprint(i), func(t *testing.T) {
c := NewCompiler() c := NewCompiler([]rune{})
c.addU16(uint16(i)) c.addU16(uint16(i))
if c.Chunk.Bytecode[0] != Bytecode(i>>8) { if c.Chunk.Bytecode[0] != Bytecode(i>>8) {
@ -417,24 +620,9 @@ func TestCompiler_CleanStack(t *testing.T) {
cases := GetCompileTestData() cases := GetCompileTestData()
for name, tc := range cases { for name, tc := range cases {
switch tc.tree.Type() {
// skip all expected unclean nodes
case StringNodeType, NumberNodeType, ReferenceNodeType, BooleanNodeType, NilNodeType, BinaryNodeType, ReturnNodeType:
continue
case CallNodeType:
if tc.tree.(*CallNode).keep {
// if we know it should be unclean, skip it
continue
}
// clean statements
default:
}
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
c := NewCompiler() c := NewCompiler([]rune(tc.program.String()))
err := c.Compile(tc.tree) err := c.Compile(tc.program)
if err != nil { if err != nil {
t.Fatalf("Compiling failed: %v", err) t.Fatalf("Compiling failed: %v", err)
} }

View file

@ -29,6 +29,7 @@ const (
TokenSemicolon TokenSemicolon
TokenNumber TokenNumber
TokenHexadecimal
TokenString TokenString
TokenName TokenName
@ -53,6 +54,7 @@ const (
TokenComma TokenComma
TokenDot TokenDot
TokenColon
TokenAssign TokenAssign
TokenDeclare TokenDeclare
@ -64,6 +66,7 @@ const (
TokenLessThanOrEqual TokenLessThanOrEqual
TokenDoubleAmpersand TokenDoubleAmpersand
TokenPipe
TokenDoublePipe TokenDoublePipe
TokenBreakpoint TokenBreakpoint
@ -153,6 +156,12 @@ func (t TokenType) String() string {
return "close bracket" return "close bracket"
case TokenImport: case TokenImport:
return "import" return "import"
case TokenColon:
return "colon"
case TokenPipe:
return "pipe"
case TokenHexadecimal:
return "hexadecimal"
} }
return "UNDEFINED TOKENTYPE STRING CONVERSION" return "UNDEFINED TOKENTYPE STRING CONVERSION"
@ -234,11 +243,11 @@ func (l *Lexer) NextToken() (Token, error) {
case '.': case '.':
return l.makeToken(TokenDot), nil return l.makeToken(TokenDot), nil
case ':': case ':':
if !l.accept('=') { if l.accept('=') {
return l.makeToken(TokenError), errors.New("malformed token (got ':', expected '=' to follow)") return l.makeToken(TokenDeclare), nil
} }
return l.makeToken(TokenDeclare), nil return l.makeToken(TokenColon), nil
case '!': case '!':
if l.accept('=') { if l.accept('=') {
return l.makeToken(TokenBangEquals), nil return l.makeToken(TokenBangEquals), nil
@ -276,7 +285,7 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenDoublePipe), nil return l.makeToken(TokenDoublePipe), nil
} }
return l.makeToken(TokenError), errors.New("malformed token (got '|', expected '|' to follow)") return l.makeToken(TokenPipe), nil
case '"': case '"':
// include ending quote // include ending quote
@ -327,6 +336,19 @@ func (l *Lexer) NextToken() (Token, error) {
default: default:
return l.makeToken(TokenName), nil return l.makeToken(TokenName), nil
} }
} else if c == '0' && l.peek() != '.' {
if l.peek() == 'x' {
l.advance()
// hex
for unicode.In(l.peek(), unicode.Hex_Digit) {
l.advance()
}
return l.makeToken(TokenHexadecimal), nil
}
return l.makeToken(TokenNumber), nil
} else if unicode.IsDigit(c) { } else if unicode.IsDigit(c) {
for unicode.IsDigit(l.peek()) { for unicode.IsDigit(l.peek()) {
l.advance() l.advance()

View file

@ -11,6 +11,8 @@ type NodeType int
type Node interface { type Node interface {
Type() NodeType Type() NodeType
String() string String() string
Bounds() (Pos, Pos)
} }
const ( const (
@ -21,6 +23,7 @@ const (
NilNodeType NilNodeType
ListNodeType ListNodeType
BinaryNodeType BinaryNodeType
UnaryNodeType
BlockNodeType BlockNodeType
ConditionalNodeType ConditionalNodeType
LoopNodeType LoopNodeType
@ -29,7 +32,6 @@ const (
FunctionNodeType FunctionNodeType
ReturnNodeType ReturnNodeType
AccessNodeType AccessNodeType
ImportNodeType
BreakpointNodeType BreakpointNodeType
) )
@ -67,8 +69,8 @@ func (n NodeType) String() string {
return "Access" return "Access"
case BreakpointNodeType: case BreakpointNodeType:
return "Breakpoint" return "Breakpoint"
case ImportNodeType: case UnaryNodeType:
return "Import" return "Unary"
} }
return "Invalid Node Type" return "Invalid Node Type"
} }
@ -76,6 +78,9 @@ func (n NodeType) String() string {
// ReferenceNode a reference to a variable on the stack // ReferenceNode a reference to a variable on the stack
type ReferenceNode struct { type ReferenceNode struct {
name string name string
start Pos
end Pos
} }
func (n ReferenceNode) Type() NodeType { func (n ReferenceNode) Type() NodeType {
@ -86,10 +91,17 @@ func (n ReferenceNode) String() string {
return n.name return n.name
} }
func (n ReferenceNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// StringNode string/text values // StringNode string/text values
type StringNode struct { type StringNode struct {
value string value string
quoted string quoted string
start Pos
end Pos
} }
func (n StringNode) Type() NodeType { func (n StringNode) Type() NodeType {
@ -100,8 +112,15 @@ func (n StringNode) String() string {
return n.quoted return n.quoted
} }
func (n StringNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type NumberNode struct { type NumberNode struct {
value float64 value float64
start Pos
end Pos
} }
func (n NumberNode) Type() NodeType { func (n NumberNode) Type() NodeType {
@ -112,9 +131,17 @@ func (n NumberNode) String() string {
return strconv.FormatFloat(n.value, 'g', -1, NumberSize) return strconv.FormatFloat(n.value, 'g', -1, NumberSize)
} }
func (n NumberNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ListNode a list or sequence of values (items) // ListNode a list or sequence of values (items)
type ListNode struct { type ListNode struct {
items []Node items []Node
content TypeSignature
start Pos
end Pos
} }
func (n ListNode) Type() NodeType { func (n ListNode) Type() NodeType {
@ -125,18 +152,25 @@ func (n ListNode) String() string {
sb := strings.Builder{} sb := strings.Builder{}
sb.WriteString("[") sb.WriteString("[")
for i, item := range n.items { for i, item := range n.items {
sb.WriteString(item.String())
if i > 0 { if i > 0 {
sb.WriteString(", ") sb.WriteString(", ")
} }
sb.WriteString(item.String())
} }
sb.WriteString("]") sb.WriteString("]")
return sb.String() return sb.String()
} }
func (n ListNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type AccessNode struct { type AccessNode struct {
source Node source Node
property string property string
start Pos
end Pos
} }
func (n AccessNode) Type() NodeType { func (n AccessNode) Type() NodeType {
@ -147,6 +181,10 @@ func (n AccessNode) String() string {
return fmt.Sprintf("(%s from %s)", n.property, n.source) return fmt.Sprintf("(%s from %s)", n.property, n.source)
} }
func (n AccessNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type BinaryOperation uint type BinaryOperation uint
func (n BinaryOperation) String() string { func (n BinaryOperation) String() string {
@ -198,11 +236,45 @@ const (
BinaryGreaterEqual BinaryGreaterEqual
) )
func (n BinaryOperation) Symbol() string {
switch n {
case BinaryAddition:
return "+"
case BinarySubtraction:
return "-"
case BinaryMultiplication:
return "*"
case BinaryDivision:
return "/"
case BinaryEquality:
return "=="
case BinaryInequality:
return "!="
case BinaryLess:
return "<"
case BinaryGreater:
return ">"
case BinaryAnd:
return "&&"
case BinaryOr:
return "||"
case BinaryLessEqual:
return "<="
case BinaryGreaterEqual:
return ">="
}
panic("unsupported binary operation to symbol conversion for " + n.String())
}
// BinaryNode All operations which take 2 variables // BinaryNode All operations which take 2 variables
type BinaryNode struct { type BinaryNode struct {
BinaryOperation BinaryOperation
Left Node Left Node
Right Node Right Node
start Pos
end Pos
} }
func (n BinaryNode) Type() NodeType { func (n BinaryNode) Type() NodeType {
@ -213,9 +285,65 @@ func (n BinaryNode) String() string {
return fmt.Sprintf("%s %s %s", n.Left.String(), n.BinaryOperation.String(), n.Right.String()) return fmt.Sprintf("%s %s %s", n.Left.String(), n.BinaryOperation.String(), n.Right.String())
} }
func (n BinaryNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type UnaryOperation int
const (
UnaryNegate UnaryOperation = iota
UnaryNot
)
func (op UnaryOperation) String() string {
switch op {
case UnaryNegate:
return "negate"
case UnaryNot:
return "not"
}
panic("unimplemented unary operation to string conversion")
}
func (op UnaryOperation) Symbol() string {
switch op {
case UnaryNegate:
return "-"
case UnaryNot:
return "!"
}
panic("unimplemented unary operation to symbol conversion")
}
type UnaryNode struct {
UnaryOperation
value Node
start Pos
end Pos
}
func (n UnaryNode) Type() NodeType {
return UnaryNodeType
}
func (n UnaryNode) String() string {
return fmt.Sprintf("%s %s", n.UnaryOperation.String(), n.value.String())
}
func (n UnaryNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// BooleanNode boolean value // BooleanNode boolean value
type BooleanNode struct { type BooleanNode struct {
value bool value bool
start Pos
end Pos
} }
func (n BooleanNode) Type() NodeType { func (n BooleanNode) Type() NodeType {
@ -226,8 +354,15 @@ func (n BooleanNode) String() string {
return strconv.FormatBool(n.value) return strconv.FormatBool(n.value)
} }
func (n BooleanNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// NilNode nil value // NilNode nil value
type NilNode struct{} type NilNode struct {
start Pos
end Pos
}
func (n NilNode) Type() NodeType { func (n NilNode) Type() NodeType {
return NilNodeType return NilNodeType
@ -237,9 +372,16 @@ func (n NilNode) String() string {
return "nil" return "nil"
} }
func (n NilNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// BlockNode block node with statements // BlockNode block node with statements
type BlockNode struct { type BlockNode struct {
statements []Node statements []Node
start Pos
end Pos
} }
func (n BlockNode) Type() NodeType { func (n BlockNode) Type() NodeType {
@ -257,16 +399,8 @@ func (n BlockNode) String() string {
return builder.String() return builder.String()
} }
type ImportNode struct { func (n BlockNode) Bounds() (Pos, Pos) {
path string return n.start, n.end
}
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)
@ -274,6 +408,9 @@ type ConditionalNode struct {
condition Node condition Node
do Node do Node
otherwise Node otherwise Node
start Pos
end Pos
} }
func (n ConditionalNode) Type() NodeType { func (n ConditionalNode) Type() NodeType {
@ -281,13 +418,24 @@ func (n ConditionalNode) Type() NodeType {
} }
func (n ConditionalNode) String() string { func (n ConditionalNode) String() string {
if n.otherwise == nil {
return fmt.Sprintf("if %s then %s", n.condition.String(), n.do.String())
}
return fmt.Sprintf("if %s then %s otheriwise %s", n.condition.String(), n.do.String(), n.otherwise.String()) return fmt.Sprintf("if %s then %s otheriwise %s", n.condition.String(), n.do.String(), n.otherwise.String())
} }
func (n ConditionalNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// LoopNode Loops (for/while) // LoopNode Loops (for/while)
type LoopNode struct { type LoopNode struct {
condition Node condition Node
do Node do Node
start Pos
end Pos
} }
func (n LoopNode) Type() NodeType { func (n LoopNode) Type() NodeType {
@ -298,11 +446,18 @@ func (n LoopNode) String() string {
return fmt.Sprintf("while %s loop %s", n.condition.String(), n.do.String()) return fmt.Sprintf("while %s loop %s", n.condition.String(), n.do.String())
} }
func (n LoopNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// AssignNode assignment // AssignNode assignment
type AssignNode struct { type AssignNode struct {
name string name string
value Node value Node
declare bool declare bool
start Pos
end Pos
} }
func (n AssignNode) Type() NodeType { func (n AssignNode) Type() NodeType {
@ -313,11 +468,18 @@ func (n AssignNode) String() string {
return fmt.Sprintf("set %s to %s", n.name, n.value) return fmt.Sprintf("set %s to %s", n.name, n.value)
} }
func (n AssignNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// CallNode function call // CallNode function call
type CallNode struct { type CallNode struct {
source Node source Node
args []Node args []Node
keep bool keep bool
start Pos
end Pos
} }
func (n CallNode) Type() NodeType { func (n CallNode) Type() NodeType {
@ -328,11 +490,24 @@ func (n CallNode) String() string {
return fmt.Sprintf("call %s with args (%s)", n.source.String(), n.args) return fmt.Sprintf("call %s with args (%s)", n.source.String(), n.args)
} }
func (n CallNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// FunctionNode definition of function // FunctionNode definition of function
type FunctionNode struct { type FunctionNode struct {
name string name string
params []string parameters []FunctionParameter
yield TypeSignature
logic Node logic Node
start Pos
end Pos
}
type FunctionParameter struct {
Name string
Signature TypeSignature
} }
func (n FunctionNode) Type() NodeType { func (n FunctionNode) Type() NodeType {
@ -343,9 +518,16 @@ func (n FunctionNode) String() string {
return fmt.Sprintf("definition of %s, do %s", n.name, n.logic.String()) return fmt.Sprintf("definition of %s, do %s", n.name, n.logic.String())
} }
func (n FunctionNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ReturnNode return a value out of this context // ReturnNode return a value out of this context
type ReturnNode struct { type ReturnNode struct {
value Node value Node
start Pos
end Pos
} }
func (n ReturnNode) Type() NodeType { func (n ReturnNode) Type() NodeType {
@ -356,7 +538,14 @@ func (n ReturnNode) String() string {
return fmt.Sprintf("return %s", n.value) return fmt.Sprintf("return %s", n.value)
} }
type BreakpointNode struct{} func (n ReturnNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type BreakpointNode struct {
start Pos
end Pos
}
func (n BreakpointNode) Type() NodeType { func (n BreakpointNode) Type() NodeType {
return BreakpointNodeType return BreakpointNodeType
@ -365,3 +554,7 @@ func (n BreakpointNode) Type() NodeType {
func (n BreakpointNode) String() string { func (n BreakpointNode) String() string {
return "breakpoint" return "breakpoint"
} }
func (n BreakpointNode) Bounds() (Pos, Pos) {
return n.start, n.end
}

View file

@ -8,17 +8,24 @@ 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 {
return p.Description return p.Description
} }
// 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
@ -38,13 +45,17 @@ func (p *ParsingError) Format(src []rune) string {
} }
} }
builder.WriteString(" \t v ") descriptor := fmt.Sprintf("%d:%d", lineNumber, int(p.Causer.Start)-lineBeginning+1)
builder.WriteString(p.Description) builder.WriteString(p.Description)
builder.WriteRune('\n') builder.WriteRune('\n')
builder.WriteString(fmt.Sprintf(" %d:%d\t | %s", lineNumber, int(p.Causer.Start)-lineBeginning+1, string(src[lineBeginning:lineEnd]))) builder.WriteString(descriptor)
builder.WriteString(" | ")
builder.WriteString(string(src[lineBeginning:lineEnd]))
builder.WriteString("\n\t ^") builder.WriteString("\n")
builder.WriteString(strings.Repeat(" ", len(descriptor)))
builder.WriteString(" ")
for i := lineBeginning; i <= int(p.Causer.Start); i++ { for i := lineBeginning; i <= int(p.Causer.Start); i++ {
builder.WriteRune(' ') builder.WriteRune(' ')
} }
@ -58,20 +69,45 @@ 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
Path string
}
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(path string) (*Program, error) {
imports := make([]string, 0)
// top level statements // top level statements
statements := make([]Node, 0) statements := make([]Node, 0)
@ -79,17 +115,33 @@ 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, "import requires a path/name to import"); 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 {
return nil, err return nil, err
} }
if b != nil {
statements = append(statements, b) statements = append(statements, b)
} }
}
return &BlockNode{ return &Program{
statements: statements, imports,
&BlockNode{
statements,
0,
p.curr.Start + p.curr.Length,
},
path,
}, nil }, nil
} }
@ -107,9 +159,9 @@ func (p *Parser) accept(tokenType TokenType) bool {
return false return false
} }
func (p *Parser) expect(tokenType TokenType) error { func (p *Parser) expect(tokenType TokenType, reason string) error {
if !p.accept(tokenType) { if !p.accept(tokenType) {
return p.error("Expected token "+tokenType.String()+", got "+p.curr.Type.String(), p.curr) return p.error(fmt.Sprintf("Expected token %s, got %s; %s", tokenType, p.curr.Type, reason), p.curr)
} }
return nil return nil
} }
@ -134,9 +186,10 @@ func (p *Parser) advance() {
} }
func (p *Parser) error(error string, causer *Token) error { func (p *Parser) error(error string, causer *Token) error {
return &ParsingError{ return ParsingError{
Description: error, Description: error,
Causer: causer, Causer: causer,
Source: p.source,
} }
} }
@ -147,6 +200,8 @@ func (p *Parser) factor() (Node, error) {
return &StringNode{ return &StringNode{
(*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1], (*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1],
(*p.prev).Lexeme, (*p.prev).Lexeme,
p.prev.Start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenNumber: case TokenNumber:
@ -159,17 +214,37 @@ func (p *Parser) factor() (Node, error) {
return &NumberNode{ return &NumberNode{
num, num,
p.prev.Start,
p.prev.Start + p.prev.Length,
}, nil
case TokenHexadecimal:
p.advance()
start := (*p.prev).Start
num, err := strconv.ParseUint((*p.prev).Lexeme[2:], 16, NumberSize)
if err != nil {
return nil, err
}
return &NumberNode{
float64(num),
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenTrue: case TokenTrue:
p.advance() p.advance()
return &BooleanNode{ return &BooleanNode{
true, true,
p.prev.Start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenFalse: case TokenFalse:
p.advance() p.advance()
return &BooleanNode{ return &BooleanNode{
false, false,
p.prev.Start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenNil: case TokenNil:
@ -178,11 +253,25 @@ func (p *Parser) factor() (Node, error) {
case TokenOpenBracket: case TokenOpenBracket:
p.advance() p.advance()
start := p.prev.Start
if p.accept(TokenCloseBracket) {
s, err := p.parseSignature()
if err != nil {
return nil, err
}
return &ListNode{
[]Node{},
s,
start,
p.prev.Start + p.prev.Length,
}, nil
}
var values []Node var values []Node
for !p.accept(TokenCloseBracket) { for !p.accept(TokenCloseBracket) {
if len(values) > 0 { if len(values) > 0 {
if err := p.expect(TokenComma); err != nil { if err := p.expect(TokenComma, "list values must be separated by a comma"); err != nil {
return nil, err return nil, err
} }
} }
@ -198,24 +287,48 @@ func (p *Parser) factor() (Node, error) {
return &ListNode{ return &ListNode{
values, values,
nil,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
// unary minus // unary minus
case TokenMinus: case TokenMinus:
p.advance() p.advance()
first := p.prev
f, err := p.factor() f, err := p.factor()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &BinaryNode{ return &UnaryNode{
BinarySubtraction, UnaryNegate,
&NumberNode{0},
f, f,
first.Start,
p.prev.Start + p.prev.Length,
}, nil
case TokenBang:
p.advance()
start := p.prev.Start
v, err := p.factor()
if err != nil {
return nil, err
}
return &UnaryNode{
UnaryNot,
v,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenName: case TokenName:
p.advance() p.advance()
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
start := p.prev.Start
nameEnd := start + p.prev.Length
if p.curr.Type == TokenOpenParenthesis { if p.curr.Type == TokenOpenParenthesis {
args, err := p.parseArgs() args, err := p.parseArgs()
@ -226,23 +339,39 @@ func (p *Parser) factor() (Node, error) {
return &CallNode{ return &CallNode{
&ReferenceNode{ &ReferenceNode{
name, name,
start,
nameEnd,
}, },
args, args,
true, true,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
} }
return &ReferenceNode{ return &ReferenceNode{
name, name,
start,
nameEnd,
}, nil }, nil
case TokenFunc: case TokenFunc:
p.advance() p.advance()
start := p.prev.Start
params, err := p.parseParams() params, err := p.parseParams()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var sig TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace {
sig, err = p.parseSignature()
if err != nil {
return nil, err
}
}
b, err := p.block(false) b, err := p.block(false)
if err != nil { if err != nil {
return nil, err return nil, err
@ -251,7 +380,10 @@ func (p *Parser) factor() (Node, error) {
return &FunctionNode{ return &FunctionNode{
"*", "*",
params, params,
sig,
b, b,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenOpenParenthesis: case TokenOpenParenthesis:
@ -260,20 +392,20 @@ func (p *Parser) factor() (Node, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := p.expect(TokenCloseParenthesis); err != nil { if err := p.expect(TokenCloseParenthesis, "an opened parenthesis must be closed"); err != nil {
return nil, err return nil, err
} }
return v, nil return v, nil
default: default:
err := p.error("invalid factor", p.curr) return nil, p.error("invalid factor", p.curr)
p.advance()
return nil, err
} }
} }
func (p *Parser) prop() (Node, error) { func (p *Parser) prop() (Node, error) {
start := p.curr.Start
v, err := p.factor() v, err := p.factor()
if err != nil { if err != nil {
return nil, err return nil, err
@ -281,7 +413,7 @@ func (p *Parser) prop() (Node, error) {
// parse chains of prop-getting ( "".split().join().length.round() ) // parse chains of prop-getting ( "".split().join().length.round() )
for p.accept(TokenDot) { for p.accept(TokenDot) {
if err := p.expect(TokenName); err != nil { if err := p.expect(TokenName, "property must be a name"); err != nil {
return nil, err return nil, err
} }
property := (*p.prev).Lexeme property := (*p.prev).Lexeme
@ -289,6 +421,8 @@ func (p *Parser) prop() (Node, error) {
v = &AccessNode{ v = &AccessNode{
v, v,
property, property,
start,
p.prev.Start + p.prev.Length,
} }
// if called, also add // if called, also add
@ -302,6 +436,8 @@ func (p *Parser) prop() (Node, error) {
v, v,
args, args,
true, true,
start,
p.prev.Start + p.prev.Length,
} }
} }
} }
@ -310,6 +446,7 @@ func (p *Parser) prop() (Node, error) {
} }
func (p *Parser) product() (Node, error) { func (p *Parser) product() (Node, error) {
start := p.curr.Start
left, err := p.prop() left, err := p.prop()
if err != nil { if err != nil {
return nil, err return nil, err
@ -331,6 +468,8 @@ func (p *Parser) product() (Node, error) {
op, op,
left, left,
f, f,
start,
p.prev.Start + p.prev.Length,
} }
} }
@ -338,6 +477,8 @@ func (p *Parser) product() (Node, error) {
} }
func (p *Parser) term() (Node, error) { func (p *Parser) term() (Node, error) {
start := p.curr.Start
left, err := p.product() left, err := p.product()
if err != nil { if err != nil {
return nil, err return nil, err
@ -359,6 +500,8 @@ func (p *Parser) term() (Node, error) {
op, op,
left, left,
pr, pr,
start,
p.prev.Start + p.prev.Length,
} }
} }
@ -366,6 +509,7 @@ func (p *Parser) term() (Node, error) {
} }
func (p *Parser) comparison() (Node, error) { func (p *Parser) comparison() (Node, error) {
start := p.curr.Start
left, err := p.term() left, err := p.term()
if err != nil { if err != nil {
@ -403,10 +547,13 @@ func (p *Parser) comparison() (Node, error) {
op, op,
left, left,
t, t,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
} }
func (p *Parser) condition() (Node, error) { func (p *Parser) condition() (Node, error) {
start := p.curr.Start
left, err := p.comparison() left, err := p.comparison()
if err != nil { if err != nil {
return nil, err return nil, err
@ -434,12 +581,15 @@ func (p *Parser) condition() (Node, error) {
op, op,
left, left,
c, c,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
} }
func (p *Parser) statement() (Node, error) { func (p *Parser) statement() (Node, error) {
switch (*p.curr).Type { switch (*p.curr).Type {
case TokenIf: case TokenIf:
start := p.curr.Start
p.advance() p.advance()
condition, err := p.condition() condition, err := p.condition()
@ -470,20 +620,25 @@ func (p *Parser) statement() (Node, error) {
condition, condition,
then, then,
otherwise, otherwise,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenName: case TokenName:
p.advance() p.advance()
start := p.prev.Start
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
if (*p.curr).Type == TokenDot { if (*p.curr).Type == TokenDot {
var v Node = &ReferenceNode{ var v Node = &ReferenceNode{
name, name,
start,
p.prev.Start + p.prev.Length,
} }
// parse chains of prop-getting ( "".split().join().length.round() ) // parse chains of prop-getting ( "".split().join().length.round() )
for p.accept(TokenDot) { for p.accept(TokenDot) {
if err := p.expect(TokenName); err != nil { if err := p.expect(TokenName, "property must be name"); err != nil {
return nil, err return nil, err
} }
property := (*p.prev).Lexeme property := (*p.prev).Lexeme
@ -491,6 +646,8 @@ func (p *Parser) statement() (Node, error) {
v = &AccessNode{ v = &AccessNode{
v, v,
property, property,
start,
p.prev.Start + p.prev.Length,
} }
// if called, also add // if called, also add
@ -504,6 +661,8 @@ func (p *Parser) statement() (Node, error) {
v, v,
args, args,
(*p.curr).Type == TokenDot, // if the chain is continued, keep the value. (*p.curr).Type == TokenDot, // if the chain is continued, keep the value.
start,
p.prev.Start + p.prev.Length,
} }
} }
} }
@ -518,9 +677,13 @@ func (p *Parser) statement() (Node, error) {
return &CallNode{ return &CallNode{
&ReferenceNode{ &ReferenceNode{
name, name,
start,
start + Pos(len(name)),
}, },
args, args,
false, false,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
} else if p.accept(TokenAssign) || p.accept(TokenDeclare) { } else if p.accept(TokenAssign) || p.accept(TokenDeclare) {
isDeclaration := p.prev.Type == TokenDeclare isDeclaration := p.prev.Type == TokenDeclare
@ -533,28 +696,19 @@ func (p *Parser) statement() (Node, error) {
name, name,
c, c,
isDeclaration, isDeclaration,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
} else {
return p.condition()
} }
case TokenImport: return nil, p.error("invalid statement", p.curr)
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()
if err := p.expect(TokenName); err != nil { funcStart := p.prev.Start
if err := p.expect(TokenName, "function must have a name"); err != nil {
return nil, err return nil, err
} }
name := p.prev.Lexeme name := p.prev.Lexeme
@ -564,6 +718,14 @@ func (p *Parser) statement() (Node, error) {
return nil, err return nil, err
} }
var yield TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace {
yield, err = p.parseSignature()
if err != nil {
return nil, err
}
}
b, err := p.block(false) b, err := p.block(false)
if err != nil { if err != nil {
return nil, err return nil, err
@ -574,13 +736,19 @@ func (p *Parser) statement() (Node, error) {
&FunctionNode{ &FunctionNode{
name, name,
params, params,
yield,
b, b,
funcStart,
p.prev.Start + p.prev.Length,
}, },
true, true,
funcStart,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenWhile: case TokenWhile:
p.advance() p.advance()
start := p.prev.Start
c, err := p.condition() c, err := p.condition()
if err != nil { if err != nil {
@ -595,10 +763,13 @@ func (p *Parser) statement() (Node, error) {
return &LoopNode{ return &LoopNode{
c, c,
b, b,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenReturn: case TokenReturn:
p.advance() p.advance()
start := p.prev.Start
c, err := p.condition() c, err := p.condition()
if err != nil { if err != nil {
@ -607,6 +778,8 @@ func (p *Parser) statement() (Node, error) {
return &ReturnNode{ return &ReturnNode{
c, c,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
case TokenBreakpoint: case TokenBreakpoint:
@ -614,24 +787,33 @@ func (p *Parser) statement() (Node, error) {
return &BreakpointNode{}, nil return &BreakpointNode{}, nil
case TokenImport:
defer p.advance()
return nil, p.error("import statements must be top-level", p.curr)
default: default:
err := p.error("invalid statement", p.curr) defer p.advance()
p.advance() return nil, p.error("invalid statement", p.curr)
return nil, err
} }
} }
func (p *Parser) block(canBeStatement bool) (Node, error) { func (p *Parser) block(canBeStatement bool) (Node, error) {
if canBeStatement { if canBeStatement {
if !p.accept(TokenOpenBrace) { if !p.accept(TokenOpenBrace) {
if p.curr.Type == TokenEOF {
return nil, nil
}
return p.statement() return p.statement()
} }
} else { } else {
if err := p.expect(TokenOpenBrace); err != nil { if err := p.expect(TokenOpenBrace, "a block is required"); err != nil {
return nil, err return nil, err
} }
} }
start := p.prev.Start
statements := make([]Node, 0) statements := make([]Node, 0)
for !p.accept(TokenCloseBrace) { for !p.accept(TokenCloseBrace) {
@ -646,13 +828,15 @@ func (p *Parser) block(canBeStatement bool) (Node, error) {
return &BlockNode{ return &BlockNode{
statements, statements,
start,
p.prev.Start + p.prev.Length,
}, nil }, nil
} }
func (p *Parser) parseArgs() ([]Node, error) { func (p *Parser) parseArgs() ([]Node, error) {
args := make([]Node, 0) args := make([]Node, 0)
if err := p.expect(TokenOpenParenthesis); err != nil { if err := p.expect(TokenOpenParenthesis, "arguments must be contained in parenthesis"); err != nil {
return nil, err return nil, err
} }
@ -663,7 +847,7 @@ func (p *Parser) parseArgs() ([]Node, error) {
} }
args = append(args, c) args = append(args, c)
for !p.accept(TokenCloseParenthesis) { for !p.accept(TokenCloseParenthesis) {
if err := p.expect(TokenComma); err != nil { if err := p.expect(TokenComma, "arguments must be separated by comma"); err != nil {
return nil, err return nil, err
} }
c, err = p.condition() c, err = p.condition()
@ -678,30 +862,142 @@ func (p *Parser) parseArgs() ([]Node, error) {
} }
// parseParams parse parameters and parentheses // parseParams parse parameters and parentheses
func (p *Parser) parseParams() ([]string, error) { func (p *Parser) parseParams() ([]FunctionParameter, error) {
if err := p.expect(TokenOpenParenthesis); err != nil { if err := p.expect(TokenOpenParenthesis, "parameters must be in parentheses"); err != nil {
return nil, err return nil, err
} }
params := make([]string, 0) params := make([]FunctionParameter, 0)
if p.accept(TokenName) { if p.accept(TokenName) {
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
params = append(params, name) if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil {
for !p.accept(TokenCloseParenthesis) {
if err := p.expect(TokenComma); err != nil {
return nil, err return nil, err
} }
if err := p.expect(TokenName); err != nil {
t, err := p.parseSignature()
if err != nil {
return nil, err
}
params = append(params, FunctionParameter{
name,
t,
})
for !p.accept(TokenCloseParenthesis) {
if err := p.expect(TokenComma, "parameters must be separated by comma"); err != nil {
return nil, err
}
if err := p.expect(TokenName, "parameters must have a name (cannot have trailing comma)"); err != nil {
return nil, err return nil, err
} }
name = (*p.prev).Lexeme name = (*p.prev).Lexeme
params = append(params, name) if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil {
return nil, err
}
t, err := p.parseSignature()
if err != nil {
return nil, err
}
params = append(params, FunctionParameter{
name,
t,
})
} }
} else { } else {
if err := p.expect(TokenCloseParenthesis); err != nil { if err := p.expect(TokenCloseParenthesis, "must close parameter list"); err != nil {
return nil, err return nil, err
} }
} }
return params, nil return params, nil
} }
func (p *Parser) parseSignature() (TypeSignature, error) {
var s TypeSignature
if p.accept(TokenFunc) {
if err := p.expect(TokenOpenParenthesis, "func signature must have parentheses for parameters"); err != nil {
return nil, err
}
var in []TypeSignature
for !p.accept(TokenCloseParenthesis) {
if len(in) > 0 {
if err := p.expect(TokenComma, "parameter types must be separated by a comma"); err != nil {
return nil, err
}
}
sig, err := p.parseSignature()
if err != nil {
return nil, err
}
in = append(in, sig)
}
out, err := p.parseSignature()
if err != nil {
return nil, err
}
s = &FunctionSignature{
in,
out,
}
} else {
if err := p.expect(TokenName, "type must be a name"); 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, "list must have content typed"); err != nil {
return nil, err
}
contents, err := p.parseSignature()
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseBracket, "list must close parameter list"); err != nil {
return nil, err
}
s = &ListSignature{
contents,
}
case "any":
s = &AnySignature{}
default:
return nil, p.error("unsupported type: "+name, p.prev)
}
}
if p.accept(TokenPipe) {
other, err := p.parseSignature()
if err != nil {
return nil, err
}
return &CompositeSignature{
s,
other,
}, nil
}
return s, nil
}

View file

@ -1,14 +1,16 @@
package core package core
import ( import (
"fmt"
"strconv" "strconv"
"strings"
"testing" "testing"
) )
func TestNewParser(t *testing.T) { func TestNewParser(t *testing.T) {
tokens := make([]Token, 0) tokens := make([]Token, 0)
p := NewParser(tokens) p := NewParser("", tokens)
if p == nil { if p == nil {
t.Fatal("parser should not be nil") t.Fatal("parser should not be nil")
@ -32,7 +34,7 @@ func TestNewParser(t *testing.T) {
func BenchmarkNewParser(b *testing.B) { func BenchmarkNewParser(b *testing.B) {
tokens := make([]Token, 0) tokens := make([]Token, 0)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = NewParser(tokens) _ = NewParser("", tokens)
} }
} }
@ -66,14 +68,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition, BinaryAddition,
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
&NumberNode{ &NumberNode{
2, 2,
0, 0,
}, },
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
"assignment": { "assignment": {
@ -90,10 +97,13 @@ func GetTokenTestData() map[string]TokenTestData {
&StringNode{ &StringNode{
"Hello world!", "Hello world!",
"\"Hello world!\"", "\"Hello world!\"",
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
"declaration": { "declaration": {
@ -113,14 +123,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition, BinaryAddition,
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
&ReferenceNode{ &ReferenceNode{
"b", "b",
0, 0,
}, },
0, 0,
}, },
true, true,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
// (2 + 1) * 5 + 3 / (6 - 2) - 10 / 2 // (2 + 1) * 5 + 3 / (6 - 2) - 10 / 2
@ -167,30 +182,63 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryMultiplication, BinaryMultiplication,
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&NumberNode{2}, &NumberNode{
&NumberNode{1}, 2,
0, 0,
}, },
&NumberNode{5}, &NumberNode{
1,
0, 0,
},
0, 0,
},
&NumberNode{
5,
0, 0,
},
0, 0,
}, },
&BinaryNode{ &BinaryNode{
BinaryDivision, BinaryDivision,
&NumberNode{3}, &NumberNode{
3,
0, 0,
},
&BinaryNode{ &BinaryNode{
BinarySubtraction, BinarySubtraction,
&NumberNode{6}, &NumberNode{
&NumberNode{2}, 6,
0, 0,
}, },
&NumberNode{
2,
0, 0,
}, },
0, 0,
},
0, 0,
},
0, 0,
}, },
&BinaryNode{ &BinaryNode{
BinaryDivision, BinaryDivision,
&NumberNode{10}, &NumberNode{
&NumberNode{2}, 10,
0, 0,
}, },
&NumberNode{
2,
0, 0,
},
0, 0,
},
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
"condition_equal": { "condition_equal": {
@ -210,14 +258,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality, BinaryEquality,
&NumberNode{ &NumberNode{
20, 20,
0, 0,
}, },
&NumberNode{ &NumberNode{
15, 15,
0, 0,
}, },
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
"if_statement": { "if_statement": {
@ -240,10 +293,13 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality, BinaryEquality,
&ReferenceNode{ &ReferenceNode{
"a", "a",
0, 0,
}, },
&NumberNode{ &NumberNode{
0, 0,
0, 0,
}, },
0, 0,
}, },
do: &BlockNode{ do: &BlockNode{
[]Node{ []Node{
@ -251,13 +307,17 @@ func GetTokenTestData() map[string]TokenTestData {
"b", "b",
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
false, false,
0, 0,
},
},
0, 0,
}, },
}, },
}, },
}, 0, 0,
},
}, },
}, },
"if_else_statement": { "if_else_statement": {
@ -286,10 +346,13 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality, BinaryEquality,
&ReferenceNode{ &ReferenceNode{
"a", "a",
0, 0,
}, },
&NumberNode{ &NumberNode{
0, 0,
0, 0,
}, },
0, 0,
}, },
do: &BlockNode{ do: &BlockNode{
[]Node{ []Node{
@ -297,10 +360,13 @@ func GetTokenTestData() map[string]TokenTestData {
"b", "b",
&NumberNode{ &NumberNode{
1, 1,
0, 0,
}, },
false, false,
0, 0,
}, },
}, },
0, 0,
}, },
otherwise: &BlockNode{ otherwise: &BlockNode{
[]Node{ []Node{
@ -308,13 +374,17 @@ func GetTokenTestData() map[string]TokenTestData {
"b", "b",
&NumberNode{ &NumberNode{
0, 0,
0, 0,
}, },
false, false,
0, 0,
},
},
0, 0,
}, },
}, },
}, },
}, 0, 0,
},
}, },
}, },
"empty_block": { "empty_block": {
@ -327,8 +397,10 @@ func GetTokenTestData() map[string]TokenTestData {
[]Node{ []Node{
&BlockNode{ &BlockNode{
[]Node{}, []Node{},
0, 0,
}, },
}, },
0, 0,
}, },
}, },
"lambda": { // a := func(a, b) { return a + b } "lambda": { // a := func(a, b) { return a + b }
@ -338,9 +410,14 @@ func GetTokenTestData() map[string]TokenTestData {
NewToken(TokenFunc, 3, 4, 0, "func"), NewToken(TokenFunc, 3, 4, 0, "func"),
NewToken(TokenOpenParenthesis, 7, 1, 0, "("), NewToken(TokenOpenParenthesis, 7, 1, 0, "("),
NewToken(TokenName, 8, 1, 0, "a"), NewToken(TokenName, 8, 1, 0, "a"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenComma, 9, 1, 0, ","), NewToken(TokenComma, 9, 1, 0, ","),
NewToken(TokenName, 10, 1, 0, "b"), NewToken(TokenName, 10, 1, 0, "b"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenCloseParenthesis, 11, 1, 0, ")"), NewToken(TokenCloseParenthesis, 11, 1, 0, ")"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenOpenBrace, 12, 1, 1, "{"), NewToken(TokenOpenBrace, 12, 1, 1, "{"),
NewToken(TokenReturn, 13, 6, 1, "return"), NewToken(TokenReturn, 13, 6, 1, "return"),
@ -357,7 +434,17 @@ func GetTokenTestData() map[string]TokenTestData {
"a", "a",
&FunctionNode{ &FunctionNode{
"*", "*",
[]string{"a", "b"}, []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
@ -365,18 +452,26 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition, BinaryAddition,
&ReferenceNode{ &ReferenceNode{
"a", "a",
0, 0,
}, },
&ReferenceNode{ &ReferenceNode{
"b", "b",
0, 0,
},
0, 0,
},
0, 0,
}, },
}, },
0, 0,
}, },
}, 0, 0,
},
}, },
true, true,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
"function_declaration": { "function_declaration": {
@ -404,7 +499,17 @@ func GetTokenTestData() map[string]TokenTestData {
"a", "a",
&FunctionNode{ &FunctionNode{
"a", "a",
[]string{"a", "b"}, []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
@ -412,18 +517,26 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition, BinaryAddition,
&ReferenceNode{ &ReferenceNode{
"a", "a",
0, 0,
}, },
&ReferenceNode{ &ReferenceNode{
"b", "b",
0, 0,
},
0, 0,
},
0, 0,
}, },
}, },
0, 0,
}, },
}, 0, 0,
},
}, },
true, true,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
"prop_getting": { "prop_getting": {
@ -443,12 +556,16 @@ func GetTokenTestData() map[string]TokenTestData {
&AccessNode{ &AccessNode{
&ReferenceNode{ &ReferenceNode{
"a", "a",
0, 0,
}, },
"b", "b",
0, 0,
}, },
true, true,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
"list_init": { "list_init": {
@ -488,27 +605,43 @@ func GetTokenTestData() map[string]TokenTestData {
[]Node{ []Node{
&ReferenceNode{ &ReferenceNode{
"a", "a",
0, 0,
}, },
&NumberNode{ &NumberNode{
3.141, 3.141,
0, 0,
}, },
&StringNode{ &StringNode{
"Hello world!", "Hello world!",
"\"Hello world!\"", "\"Hello world!\"",
0, 0,
}, },
&BooleanNode{ &BooleanNode{
true, true,
0, 0,
}, },
&ListNode{ &ListNode{
[]Node{ []Node{
&NumberNode{2}, &NumberNode{3}, &NumberNode{
2,
0, 0,
}, &NumberNode{
3,
0, 0,
}, },
}, },
nil,
0, 0,
}, },
}, },
nil,
0, 0,
},
true, true,
0, 0,
}, },
}, },
0, 0,
}, },
}, },
} }
@ -642,15 +775,17 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
t.Logf("Function node names match (%s)", n.name) t.Logf("Function node names match (%s)", n.name)
} }
if len(n.params) != len(m.params) { if len(n.parameters) != len(m.parameters) {
t.Fatalf("Function node parameters count does not match (%d and %d)", len(n.params), len(m.params)) t.Fatalf("Function node parameters count does not match (%d and %d)", len(n.parameters), len(m.parameters))
} else { } else {
t.Logf("Function node parameters count is equal (%d) ", len(n.params)) t.Logf("Function node parameters count is equal (%d) ", len(n.parameters))
} }
for i, p := range m.params { for i, p := range m.parameters {
if n.params[i] != p { if !n.parameters[i].Signature.Matches(p.Signature) {
t.Errorf("Function node parameter %d does not match: %s and %s", i, p, m.params) t.Errorf("Function node parameter signature %d does not match: %s and %s", i, p.Signature, n.parameters[i].Signature)
} else if n.parameters[i].Name != p.Name {
t.Errorf("Function node parameter name %d does not match: %s and %s", i, p.Name, n.parameters[i].Name)
} else { } else {
t.Logf("Function node parameter %d matches (%s)", i, p) t.Logf("Function node parameter %d matches (%s)", i, p)
} }
@ -665,6 +800,105 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
} }
} }
func SerializeTokens(tokens []Token) string {
out := strings.Builder{}
level := 0
for _, token := range tokens {
switch token.Type {
case TokenPlus:
out.WriteString(" + ")
case TokenMinus:
out.WriteString(" - ")
case TokenStar:
out.WriteString("*")
case TokenSlash:
out.WriteString("/")
case TokenBang:
out.WriteString("!")
case TokenSemicolon:
out.WriteString(";")
case TokenNumber:
out.WriteString(token.Lexeme)
case TokenString:
out.WriteString(fmt.Sprintf("\"%s\"", token.Lexeme))
case TokenName:
out.WriteString(token.Lexeme)
case TokenOpenParenthesis:
out.WriteString("(")
case TokenCloseParenthesis:
out.WriteString(")")
case TokenOpenBracket:
out.WriteString("[")
case TokenCloseBracket:
out.WriteString("]")
case TokenOpenBrace:
out.WriteString("{")
level = level + 1
case TokenCloseBrace:
out.WriteString("}")
level = level - 1
case TokenTrue:
out.WriteString("true")
case TokenFalse:
out.WriteString("false")
case TokenNil:
out.WriteString("nil")
case TokenFunc:
out.WriteString("func")
case TokenReturn:
out.WriteString("return ")
case TokenWhile:
out.WriteString("while ")
case TokenVar:
out.WriteString("var ")
case TokenIf:
out.WriteString("if ")
case TokenElse:
out.WriteString(" else ")
case TokenImport:
out.WriteString("import ")
case TokenComma:
out.WriteString(", ")
case TokenDot:
out.WriteString(".")
case TokenColon:
out.WriteString(": ")
case TokenAssign:
out.WriteString(" = ")
case TokenDeclare:
out.WriteString(" := ")
case TokenBangEquals:
out.WriteString(" != ")
case TokenEquals:
out.WriteString(" == ")
case TokenGreaterThan:
out.WriteString(" > ")
case TokenLessThan:
out.WriteString(" < ")
case TokenGreaterThanOrEqual:
out.WriteString(" >= ")
case TokenLessThanOrEqual:
out.WriteString(" <= ")
case TokenDoubleAmpersand:
out.WriteString(" && ")
case TokenDoublePipe:
out.WriteString(" || ")
case TokenBreakpoint:
out.WriteString("breakpoint")
case TokenEOF:
out.WriteString(fmt.Sprintf("<error: \"%s\">", token.Lexeme))
case TokenHexadecimal:
out.WriteString(token.Lexeme)
case TokenPipe:
out.WriteString(" | ")
case TokenError:
}
}
return out.String()
}
func TestParser_Parse(t *testing.T) { func TestParser_Parse(t *testing.T) {
t.Logf("Getting test data") t.Logf("Getting test data")
tokenData := GetTokenTestData() tokenData := GetTokenTestData()
@ -676,17 +910,17 @@ func TestParser_Parse(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Logf("Initializing parser") t.Logf("Initializing parser")
p := NewParser(data.tokens) p := NewParser("", data.tokens)
t.Logf("Parsing main") t.Logf("Parsing main")
tree, err := p.Parse() tree, err := p.Parse("")
if err != nil { if err != nil {
t.Fatalf("Unexpected error(s): %s", err.(*ParsingError).Format([]rune{})) t.Fatalf("Unexpected error(s): %s", err.(ParsingError).Format())
} }
t.Logf("Checking parsed tree") t.Logf("Checking parsed tree")
NodeEquality(t, tree, data.tree) NodeEquality(t, tree.Block, data.tree)
}) })
} }
} }
@ -697,9 +931,9 @@ func BenchmarkParser_Parse(b *testing.B) {
for name, data := range tokenData { for name, data := range tokenData {
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++ {
p := NewParser(data.tokens) p := NewParser("", data.tokens)
_, _ = p.Parse() _, _ = p.Parse("")
} }
}) })
} }

View file

@ -2,24 +2,27 @@ package core
type Stack[T any] struct { type Stack[T any] struct {
Current Pos Current Pos
Size Pos Capacity Pos
items []T items []T
} }
func NewStack[T any](size Pos) *Stack[T] { func NewStack[T any](capacity Pos) *Stack[T] {
return &Stack[T]{ return &Stack[T]{
items: make([]T, size), items: make([]T, 16),
Size: size, Capacity: capacity,
Current: 0, Current: 0,
} }
} }
func (s *Stack[T]) Push(items ...T) { func (s *Stack[T]) Push(items ...T) {
for _, item := range items { for _, item := range items {
if s.Current >= s.Size { if s.Current >= s.Capacity {
panic("stack overflow") panic("stack overflow")
} }
if int(s.Current) == len(s.items) {
s.items = append(s.items, item)
}
s.items[s.Current] = item s.items[s.Current] = item
s.Current++ s.Current++
@ -45,7 +48,7 @@ func (s *Stack[T]) Peek() T {
// check whether the stack is invalid (stack over-/underflow) // check whether the stack is invalid (stack over-/underflow)
func (s *Stack[T]) check() { func (s *Stack[T]) check() {
if s.Current >= s.Size { if s.Current >= s.Capacity {
panic("stack underflow") panic("stack underflow")
} }

View file

@ -25,16 +25,10 @@ func TestNewStack(t *testing.T) {
s := NewStack[any](Pos(size)) s := NewStack[any](Pos(size))
if s.Size != Pos(size) { if s.Capacity != Pos(size) {
t.Errorf("Stack size (%d) does not match expected size (%d)", s.Size, size) t.Errorf("Stack size (%d) does not match expected size (%d)", s.Capacity, size)
} else { } else {
t.Logf("Stack size is expected size (%d)", s.Size) t.Logf("Stack size is expected size (%d)", s.Capacity)
}
if len(s.items) != size {
t.Errorf("internal items slice size (%d) does not match expected size (%d)", len(s.items), size)
} else {
t.Logf("internal items slice size is as expected (%d)", len(s.items))
} }
if s.Current != 0 { if s.Current != 0 {

349
core/types.go Normal file
View file

@ -0,0 +1,349 @@
package core
import (
"fmt"
"strings"
)
type Type int
const (
TypeString Type = iota
TypeNumber
TypeBoolean
TypeNil
TypeList
TypeObject
TypeFunction
TypeAny
TypeComposite
TypeInner
)
func (t Type) String() string {
switch t {
case TypeString:
return "string"
case TypeNumber:
return "number"
case TypeBoolean:
return "boolean"
case TypeNil:
return "nil"
case TypeList:
return "list"
case TypeObject:
return "object"
case TypeFunction:
return "func"
case TypeAny:
return "any"
case TypeComposite:
return "composite"
case TypeInner:
return "inner"
}
panic(fmt.Sprintf("unsupported string conversion for type %v", int(t)))
}
func SignatureOf(v Value) TypeSignature {
switch t := v.(type) {
case *StringValue:
return &StringSignature{}
case *NumberValue:
return &NumberSignature{}
case *BoolValue:
return &BooleanSignature{}
case *ListValue:
// try to deduce contents type
var contains TypeSignature
for _, p := range t.Items {
sig := SignatureOf(p)
if contains == nil {
contains = sig
} else if !contains.Matches(sig) {
contains = &AnySignature{}
break
}
}
return &ListSignature{
contains,
}
case *ObjectValue:
return &ObjectSignature{}
case *FunctionValue:
params := make([]TypeSignature, len(t.Params))
for i, p := range t.Params {
params[i] = p.Signature
}
return &FunctionSignature{
params,
t.Yield,
}
case *BuiltinFunctionValue:
return t.Signature
}
panic(fmt.Sprintf("unknown value; cannot get signature of %s", v))
}
type TypeSignature interface {
Type() Type
// Matches check if this type signature matches another.
Matches(TypeSignature) bool
// String create a human-readable string version of the value type.
String() string
}
type NilSignature struct{}
func (*NilSignature) Type() Type {
return TypeNil
}
func (s *NilSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeNil
}
func (*NilSignature) String() string {
return "nil"
}
type StringSignature struct{}
func (*StringSignature) Type() Type {
return TypeString
}
func (s *StringSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeString
}
func (*StringSignature) String() string {
return "string"
}
type NumberSignature struct{}
func (*NumberSignature) Type() Type {
return TypeNumber
}
func (s *NumberSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeNumber
}
func (*NumberSignature) String() string {
return "number"
}
type BooleanSignature struct{}
func (*BooleanSignature) Type() Type {
return TypeBoolean
}
func (s *BooleanSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeBoolean
}
func (*BooleanSignature) String() string {
return "boolean"
}
type ListSignature struct {
Contents TypeSignature
}
func (*ListSignature) Type() Type {
return TypeList
}
func (s *ListSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || (other.Type() == TypeList && other.(*ListSignature).Contents.Matches(s.Contents))
}
func (s *ListSignature) String() string {
return fmt.Sprintf("list[%s]", s.Contents)
}
type ObjectSignature struct {
Members map[string]TypeSignature
}
func (*ObjectSignature) Type() Type {
return TypeObject
}
func (s *ObjectSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
if other.Type() == TypeAny {
return true
}
if other.Type() != TypeObject {
return false
}
o := other.(*ObjectSignature)
if len(o.Members) != len(s.Members) {
return false
}
for name, member := range s.Members {
v, ok := o.Members[name]
if !ok {
return false
}
if !v.Matches(member) {
return false
}
}
return true
}
func (s *ObjectSignature) String() string {
panic("unimplemented")
}
type FunctionSignature struct {
In []TypeSignature
Out TypeSignature
}
func (*FunctionSignature) Type() Type {
return TypeFunction
}
func (s *FunctionSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
if other.Type() == TypeAny {
return true
}
if other.Type() != TypeFunction {
return false
}
f := other.(*FunctionSignature)
if !s.Out.Matches(f.Out) {
return false
}
if len(f.In) != len(s.In) {
return false
}
for i, p := range s.In {
v := f.In[i]
if !p.Matches(v) {
return false
}
}
return true
}
func (s *FunctionSignature) String() string {
b := strings.Builder{}
b.WriteString("func(")
for i, t := range s.In {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(t.String())
}
b.WriteString(")")
if s.Out.Type() != TypeNil {
b.WriteString(" ")
b.WriteString(s.Out.String())
}
return b.String()
}
type AnySignature struct{}
func (*AnySignature) Type() Type {
return TypeAny
}
func (*AnySignature) Matches(_ TypeSignature) bool {
return true
}
func (*AnySignature) String() string {
return "any"
}
type CompositeSignature struct {
A TypeSignature
B TypeSignature
}
func (*CompositeSignature) Type() Type {
return TypeComposite
}
func (s *CompositeSignature) Matches(other TypeSignature) bool {
return s.A.Matches(other) || s.B.Matches(other)
}
func (s *CompositeSignature) String() string {
return fmt.Sprintf("%s|%s", s.A, s.B)
}
type InnerSignature struct{}
func (*InnerSignature) Type() Type {
return TypeInner
}
func (*InnerSignature) Matches(_ TypeSignature) bool {
return false
}
func (*InnerSignature) String() string {
return "inner"
}

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 {
@ -106,6 +105,9 @@ type Value interface {
// Get a member from the value. An error is returned if the member does not exist // Get a member from the value. An error is returned if the member does not exist
Get(string) (Value, error) Get(string) (Value, error)
// Clone create a clone of the value. The returned value is a pointer to a new value of the same type as the value.
Clone() Value
} }
type NilValue struct{} type NilValue struct{}
@ -130,8 +132,12 @@ func (v *NilValue) Get(_ string) (Value, error) {
return nil, errors.New("nil has no properties") return nil, errors.New("nil has no properties")
} }
func (v *NilValue) Clone() Value {
return &NilValue{}
}
type BoolValue struct { type BoolValue struct {
bool Boolean bool
} }
func (v *BoolValue) Type() ValueType { func (v *BoolValue) Type() ValueType {
@ -139,7 +145,7 @@ func (v *BoolValue) Type() ValueType {
} }
func (v *BoolValue) String() string { func (v *BoolValue) String() string {
if v.bool { if v.Boolean {
return "true" return "true"
} else { } else {
return "false" return "false"
@ -151,16 +157,22 @@ func (v *BoolValue) DebugString() string {
} }
func (v *BoolValue) Equals(other Value) bool { func (v *BoolValue) Equals(other Value) bool {
return other.Type() == BoolValueType && other.(*BoolValue).bool == v.bool return other.Type() == BoolValueType && other.(*BoolValue).Boolean == v.Boolean
} }
func (v *BoolValue) Get(_ string) (Value, error) { func (v *BoolValue) Get(_ string) (Value, error) {
return nil, errors.New("booleans have no properties") return nil, errors.New("booleans have no properties")
} }
func (v *BoolValue) Clone() Value {
return &BoolValue{
v.Boolean,
}
}
// ObjectValue An object with any number of members (key-value pairs) // ObjectValue An object with any number of members (key-value pairs)
type ObjectValue struct { type ObjectValue struct {
members map[string]Value Members map[string]Value
} }
func (v *ObjectValue) Type() ValueType { func (v *ObjectValue) Type() ValueType {
@ -169,12 +181,12 @@ func (v *ObjectValue) Type() ValueType {
func (v *ObjectValue) String() string { func (v *ObjectValue) String() string {
out := "{" out := "{"
for key, value := range v.members { for key, value := range v.Members {
if out != "{" { if out != "{" {
out += ", " out += ", "
} }
out += fmt.Sprintf("%q=%s", key, value.String()) out += fmt.Sprintf("%q=%s", key, value.DebugString())
} }
out += "}" out += "}"
@ -191,8 +203,8 @@ func (v *ObjectValue) Equals(other Value) bool {
return false return false
} }
for key, value := range v.members { for key, value := range v.Members {
if !object.members[key].Equals(value) { if !object.Members[key].Equals(value) {
return false return false
} }
} }
@ -203,26 +215,30 @@ func (v *ObjectValue) Equals(other Value) bool {
var ObjectPrototype = map[string]Value{ var ObjectPrototype = map[string]Value{
"set": &BuiltinFunctionValue{ "set": &BuiltinFunctionValue{
"set", "set",
[]string{"property", "value"}, &FunctionSignature{
func(vm *VM, _this Value, params map[string]Value) (Value, error) { []TypeSignature{&StringSignature{}, &ListSignature{}},
&NilSignature{},
},
func(vm *VM, _this Value, params []Value) (Value, error) {
this := _this.(*ObjectValue) this := _this.(*ObjectValue)
p := params["property"] p := params[1]
v, ok := params["value"].(*StringValue) v, ok := params[0].(*StringValue)
if !ok { if !ok {
return nil, errors.New("property is not a string") return nil, errors.New("property is not a string")
} }
this.members[v.string] = p this.Members[v.Text] = p
return &NilValue{}, nil return &NilValue{}, nil
}, },
nil, nil,
false,
}, },
} }
func (v *ObjectValue) Get(key string) (Value, error) { func (v *ObjectValue) Get(key string) (Value, error) {
if member, ok := v.members[key]; ok { if member, ok := v.Members[key]; ok {
return member, nil return member, nil
} else if p, ok := ObjectPrototype[key]; ok { } else if p, ok := ObjectPrototype[key]; ok {
return p, nil return p, nil
@ -231,9 +247,21 @@ func (v *ObjectValue) Get(key string) (Value, error) {
} }
} }
func (v *ObjectValue) Clone() Value {
m := make(map[string]Value, len(v.Members))
for name, mem := range v.Members {
m[name] = mem.Clone()
}
return &ObjectValue{
m,
}
}
// NumberValue Integer or floating-point values // NumberValue Integer or floating-point values
type NumberValue struct { type NumberValue struct {
float64 Number float64
} }
const NumberSize int = 64 const NumberSize int = 64
@ -243,7 +271,7 @@ func (v *NumberValue) Type() ValueType {
} }
func (v *NumberValue) String() string { func (v *NumberValue) String() string {
return strconv.FormatFloat(v.float64, 'g', -1, NumberSize) return strconv.FormatFloat(v.Number, 'g', -1, NumberSize)
} }
func (v *NumberValue) DebugString() string { func (v *NumberValue) DebugString() string {
@ -251,7 +279,7 @@ func (v *NumberValue) DebugString() string {
} }
func (v *NumberValue) Equals(other Value) bool { func (v *NumberValue) Equals(other Value) bool {
return other.Type() == NumberValueType && other.(*NumberValue).float64 == v.float64 return other.Type() == NumberValueType && other.(*NumberValue).Number == v.Number
} }
func (v *NumberValue) Get(_ string) (Value, error) { func (v *NumberValue) Get(_ string) (Value, error) {
@ -259,8 +287,14 @@ func (v *NumberValue) Get(_ string) (Value, error) {
return nil, errors.New("numbers have no properties") return nil, errors.New("numbers have no properties")
} }
func (v *NumberValue) Clone() Value {
return &NumberValue{
v.Number,
}
}
type StringValue struct { type StringValue struct {
string Text string
} }
func (v *StringValue) Type() ValueType { func (v *StringValue) Type() ValueType {
@ -268,7 +302,7 @@ func (v *StringValue) Type() ValueType {
} }
func (v *StringValue) String() string { func (v *StringValue) String() string {
return v.string return v.Text
} }
func (v *StringValue) DebugString() string { func (v *StringValue) DebugString() string {
@ -276,31 +310,47 @@ func (v *StringValue) DebugString() string {
} }
func (v *StringValue) Equals(other Value) bool { func (v *StringValue) Equals(other Value) bool {
return other.Type() == StringValueType && other.(*StringValue).string == v.string return other.Type() == StringValueType && other.(*StringValue).Text == v.Text
} }
var StringPrototype = map[string]*BuiltinFunctionValue{ var StringPrototype = map[string]*BuiltinFunctionValue{
"split": { "split": {
"split", "split",
[]string{"seperator"}, &FunctionSignature{
func(vm *VM, this Value, m map[string]Value) (Value, error) { []TypeSignature{&StringSignature{}},
&ListSignature{
&StringSignature{},
},
},
func(vm *VM, this Value, v []Value) (Value, error) {
str := this.(*StringValue).String() str := this.(*StringValue).String()
sep := m["seperator"].(*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,
},
"length": {
Name: "length",
Signature: &FunctionSignature{
[]TypeSignature{},
&NumberSignature{},
},
F: func(vm *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*StringValue).Text)), nil
},
}, },
} }
@ -312,9 +362,15 @@ func (v *StringValue) Get(key string) (Value, error) {
return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key)) return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key))
} }
func (v *StringValue) Clone() Value {
return &StringValue{
v.Text,
}
}
// ListValue a dynamic list of values // ListValue a dynamic list of values
type ListValue struct { type ListValue struct {
items []Value Items []Value
} }
func (v *ListValue) Type() ValueType { func (v *ListValue) Type() ValueType {
@ -323,7 +379,7 @@ func (v *ListValue) Type() ValueType {
func (v *ListValue) String() string { func (v *ListValue) String() string {
out := "[" out := "["
for i, item := range v.items { for i, item := range v.Items {
if i != 0 { if i != 0 {
out += ", " out += ", "
} }
@ -345,12 +401,12 @@ func (v *ListValue) Equals(other Value) bool {
l := other.(*ListValue) l := other.(*ListValue)
if len(v.items) != len(l.items) { if len(v.Items) != len(l.Items) {
return false return false
} }
for i, item := range l.items { for i, item := range v.Items {
if !item.Equals(l.items[i]) { if !item.Equals(l.Items[i]) {
return false return false
} }
} }
@ -361,19 +417,28 @@ func (v *ListValue) Equals(other Value) bool {
var ListPrototype = map[string]*BuiltinFunctionValue{ var ListPrototype = map[string]*BuiltinFunctionValue{
"append": { "append": {
"append", "append",
[]string{"item"}, &FunctionSignature{
func(_ *VM, this Value, p map[string]Value) (Value, error) { []TypeSignature{&AnySignature{}},
this.(*ListValue).items = append(this.(*ListValue).items, p["item"]) &NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
this.(*ListValue).Items = append(this.(*ListValue).Items, v[0])
return &NilValue{}, nil return &NilValue{}, nil
}, },
nil, nil,
false,
}, },
"at": { "at": {
"at", "at",
[]string{"index"}, &FunctionSignature{
func(_ *VM, this Value, p map[string]Value) (Value, error) { []TypeSignature{
items := this.(*ListValue).items &NumberSignature{},
index := int(p["index"].(*NumberValue).float64) },
&InnerSignature{},
},
func(_ *VM, this Value, p []Value) (Value, error) {
items := this.(*ListValue).Items
index := int(p[0].(*NumberValue).Number)
if index >= len(items) { if index >= len(items) {
return nil, errors.New(fmt.Sprintf("list index %x out of range", index)) return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
@ -382,56 +447,41 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
return items[index], nil return items[index], nil
}, },
nil, nil,
false,
}, },
"length": { "length": {
"length", "length",
[]string{}, &FunctionSignature{
func(_ *VM, this Value, p map[string]Value) (Value, error) { []TypeSignature{},
return GoToValue(len(this.(*ListValue).items)), nil &NumberSignature{},
}, },
nil, func(_ *VM, this Value, _ []Value) (Value, error) {
}, return GoToValue(len(this.(*ListValue).Items)), 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, nil,
false,
}, },
"reduce": { "reduce": {
"reduce", "reduce",
[]string{"f", "start"}, &FunctionSignature{
func(vm *VM, value Value, m map[string]Value) (Value, error) { []TypeSignature{
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&AnySignature{},
},
&AnySignature{},
},
&AnySignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
list := value.(*ListValue) list := value.(*ListValue)
f := m["f"] f := m[0]
sum := m["start"] sum := m[1]
for _, v := range list.items { for _, v := range list.Items {
result, err := vm.Call(f, []Value{sum, v}) result, err := vm.Call(f, []Value{sum, v})
if err != nil { if err != nil {
return nil, err return nil, err
@ -442,6 +492,7 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
return sum, nil return sum, nil
}, },
nil, nil,
false,
}, },
} }
@ -453,9 +504,22 @@ func (v *ListValue) Get(key string) (Value, error) {
return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key)) return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key))
} }
func (v *ListValue) Clone() Value {
n := make([]Value, len(v.Items))
for i, item := range v.Items {
n[i] = item.Clone()
}
return &ListValue{
n,
}
}
type FunctionValue struct { type FunctionValue struct {
Name string Name string
Params []string Params []FunctionParameter
Yield TypeSignature
Chunk *Chunk Chunk *Chunk
Parent Value Parent Value
} }
@ -482,11 +546,22 @@ func (v *FunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties") return nil, errors.New("functions have no properties")
} }
func (v *FunctionValue) Clone() Value {
return &FunctionValue{
v.Name,
v.Params,
v.Yield,
v.Chunk,
v.Parent,
}
}
type BuiltinFunctionValue struct { type BuiltinFunctionValue struct {
Name string Name string
Parameters []string Signature *FunctionSignature
F func(*VM, Value, map[string]Value) (Value, error) F func(*VM, Value, []Value) (Value, error)
Parent Value Parent Value
Constant bool
} }
func (v *BuiltinFunctionValue) Type() ValueType { func (v *BuiltinFunctionValue) Type() ValueType {
@ -510,6 +585,16 @@ func (v *BuiltinFunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties") return nil, errors.New("functions have no properties")
} }
func (v *BuiltinFunctionValue) Clone() Value {
return &BuiltinFunctionValue{
v.Name,
v.Signature,
v.F,
v.Parent,
v.Constant,
}
}
// 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
@ -541,3 +626,11 @@ func (v *VariableValue) Equals(other Value) bool {
func (v *VariableValue) Get(_ string) (Value, error) { func (v *VariableValue) Get(_ string) (Value, error) {
return nil, errors.New("variables have no properties") return nil, errors.New("variables have no properties")
} }
func (v *VariableValue) Clone() Value {
return &VariableValue{
v.name,
v.value.Clone(),
v.scope,
}
}

View file

@ -16,19 +16,19 @@ func CompareValues(t *testing.T, got Value, want Value) {
t.Logf("Both are nil") t.Logf("Both are nil")
return return
case BoolValueType: case BoolValueType:
if got.(*BoolValue).bool != want.(*BoolValue).bool { if got.(*BoolValue).Boolean != want.(*BoolValue).Boolean {
t.Errorf("bool value mismatch: got %v, want %v", got.(*BoolValue), want.(*BoolValue)) t.Errorf("bool value mismatch: got %v, want %v", got.(*BoolValue), want.(*BoolValue))
} else { } else {
t.Logf("Both are same boolean (%s)", want.(*BoolValue).String()) t.Logf("Both are same boolean (%s)", want.(*BoolValue).String())
} }
case NumberValueType: case NumberValueType:
if got.(*NumberValue).float64 != want.(*NumberValue).float64 { if got.(*NumberValue).Number != want.(*NumberValue).Number {
t.Errorf("number value mismatch: got %v, want %v", got.(*NumberValue), want.(*NumberValue)) t.Errorf("number value mismatch: got %v, want %v", got.(*NumberValue), want.(*NumberValue))
} else { } else {
t.Logf("Both are same number (%s)", got.(*NumberValue).String()) t.Logf("Both are same number (%s)", got.(*NumberValue).String())
} }
case StringValueType: case StringValueType:
if got.(*StringValue).string != want.(*StringValue).string { if got.(*StringValue).Text != want.(*StringValue).Text {
t.Errorf("string value mismatch: got %v, want %v", got.(*StringValue), want.(*StringValue)) t.Errorf("string value mismatch: got %v, want %v", got.(*StringValue), want.(*StringValue))
} else { } else {
t.Logf("Both are same string (%s)", got.(*StringValue).String()) t.Logf("Both are same string (%s)", got.(*StringValue).String())
@ -60,19 +60,10 @@ func CompareValues(t *testing.T, got Value, want Value) {
t.Errorf("builtin function name mismatch: got %v, want %v", n.Name, m.Name) t.Errorf("builtin function name mismatch: got %v, want %v", n.Name, m.Name)
} }
if len(n.Parameters) != len(m.Parameters) { if !n.Signature.Matches(m.Signature) {
t.Errorf("builtin function parameter count mismatch: got %v, want %v", n.Parameters, m.Parameters) t.Errorf("builtin function parameter count mismatch: got %v, want %v", n, m)
} }
for i, v := range n.Parameters {
if v != m.Parameters[i] {
t.Errorf("builtin function parameter %d mismatch: got %v, want %v", i, v, m.Parameters[i])
}
}
if &n.F != &m.F {
t.Errorf("builtin function f mismatch: got %v, want %v", &n.F, &m.F)
}
case VariableValueType: case VariableValueType:
n := got.(*VariableValue) n := got.(*VariableValue)
m := want.(*VariableValue) m := want.(*VariableValue)
@ -87,6 +78,32 @@ func CompareValues(t *testing.T, got Value, want Value) {
CompareValues(t, n.value, m.value) CompareValues(t, n.value, m.value)
case ListValueType:
n := got.(*ListValue)
m := want.(*ListValue)
if len(n.Items) != len(m.Items) {
t.Fatalf("list items length mismatch: got %d, want %d", len(n.Items), len(m.Items))
}
for i, v := range n.Items {
t.Logf("comparing list items #%d: got %s, want %s", i, v, m.Items[i])
CompareValues(t, v, m.Items[i])
}
case ObjectValueType:
n := got.(*ObjectValue)
m := want.(*ObjectValue)
if len(n.Members) != len(m.Members) {
t.Fatalf("object members count mismatch: got %d, want %d", len(n.Members), len(m.Members))
}
for k, v := range n.Members {
t.Logf("comparing object member %s: got %s, want %s", k, v, m.Members[k])
CompareValues(t, v, m.Members[k])
}
default: default:
panic("unimplemented comparison") panic("unimplemented comparison")
} }

View file

@ -6,6 +6,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"math"
"os"
"strings" "strings"
) )
@ -26,6 +28,8 @@ const (
InstructionMul InstructionMul
// InstructionDiv pop two and divide the second by the first // InstructionDiv pop two and divide the second by the first
InstructionDiv InstructionDiv
// InstructionNegate negate the value; if it was positive, make it negative, and vice versa.
InstructionNegate
// InstructionEquals whether the two top values on the stack are equal // InstructionEquals whether the two top values on the stack are equal
InstructionEquals InstructionEquals
// InstructionNotEqual whether the two top values on the stack are not equal // InstructionNotEqual whether the two top values on the stack are not equal
@ -99,6 +103,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
@ -118,6 +124,8 @@ func (b Bytecode) String() string {
return "MUL" return "MUL"
case InstructionDiv: case InstructionDiv:
return "DIV" return "DIV"
case InstructionNegate:
return "NEGATE"
case InstructionEquals: case InstructionEquals:
return "EQUALS" return "EQUALS"
case InstructionNotEqual: case InstructionNotEqual:
@ -182,6 +190,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"
} }
@ -202,7 +212,7 @@ func (c Chunk) String() string {
b.WriteString("=-= constants =-=\n") b.WriteString("=-= constants =-=\n")
for i, ct := range c.Constants { for i, ct := range c.Constants {
b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct)) b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct.DebugString()))
f, ok := ct.(*FunctionValue) f, ok := ct.(*FunctionValue)
if ok { if ok {
@ -228,6 +238,16 @@ func RegisterGOBTypes() {
Params: nil, Params: nil,
Chunk: nil, Chunk: nil,
}) })
// Signatures
gob.Register(&NilSignature{})
gob.Register(&NumberSignature{})
gob.Register(&StringSignature{})
gob.Register(&FunctionSignature{})
gob.Register(&ListSignature{})
gob.Register(&ObjectSignature{})
gob.Register(&BooleanSignature{})
} }
func (c Chunk) Serialize() []byte { func (c Chunk) Serialize() []byte {
@ -287,38 +307,108 @@ type Call struct {
var DefaultGlobals = map[string]Value{ 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"}, &FunctionSignature{
func(_ *VM, this Value, v map[string]Value) (Value, error) { []TypeSignature{&StringSignature{}},
println(v["value"].String()) &NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
println(v[0].String())
return nil, nil return nil, nil
}, },
nil, nil,
false,
}, },
"print": &BuiltinFunctionValue{ "print": &BuiltinFunctionValue{
"print", "print",
[]string{"value"}, &FunctionSignature{
func(_ *VM, this Value, v map[string]Value) (Value, error) { []TypeSignature{&StringSignature{}},
print(v["value"].String()) &NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
print(v[0].String())
return nil, nil return nil, nil
}, },
nil, nil,
false,
}, },
"format": &BuiltinFunctionValue{ "format": &BuiltinFunctionValue{
"format", "format",
[]string{"format_string", "values"}, &FunctionSignature{
func(vm *VM, value Value, m map[string]Value) (Value, error) { []TypeSignature{
valuies := m["values"].(*ListValue).items &StringSignature{},
&ListSignature{
&AnySignature{},
},
},
&StringSignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
b := strings.Builder{}
template := m[0].(*StringValue).Text
valuies := m[1].(*ListValue).Items
return GoToValue(fmt.Sprintf(m["format_string"].String(), valuies)), nil vi := 0
last := 0
for i := 0; i < len(template); i++ {
if template[i] == '%' {
b.WriteString(template[last:i])
b.WriteString(valuies[vi].String())
vi++
last = i + 1
}
}
b.WriteString(template[last:])
return GoToValue(b.String()), nil
}, },
nil, nil,
true,
},
"char": &BuiltinFunctionValue{
"char",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&StringSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
n := args[0].(*NumberValue).Number
b := byte(n)
return &StringValue{
string([]byte{b}),
}, nil
},
nil,
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",
[]string{"a", "b"}, &FunctionSignature{
func(vm *VM, this Value, params map[string]Value) (Value, error) { []TypeSignature{
a := params["a"] &AnySignature{},
b := params["b"] &AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if !a.Equals(b) { if !a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b)) return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
@ -327,13 +417,20 @@ var DefaultGlobals = map[string]Value{
return &NilValue{}, nil return &NilValue{}, nil
}, },
nil, nil,
false,
}, },
"assertNotEq": &BuiltinFunctionValue{ "assertNotEq": &BuiltinFunctionValue{
"assertNotEq", "assertNotEq",
[]string{"a", "b"}, &FunctionSignature{
func(vm *VM, this Value, params map[string]Value) (Value, error) { []TypeSignature{
a := params["a"] &AnySignature{},
b := params["b"] &AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if a.Equals(b) { if a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b)) return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
@ -342,6 +439,85 @@ var DefaultGlobals = map[string]Value{
return &NilValue{}, nil return &NilValue{}, nil
}, },
nil, nil,
false,
},
"str": &BuiltinFunctionValue{
"str",
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&StringSignature{},
},
func(vm *VM, _ Value, args []Value) (Value, error) {
return GoToValue(args[0].String()), nil
},
nil,
true,
},
"type": &BuiltinFunctionValue{
Name: "type",
Signature: &FunctionSignature{
In: []TypeSignature{&AnySignature{}},
Out: &StringSignature{},
},
F: func(vm *VM, this Value, args []Value) (Value, error) {
v := args[0]
sig := SignatureOf(v)
return GoToValue(sig.String()), nil
},
Constant: true,
},
"exit": &BuiltinFunctionValue{
"exit",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NilSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
os.Exit(int(args[0].(*NumberValue).Number))
return &NilValue{}, nil
},
nil,
false,
},
"floor": &BuiltinFunctionValue{
"floor",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
return &NumberValue{math.Floor(args[0].(*NumberValue).Number)}, nil
},
nil,
true,
},
"ceil": &BuiltinFunctionValue{
"ceil",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
return &NumberValue{math.Ceil(args[0].(*NumberValue).Number)}, nil
},
nil,
true,
},
"roundd": &BuiltinFunctionValue{
"roundd",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}, &NumberSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
x := args[0].(*NumberValue).Number
decimals := args[1].(*NumberValue).Number
multiplier := math.Pow(10, decimals)
return &NumberValue{math.Round(x*multiplier) / multiplier}, nil
},
nil,
true,
}, },
} }
@ -393,29 +569,34 @@ func (vm *VM) Next() bool {
vm.stack.Push(vm.ReadConstant()) vm.stack.Push(vm.ReadConstant())
case InstructionAdd: case InstructionAdd:
r := vm.stack.Pop().(*NumberValue).float64 r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).float64 l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l + r}) vm.stack.Push(&NumberValue{l + r})
case InstructionSub: case InstructionSub:
r := vm.stack.Pop().(*NumberValue).float64 r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).float64 l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l - r}) vm.stack.Push(&NumberValue{l - r})
case InstructionMul: case InstructionMul:
r := vm.stack.Pop().(*NumberValue).float64 r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).float64 l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l * r}) vm.stack.Push(&NumberValue{l * r})
case InstructionDiv: case InstructionDiv:
r := vm.stack.Pop().(*NumberValue).float64 r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).float64 l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l / r}) vm.stack.Push(&NumberValue{l / r})
case InstructionNegate:
v := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{-v})
case InstructionEquals: case InstructionEquals:
vm.stack.Push( vm.stack.Push(
&BoolValue{vm.stack.Pop().Equals(vm.stack.Pop())}, &BoolValue{vm.stack.Pop().Equals(vm.stack.Pop())},
@ -427,40 +608,40 @@ func (vm *VM) Next() bool {
) )
case InstructionNot: case InstructionNot:
b := vm.stack.Pop().(*BoolValue).bool b := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{!b}) vm.stack.Push(&BoolValue{!b})
case InstructionAnd: case InstructionAnd:
r := vm.stack.Pop().(*BoolValue).bool r := vm.stack.Pop().(*BoolValue).Boolean
l := vm.stack.Pop().(*BoolValue).bool l := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{l && r}) vm.stack.Push(&BoolValue{l && r})
case InstructionOr: case InstructionOr:
r := vm.stack.Pop().(*BoolValue).bool r := vm.stack.Pop().(*BoolValue).Boolean
l := vm.stack.Pop().(*BoolValue).bool l := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{l || r}) vm.stack.Push(&BoolValue{l || r})
case InstructionLess: case InstructionLess:
r := vm.stack.Pop().(*NumberValue).float64 r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).float64 l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l < r}) vm.stack.Push(&BoolValue{l < r})
case InstructionLessOrEqual: case InstructionLessOrEqual:
r := vm.stack.Pop().(*NumberValue).float64 r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).float64 l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l <= r}) vm.stack.Push(&BoolValue{l <= r})
case InstructionGreater: case InstructionGreater:
r := vm.stack.Pop().(*NumberValue).float64 r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).float64 l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l > r}) vm.stack.Push(&BoolValue{l > r})
case InstructionGreaterOrEqual: case InstructionGreaterOrEqual:
r := vm.stack.Pop().(*NumberValue).float64 r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).float64 l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l >= r}) vm.stack.Push(&BoolValue{l >= r})
@ -479,7 +660,7 @@ func (vm *VM) Next() bool {
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{
f.Params[i], f.Params[i].Name,
vm.stack.items[p], vm.stack.items[p],
vm.scope, vm.scope,
} }
@ -494,10 +675,10 @@ func (vm *VM) Next() bool {
vm.chunk = f.Chunk vm.chunk = f.Chunk
vm.ip = 0 vm.ip = 0
case *BuiltinFunctionValue: case *BuiltinFunctionValue:
args := map[string]Value{} args := make([]Value, len(f.Signature.In))
for i := len(f.Parameters) - 1; i >= 0; i-- { for i := len(f.Signature.In) - 1; i >= 0; i-- {
args[f.Parameters[i]] = vm.stack.Pop() args[i] = vm.stack.Pop()
} }
v, err := f.F(vm, f.Parent, args) v, err := f.F(vm, f.Parent, args)
@ -519,12 +700,12 @@ func (vm *VM) Next() bool {
case InstructionJumpFalse: case InstructionJumpFalse:
n := vm.NextU16() n := vm.NextU16()
if !vm.stack.Pop().(*BoolValue).bool { if !vm.stack.Pop().(*BoolValue).Boolean {
vm.ip += Pos(n) vm.ip += Pos(n)
} }
case InstructionGetLocal: case InstructionGetLocal:
name := vm.GetConstant(vm.NextByte()).(*StringValue).string name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name) v := vm.getVar(name)
if v == nil { if v == nil {
@ -536,7 +717,7 @@ func (vm *VM) Next() bool {
case InstructionSetLocal: case InstructionSetLocal:
value := vm.stack.Pop().(Value) value := vm.stack.Pop().(Value)
name := vm.GetConstant(vm.NextByte()).(*StringValue).string name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name) v := vm.getVar(name)
@ -544,19 +725,19 @@ func (vm *VM) Next() bool {
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name)) vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
} }
v.value = value v.value = value.Clone()
case InstructionDeclareLocal: case InstructionDeclareLocal:
vm.addVar( vm.addVar(
vm.GetConstant(vm.NextByte()).(*StringValue).string, vm.GetConstant(vm.NextByte()).(*StringValue).Text,
vm.stack.Pop().(Value), vm.stack.Pop().Clone(),
) )
case InstructionGetGlobal: case InstructionGetGlobal:
vm.stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string]) vm.stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text])
case InstructionSetGlobal: case InstructionSetGlobal:
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string] = vm.stack.Pop() vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text] = vm.stack.Pop()
case InstructionTrue: case InstructionTrue:
vm.stack.Push(&BoolValue{true}) vm.stack.Push(&BoolValue{true})
@ -570,20 +751,32 @@ func (vm *VM) Next() bool {
case InstructionFormList: case InstructionFormList:
n := int(vm.NextU16()) n := int(vm.NextU16())
items := make([]Value, n+1) items := make([]Value, n)
for i := 0; i <= n; i++ { for i := n - 1; i >= 0; i-- {
items[n-i] = vm.stack.Pop() items[i] = vm.stack.Pop()
} }
vm.stack.Push(&ListValue{
items,
})
case InstructionNewList: case InstructionNewList:
vm.stack.Push(&ListValue{[]Value{}}) vm.stack.Push(&ListValue{[]Value{}})
case InstructionAppend: case InstructionAppend:
value := vm.stack.Pop() value := vm.stack.Pop()
list := vm.stack.Pop().(*ListValue) list := vm.stack.Pop().(*ListValue)
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()
@ -595,8 +788,8 @@ func (vm *VM) Next() bool {
vm.stack.Push(&StringValue{v.String()}) vm.stack.Push(&StringValue{v.String()})
case InstructionStringConcatenation: case InstructionStringConcatenation:
r := vm.stack.Pop().(*StringValue).string r := vm.stack.Pop().(*StringValue).Text
l := vm.stack.Pop().(*StringValue).string l := vm.stack.Pop().(*StringValue).Text
vm.stack.Push(&StringValue{l + r}) vm.stack.Push(&StringValue{l + r})
@ -645,7 +838,7 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
}) })
for i := 0; i < len(f.Params); i++ { for i := 0; i < len(f.Params); i++ {
vm.addVar(f.Params[i], args[i]) vm.addVar(f.Params[i].Name, args[i])
} }
if f.Parent != nil { if f.Parent != nil {
@ -660,25 +853,21 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
for vm.chunk.Bytecode[vm.ip] != InstructionReturn && vm.Next() { for vm.chunk.Bytecode[vm.ip] != InstructionReturn && vm.Next() {
} }
if vm.HasNext() {
vm.Next() vm.Next()
}
return vm.stack.Pop(), nil return vm.stack.Pop(), nil
case *BuiltinFunctionValue: case *BuiltinFunctionValue:
argies := map[string]Value{} return f.F(vm, f.Parent, args)
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())) return nil, errors.New(fmt.Sprintf("value is not a function (%s)", v.DebugString()))
} }
func (vm *VM) SetChunk(c *Chunk) {
vm.chunk = c
}
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

@ -76,13 +76,13 @@ func TestNewVM(t *testing.T) {
} }
// should have given stack size // should have given stack size
if vm.stack.Size != stackSize { if vm.stack.Capacity != stackSize {
t.Errorf("vm.stack.Size = %d, want %d", vm.stack.Size, stackSize) t.Errorf("vm.stack.Capacity = %d, want %d", vm.stack.Capacity, stackSize)
} }
// should have given call stack size // should have given call stack size
if vm.call.Size != callstackSize { if vm.call.Capacity != callstackSize {
t.Errorf("vm.call.Size = %d, want %d", vm.call.Size, callstackSize) t.Errorf("vm.call.Capacity = %d, want %d", vm.call.Capacity, callstackSize)
} }
} }
@ -442,7 +442,16 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{2}, &NumberValue{2},
&FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
@ -476,7 +485,16 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{2}, &NumberValue{2},
&FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
@ -493,7 +511,12 @@ func GetExecutionTestData() map[string]struct {
}, },
&FunctionValue{ &FunctionValue{
Name: "square", Name: "square",
Params: []string{"n"}, Params: []FunctionParameter{
{
"n",
&NumberSignature{},
},
},
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
@ -514,7 +537,12 @@ func GetExecutionTestData() map[string]struct {
"square", "square",
&FunctionValue{ &FunctionValue{
Name: "square", Name: "square",
Params: []string{"n"}, Params: []FunctionParameter{
{
"n",
&NumberSignature{},
},
},
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
@ -532,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},
},
},
},
},
} }
} }

2
emoji.ang Normal file
View file

@ -0,0 +1,2 @@
write(char(0x12) + char(0x85) + char(0x07))

0
examples/brainfuck.ang Normal file
View file

View file

@ -18,7 +18,7 @@ while n <= terms {
tot = tot * 6 tot = tot * 6
# get the absolute value of a number # get the absolute value of a number
func abs(x) { func abs(x: number) number {
if x < 0 { if x < 0 {
return -x return -x
} }
@ -30,7 +30,7 @@ func abs(x) {
# see: https://en.wikipedia.org/wiki/Newton's_method # see: https://en.wikipedia.org/wiki/Newton's_method
# The required accuracy # The required accuracy
SQRT_ACC := 0.00000001 SQRT_ACC := 0.00000001
func sqrt(x) { func sqrt(x: number) number {
pg := 0 # previous guess pg := 0 # previous guess
g := 1 # current guess g := 1 # current guess
@ -45,4 +45,4 @@ func sqrt(x) {
tot = sqrt(tot) tot = sqrt(tot)
# output the result # output the result
write(tot) write(str(tot))

36
examples/pi-approx.py Normal file
View file

@ -0,0 +1,36 @@
terms = 100000
tot = 0
n = 1
while n <= terms:
tot = tot + 1 / (n*n)
n = n + 1
tot = tot * 6
# get the absolute value of a number
def abs(x):
if x < 0:
return -x
return x
# calculate an approximation of the square root of tot using
# newton's method.
# see: https://en.wikipedia.org/wiki/Newton's_method
# The required accuracy
SQRT_ACC = 0.00000001
def sqrt(x):
pg = 0 # previous guess
g = 1 # current guess
while abs(pg - g) >= SQRT_ACC:
pg = g
g = (pg + tot/pg)/2
return g
tot = sqrt(tot)
# output the result
print(tot)

16
examples/pøck.ang Normal file
View file

@ -0,0 +1,16 @@
import "math.ang"
func r_x(t) {
return 8*(exp(-t) - t)
}
func r_y(t) {
return 5*(exp(-t) - t)
}
func r(t) {
return format("(%s, %s)", [r_x(t), r_y(t)])
}
write(r(1))
write()

View file

@ -1,15 +1,14 @@
# This program computes the fibonacci numbers using recursion (O(2^n))
# fibonacci sequence # It is very slow
func fib(n) { func fib(x: number) number {
if n <= 1 { if x <= 1 {
return n return x
} }
return fib(x - 1) + fib(x - 2)
return fib(n - 1) + fib(n - 2)
} }
n := 0 n := 0
while n < 10 { while n < 100 {
write(fib(n)) write(str(fib(n)))
n = n + 1 n = n + 1
} }

13
examples/solving.ang Normal file
View file

@ -0,0 +1,13 @@
import "../lib/math.ang"
a := 120
b := 10
# find log_b(a)
func f(x: number)number {
return pow(b, x) - a
}
log_b := newtons(f)
write(str(log_b))
write("inverse: "+str(pow(b, log_b))+" = "+str(a))

4
fails.ang Normal file
View file

@ -0,0 +1,4 @@
import "lib/honning.ang"
write(_bell+_italic+"Hello "+_underline+"world "+_strike+"micheal"+_reset)

15
imp.ang Normal file
View file

@ -0,0 +1,15 @@
func is_cool(x: number|string) boolean {
if x == "cool" {
return true
} else if x == 69 {
return true
}
return nil
}
write(str(is_cool("not cool")))
write(str(is_cool("cool")))
write(str(is_cool(0)))
write(str(is_cool(69)))

15
lib/honning.ang Normal file
View file

@ -0,0 +1,15 @@
_bell := char(0x07)
ESC := char(0x1B)
CSI := ESC + "["
_reset := CSI + "0m"
_bold := CSI + "1m"
_faint := CSI + "2m"
_italic := CSI + "3m"
_underline := CSI + "4m"
_slow_blink := CSI + "5m"
_rapid_blink := CSI + "6m"
_strike := CSI + "9m"
_primary_font := CSI + "10m"

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

@ -7,7 +7,7 @@ E := 2.718281828459045235360287471352
# Get the absolute value of a number. If x is negative, the returned # Get the absolute value of a number. If x is negative, the returned
# value is positive and equal to `-x`. If x is positive or zero, the # value is positive and equal to `-x`. If x is positive or zero, the
# returned value is x. # returned value is x.
func abs(x) { func abs(x: number) number {
# if the number is negative # if the number is negative
if x < 0 { if x < 0 {
# negate it so it's positive # negate it so it's positive
@ -17,13 +17,31 @@ func abs(x) {
return x return x
} }
DERIVE_DX := 0.00000001
func derive(f: func(number)number, x: number) number {
return (f(x + DERIVE_DX) - f(x))/DERIVE_DX
}
NEWTONS_ACC := 0.000000000001
func newtons(f: func(number)number) number {
pg := 0
g := 1
while abs(g - pg) > NEWTONS_ACC {
pg = g
g = pg - f(pg) / derive(f, pg)
}
return g
}
MAX_SQRT_DX := 0.0000001 MAX_SQRT_DX := 0.0000001
# sqrt(x) # sqrt(x)
# x: number # x: number
# Calculate the approximate square root using newton's method until # Calculate the approximate square root using newton's method until
# the accuracy has increased by less than the variable `MAX_SQRT_DX`. # the accuracy has increased by less than the variable `MAX_SQRT_DX`.
func sqrt(x) { func sqrt(x: number) number {
ng := x ng := x
g := 1 g := 1
@ -42,23 +60,19 @@ func sqrt(x) {
# Return the whole number part of the number. if x is a whole number, # Return the whole number part of the number. if x is a whole number,
# the returned value is x. If x is not a whole number, the closest # the returned value is x. If x is not a whole number, the closest
# whole number which is less than or equal to x is returned. # whole number which is less than or equal to x is returned.
func floor(x) {
# todo
}
# ceil(x) # ceil(x)
# x: number # x: number
# Return the whole number part of the number. if x is a whole number, # Return the whole number part of the number. if x is a whole number,
# the returned value is x. If x is not a whole number, the closest # the returned value is x. If x is not a whole number, the closest
# whole number which is greater than or equal to x is returned. # whole number which is greater than or equal to x is returned.
func ceil(x) {
# todo
}
# round(x) # round(x)
# x: number # x: number
# Return the closest whole number to the value x. # Return the closest whole number to the value x.
func round(x) { func round(x: number) number {
f := floor(x) f := floor(x)
if x - f > 0.5 { if x - f > 0.5 {
@ -68,15 +82,128 @@ func round(x) {
return f return f
} }
# mod(x, n)
# x: number; any number
# n: number; the number to divide by
# Return the rest from a division of x by n.
func mod(x: number, n: number) number {
if x == 0 {
return 0
}
if x < 0 {
while x + n <= 0 {
x = x + n
}
} else {
while x - n >= 0 {
x = x - n
}
}
return x
}
# sm_exp(x)
# x: number; any number between 0 and 1
# Get an approximate value of e raised to the power of x.
# This value is only reasonable if 0<x<1.
# It is approximated using the taylor series of e**x.
SM_EXP_ACC := 0.00000000001
func sm_exp(x: number) number {
p_tot := 0
tot := 1
n := 1
x_pow := x
f := 1
while abs(tot - p_tot) > SM_EXP_ACC {
p_tot = tot
t := x_pow / f
tot = tot + t
f = f * (n+1)
x_pow = x_pow * x
n = n + 1
}
return tot
}
# exp(x)
# x: number; any number
# Get an approximate value of e raised to the power of x.
func exp(x: number) number {
n := abs(x)
tot := 1
while n >= 1 {
tot = tot * E
n = n - 1
}
if n > 0 {
tot = tot * sm_exp(n)
}
if x < 0 {
return 1/tot
} else {
return tot
}
}
# ln(x)
# x: number; any number
# Get the approximate value of the natural logarithm
# This function uses newton's method to approximate.
LN_ACC := 0.0000000001
func ln(x: number) number {
pg := 0
g := 1
while abs(pg - g) > LN_ACC {
pg = g
g = pg + x / exp(pg) - 1
}
return g
}
# pow(x, p)
# x: number; any number. The base
# p: number; the value of the exponent
# Raise any number to any power (x^p)
func pow(x: number, p: number) number {
return exp(p*ln(x))
}
# log(x, b)
# x: number; any number greater than 0
# b: number; any number as the base
# Calculate the approximate value of the logarithm
# of a with b as base.
LOG_ACC := 0.0000001
func log(a: number, b: number) number {
ln_b := ln(b)
pg := 0
g := 1
while abs(g - pg) > LOG_ACC {
pg = g
g = pg - 1/ln_b - a/(ln_b*pow(b, pg))
}
return g
}
# sin(x) # sin(x)
# x: number; an angle in radians # x: number; an angle in radians
# Get the sine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine # Get the sine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine
# TODO: use hashmap with precomputed values and linear interpolation # TODO: use hashmap with precomputed values and linear interpolation
func sin(x) { func sin(x: number) number {
f := 1 f := 1
x = mod(x, 2*PI) x = mod(x, 2*PI)
if x > PI { if x > PI {
x = -x x = PI - x
f = -1 f = -1
} }
@ -101,106 +228,13 @@ func sin(x) {
# cos(x) # cos(x)
# x: number; an angle in radians # x: number; an angle in radians
# Get the cosine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine # Get the cosine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine
func cos(x) { func cos(x: number) number {
# todo # todo
} }
# tan(x) # tan(x)
# x: number; an angle in radians # x: number; an angle in radians
# Get the tangent of an angle. https://en.wikipedia.org/wiki/Tangent # Get the tangent of an angle. https://en.wikipedia.org/wiki/Tangent
func tan(x) { func tan(x: number) number {
# todo # todo
} }
# mod(x, n)
# x: number; any number
# n: number; the number to divide by
# Return the rest from a division of x by n.
func mod(x, n) {
if x == 0 {
return 0
}
if x < 0 {
while x + n <= 0 {
x = x + n
}
} else {
while x - n >= 0 {
x = x - n
}
}
return x
}
# ln(x)
# x: number; any number
# Get the approximate value of the natural logarithm
# This function uses newton's method to approximate.
LN_ACC := 0.000000001
func ln(x) {
pg := 0
g := 1
while abs(pg - g) > LN_ACC {
pg = g
g = pg + x / exp(pg) - 1
}
return g
}
# sm_exp(x)
# x: number; any number between 0 and 1
# Get an approximate value of e raised to the power of x.
# This value is only reasonable if 0<x<1.
# It is approximated using the taylor series of e**x.
SM_EXP_ACC := 0.00000000001
func sm_exp(x) {
p_tot := 0
tot := 1
n := 1
x_pow := x
f := 1
while abs(tot - p_tot) > SM_EXP_ACC {
p_tot = tot
t := x_pow / f
tot = tot + t
f = f * (n+1)
x_pow = x_pow * x
n = n + 1
}
return tot
}
# exp(x)
# x: number; any number
# Get an approximate value of e raised to the power of x.
func exp(x) {
n := abs(x)
tot := 1
while n >= 1 {
tot = tot * E
n = n - 1
}
if n > 0 {
tot = tot * sm_exp(n)
}
if x < 0 {
return 1/tot
} else {
return tot
}
}
# pow(x, p)
# x: number; any number. The base
# p: number; the value of the exponent
# Raise any number to any power (x^p)
func pow(x, p) {
return exp(p*ln(x))
}

25
lib/testing.ang Normal file
View file

@ -0,0 +1,25 @@
NAMESPACE := ""
func namespace(name: string, test: func()) {
NAMESPACE = name
test()
}
func assertEqual(a: any, b: any) {
if a != b {
write(format("assertion error: % should (but doesn't) equal %", [a, b]))
exit(1)
} else if env("DEBUG") != "" {
write(format("assertion success: % equals %", [a, b]))
}
}
func assertNotEqual(a: any, b: any) {
if a == b {
write(format("assertion error: % shouldn't (but does) equal %", [a, b]))
exit(1)
} else if env("DEBUG") != "" {
write(format("assertion success: % doesn't equal %", [a, b]))
}
}

4
lib/util.ang Normal file
View file

@ -0,0 +1,4 @@
func memoize(f) {
}

View file

@ -1,22 +1,46 @@
#!/bin/zsh #!/bin/bash
echo '== Building CLI ==' echo '=== Building CLI ==='
cd cli cd cli || exit 1
go build . if ! go build .; then
echo "=== Had error building CLI ==="
exit 1
else
echo "=+= Successfully built CLI =+="
fi
cd .. cd ..
echo "=== Running go core tests ==="
cd core || exit 1
if ! go test .; then
echo "=x= Core testing failed =x= "
exit 1
else
echo "=+= Successfully ran core tests =+="
fi
cd ..
echo '== Testing anglais ==' errors=()
for file in ./tests/*.ang; do echo '=== Testing anglais ==='
echo "-- Test-running file $file --"
# read files
for file in $(find tests -type f); do
echo "-v- Test-running file $file -v-"
if ! ./cli/cli run "$file"; then if ! ./cli/cli run "$file"; then
echo "-- Error --" echo "-x- Error -x-"
exit 1 errors+=("$file")
else else
echo "-- Success --" echo "-+- Success -+-"
fi fi
done done
echo '== Successfully ran all tests =='
if [ 0 -ne "$(wc -w <<< "${errors[@]}")" ]; then
echo "=x= Errors occured while executing =x="
echo "erroring files: $(printf '%s ' "${errors[@]}")"
exit 1
else
echo '=+= Successfully ran all tests =+='
fi

View file

@ -3,9 +3,8 @@ assertEq(1, 1)
assertEq(0, 0) assertEq(0, 0)
assertEq("", "") assertEq("", "")
assertEq([], []) assertEq([]number, []number)
assertEq([3, 1, 4, 1], [3, 1, 4, 1]) assertEq([3, 1, 4, 1], [3, 1, 4, 1])
assertEq([true, 1024, nil, "Hello world!"], [true, 1024, nil, "Hello world!"])
# Inequality # Inequality
assertNotEq(2, 3) assertNotEq(2, 3)

3
tests/hex.ang Normal file
View file

@ -0,0 +1,3 @@
assertEq(0x00, 0)
assertEq(0xFF, 255)

View file

@ -1,20 +1,20 @@
list := [] list := []number
x := 1 x := 1
while x <= 1000 { while x <= 1000 {
list.append(x) list.append(x)
assertEq(list.reduce(func(tot, a){ assertEq(list.reduce(func(tot: number, a: number) number {
return tot + a return tot + a
}, 0), x*(x + 1)/2) }, 0), x*(x + 1)/2)
x = x + 1 x = x + 1
} }
func sum(a, b) { func sum(a: number, b: number) number {
return a + b return a + b
} }
list = [] list = []number
x = 1 x = 1
while x <= 100 { while x <= 100 {
list.append(2*x - 1) list.append(2*x - 1)
@ -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

@ -2,11 +2,10 @@
fibonacci_numbers := [ fibonacci_numbers := [
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657,
46368, 75025, 121393, 196418, 317811, 514229, 832040, 46368, 75025, 121393, 196418, 317811, 514229, 832040
1346269, 2178309, 3524578, 5702887, 9227465, 14930352
] ]
func fib(n) { func fib(n: number) number {
if n < 2 { if n < 2 {
return n return n
} }
@ -16,11 +15,12 @@ func fib(n) {
n := 0 n := 0
while n < fibonacci_numbers.length() { while n < fibonacci_numbers.length() {
print("_") print("-")
n = n + 1 n = n + 1
} }
write("") # return to start of line (with carriage return \r)
print(char(0x0D))
x := 0 x := 0
while x < fibonacci_numbers.length() { while x < fibonacci_numbers.length() {

13
tests/refs.ang Normal file
View file

@ -0,0 +1,13 @@
list := []number
list.append(1)
list.append(2)
assertEq(list, [1, 2])
other := list
other.append(3)
assertEq(other, [1, 2, 3])
assertEq(list, [1, 2])

View file

@ -4,9 +4,12 @@ a := 2
{ {
a := 3 a := 3
assertEq(a, 3) assertEq(a, 3)
breakpoint
a = 4 a = 4
assertEq(a, 4) assertEq(a, 4)
breakpoint
} }
assertEq(a, 2) assertEq(a, 2)
breakpoint

View file

@ -1,5 +1,5 @@
func sum(a, b) { func sum(a: number, b: number) number {
return a + b return a + b
} }

8
tests/types.ang Normal file
View file

@ -0,0 +1,8 @@
assertEq(type(1), "number")
assertEq(type("Hello"), "string")
assertEq(type(true), "boolean")
# lists
assertEq(type(["Hello", "world"]), "list[string]")
assertEq(type([0, 1]), "list[number]")

View file

@ -13,32 +13,22 @@ type JsResolver struct {
jsResolver js.Value jsResolver js.Value
} }
func (r *JsResolver) Resolve(name string) (core.Node, error) { func (r *JsResolver) IsSame(a, b string) bool {
return a == b
}
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{} {
@ -55,7 +45,8 @@ func jsErrorOfString(err string) interface{} {
func run(_ js.Value, args []js.Value) interface{} { func run(_ js.Value, args []js.Value) interface{} {
source := args[0].String() source := args[0].String()
outputHandler := args[1] outputHandler := args[1]
resolver := args[2] errorHandler := args[2]
resolver := args[3]
log.Printf("got source: %s", source) log.Printf("got source: %s", source)
lexer := core.NewLexer(source) lexer := core.NewLexer(source)
@ -67,17 +58,25 @@ 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(source)
if err != nil { if err != nil {
return jsErrorOfString(err.Error()) var e core.FormatedError
if errors.As(err, &e) {
errorHandler.Invoke(e.Format())
return nil
}
errorHandler.Invoke(err.Error())
return nil
} }
log.Printf("Parsed tree: %s", tree.String()) log.Printf("Parsed tree: %s", tree.String())
compiler := core.NewCompiler() compiler := core.NewCompiler([]rune(source))
log.Println("Set imports resolver")
compiler.SetImportsResolver(&JsResolver{ compiler.SetImportsResolver(&JsResolver{
resolver, resolver,
@ -91,6 +90,13 @@ func run(_ js.Value, args []js.Value) interface{} {
err = compiler.Compile(tree) err = compiler.Compile(tree)
if err != nil { if err != nil {
var e core.CompilerError
if errors.As(err, &e) {
errorHandler.Invoke(e.Format())
return nil
}
errorHandler.Invoke(err.Error())
return nil return nil
} }
@ -101,19 +107,27 @@ func run(_ js.Value, args []js.Value) interface{} {
// overwrite output // overwrite output
vm.SetGlobal("write", &core.BuiltinFunctionValue{ vm.SetGlobal("write", &core.BuiltinFunctionValue{
Name: "write", Name: "write",
Parameters: []string{"value"}, Signature: &core.FunctionSignature{
F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) { In: []core.TypeSignature{&core.StringSignature{}},
log.Printf("Writing value: %s", v["value"].String()) Out: &core.NilSignature{},
outputHandler.Invoke(js.ValueOf(v["value"].String() + "\n")) },
F: func(vm *core.VM, this core.Value, args []core.Value) (core.Value, error) {
s := args[0].String()
log.Printf("Writing value: %s", s)
outputHandler.Invoke(js.ValueOf(s + "\n"))
return nil, nil return nil, nil
}, },
}) })
vm.SetGlobal("print", &core.BuiltinFunctionValue{ vm.SetGlobal("print", &core.BuiltinFunctionValue{
Name: "print", Name: "print",
Parameters: []string{"value"}, Signature: &core.FunctionSignature{
F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) { In: []core.TypeSignature{&core.StringSignature{}},
log.Printf("Printing value: %s", v["value"].String()) Out: &core.NilSignature{},
outputHandler.Invoke(js.ValueOf(v["value"].String())) },
F: func(vm *core.VM, this core.Value, args []core.Value) (core.Value, error) {
s := args[0].String()
log.Printf("Printing value: %s", s)
outputHandler.Invoke(js.ValueOf(s))
return nil, nil return nil, nil
}, },
}) })