main #1
42 changed files with 3635 additions and 904 deletions
40
bad.ang
Normal file
40
bad.ang
Normal 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
15
chars.ang
Normal 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
|
||||
}
|
||||
}
|
||||
209
cli/main.go
209
cli/main.go
|
|
@ -1,10 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"github.com/alecthomas/kong"
|
||||
"log"
|
||||
"neemek.com/anglais/core"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
|
@ -13,6 +16,7 @@ type Context 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"`
|
||||
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
|
||||
}
|
||||
|
|
@ -22,45 +26,34 @@ type WorkingDirectoryResolver struct {
|
|||
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)
|
||||
f, err := os.ReadFile(pth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
str := string(f)
|
||||
|
||||
l := core.NewLexer(str)
|
||||
|
||||
tokens, err := l.Tokenize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return string(f), nil
|
||||
}
|
||||
|
||||
p := core.NewParser(tokens)
|
||||
func (r *WorkingDirectoryResolver) IsSame(a, b string) bool {
|
||||
apath := filepath.Clean(filepath.Join(r.workingDirectory, a))
|
||||
bpath := filepath.Clean(filepath.Join(r.workingDirectory, b))
|
||||
|
||||
tree, err := p.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return apath == bpath
|
||||
}
|
||||
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
func (cmd *RunCmd) Run(ctx *Context) error {
|
||||
func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, error) {
|
||||
if ctx.Debug {
|
||||
log.Println("Reading file")
|
||||
}
|
||||
|
||||
f, err := os.ReadFile(cmd.File)
|
||||
f, err := os.ReadFile(fpath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var chunk *core.Chunk
|
||||
if !cmd.Bytecode {
|
||||
src := string(f)
|
||||
|
||||
if ctx.Debug {
|
||||
|
|
@ -74,41 +67,50 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
|||
tokens, err := l.Tokenize()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(tokens) <= 1 {
|
||||
log.Fatal("Empty file")
|
||||
return nil, errors.New("empty file")
|
||||
}
|
||||
|
||||
if ctx.Debug {
|
||||
log.Printf("Lexed %d tokens", len(tokens))
|
||||
|
||||
}
|
||||
p := core.NewParser(tokens)
|
||||
p := core.NewParser(src, tokens)
|
||||
|
||||
if ctx.Debug {
|
||||
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 err != nil {
|
||||
print(err.(*core.ParsingError).Format([]rune(src)))
|
||||
print(err.(core.ParsingError).Format())
|
||||
log.Fatal("Parsing had errors")
|
||||
}
|
||||
|
||||
if ctx.Debug {
|
||||
log.Println("Initialized compiler")
|
||||
}
|
||||
c := core.NewCompiler()
|
||||
c := core.NewCompiler([]rune(src))
|
||||
|
||||
if ctx.Debug {
|
||||
log.Println("Setting imports resolver")
|
||||
}
|
||||
|
||||
dir, _ := filepath.Split(cmd.File)
|
||||
dir, _ := path.Split(fpath)
|
||||
c.SetImportsResolver(&WorkingDirectoryResolver{
|
||||
dir,
|
||||
})
|
||||
|
|
@ -117,12 +119,44 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
|||
log.Println("Compiling parse tree")
|
||||
}
|
||||
err = c.Compile(tree)
|
||||
if err != nil {
|
||||
var e core.FormatedError
|
||||
if errors.As(err, &e) {
|
||||
log.Fatal(e.Format())
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// if there were non-critical warnings, report them
|
||||
if !ignoreWarnings && len(c.Warnings) != 0 {
|
||||
for _, warning := range c.Warnings {
|
||||
log.Println(warning.Format())
|
||||
}
|
||||
log.Fatal("compiler reported warning(s) (ignore warnings with the --ignore-warnings option)")
|
||||
}
|
||||
|
||||
return c.Chunk, nil
|
||||
}
|
||||
|
||||
func (cmd *RunCmd) Run(ctx *Context) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
chunk = c.Chunk
|
||||
} else {
|
||||
if ctx.Debug {
|
||||
log.Println("Registering GOB types")
|
||||
}
|
||||
|
|
@ -159,70 +193,11 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
|||
type CompileCmd struct {
|
||||
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"`
|
||||
IgnoreWarnings bool `name:"ignore-warnings" help:"Ignore warning messages"`
|
||||
}
|
||||
|
||||
func (cmd *CompileCmd) Run(ctx *Context) error {
|
||||
if ctx.Debug {
|
||||
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)
|
||||
c, err := makeChunk(ctx, cmd.File, cmd.IgnoreWarnings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -237,7 +212,7 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
|
|||
log.Println("Serializing chunk")
|
||||
}
|
||||
|
||||
serialized := c.Chunk.Serialize()
|
||||
serialized := c.Serialize()
|
||||
|
||||
if ctx.Debug {
|
||||
log.Println("Writing file")
|
||||
|
|
@ -252,11 +227,59 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
|
|||
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 {
|
||||
Debug bool `short:"D" name:"debug" help:"Enable debug mode."`
|
||||
|
||||
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() {
|
||||
|
|
|
|||
24
codegen.ang
Normal file
24
codegen.ang
Normal 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
|
||||
}
|
||||
|
||||
|
|
@ -22,13 +22,22 @@ func GetAllTestCases() map[string]AllTestCase {
|
|||
},
|
||||
},
|
||||
"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{
|
||||
&VariableValue{
|
||||
"sum",
|
||||
&FunctionValue{
|
||||
Name: "sum",
|
||||
Params: []string{"a", "b"},
|
||||
Params: []FunctionParameter{
|
||||
{
|
||||
"a",
|
||||
&NumberSignature{},
|
||||
},
|
||||
{
|
||||
"b",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
Chunk: &Chunk{
|
||||
Bytecode: []Bytecode{
|
||||
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")
|
||||
p := NewParser(tokens)
|
||||
p := NewParser(tc.src, tokens)
|
||||
|
||||
t.Log("Parsing tokens")
|
||||
tree, err := p.Parse()
|
||||
tree, err := p.Parse(tc.src)
|
||||
|
||||
if err != nil {
|
||||
print(err.(*ParsingError).Format([]rune(tc.src)))
|
||||
print(err.(ParsingError).Format())
|
||||
t.Fatalf("parser had an error")
|
||||
}
|
||||
|
||||
t.Log("Initializing compiler")
|
||||
c := NewCompiler()
|
||||
c := NewCompiler([]rune(tc.src))
|
||||
|
||||
t.Log("Compiling parse tree")
|
||||
err = c.Compile(tree)
|
||||
|
|
@ -107,10 +173,10 @@ func BenchmarkAll(b *testing.B) {
|
|||
l := NewLexer(tc.src)
|
||||
tokens, _ := l.Tokenize()
|
||||
|
||||
p := NewParser(tokens)
|
||||
tree, _ := p.Parse()
|
||||
p := NewParser(tc.src, tokens)
|
||||
tree, _ := p.Parse(tc.src)
|
||||
|
||||
c := NewCompiler()
|
||||
c := NewCompiler([]rune(tc.src))
|
||||
_ = c.Compile(tree)
|
||||
|
||||
vm := NewVM(c.Chunk, 256, 256)
|
||||
|
|
|
|||
838
core/compiler.go
838
core/compiler.go
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
func TestNewCompiler(t *testing.T) {
|
||||
c := NewCompiler()
|
||||
c := NewCompiler([]rune{})
|
||||
|
||||
if c == nil {
|
||||
t.Fatal("NewCompiler returned nil")
|
||||
|
|
@ -23,39 +23,63 @@ func TestNewCompiler(t *testing.T) {
|
|||
|
||||
func BenchmarkNewCompiler(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewCompiler()
|
||||
_ = NewCompiler([]rune{})
|
||||
}
|
||||
}
|
||||
|
||||
type CompileTestData struct {
|
||||
tree Node
|
||||
program *Program
|
||||
expectedStack []Value
|
||||
}
|
||||
|
||||
func GetCompileTestData() map[string]CompileTestData {
|
||||
return map[string]CompileTestData{
|
||||
"constant_string": {
|
||||
&Program{
|
||||
[]string{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"a",
|
||||
&StringNode{
|
||||
"Hello world!",
|
||||
"\"Hello world!\"",
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
"",
|
||||
},
|
||||
[]Value{
|
||||
&VariableValue{
|
||||
"a",
|
||||
&StringValue{"Hello world!"},
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"conditional_false": {
|
||||
&Program{
|
||||
[]string{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"a",
|
||||
&NumberNode{
|
||||
0,
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&ConditionalNode{
|
||||
&BooleanNode{
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -63,14 +87,21 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
"a",
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
nil,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
"",
|
||||
},
|
||||
[]Value{
|
||||
&VariableValue{
|
||||
|
|
@ -81,18 +112,23 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
},
|
||||
},
|
||||
"conditional_true": {
|
||||
&Program{
|
||||
[]string{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"a",
|
||||
&NumberNode{
|
||||
0,
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&ConditionalNode{
|
||||
&BooleanNode{
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -100,14 +136,21 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
"a",
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
nil,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
"",
|
||||
},
|
||||
[]Value{
|
||||
&VariableValue{
|
||||
|
|
@ -118,18 +161,23 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
},
|
||||
},
|
||||
"conditional_else_false": {
|
||||
&Program{
|
||||
[]string{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"a",
|
||||
&NumberNode{
|
||||
0,
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&ConditionalNode{
|
||||
&BooleanNode{
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -137,10 +185,13 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
"a",
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -148,13 +199,20 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
"a",
|
||||
&NumberNode{
|
||||
2,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
"",
|
||||
},
|
||||
[]Value{
|
||||
&VariableValue{
|
||||
|
|
@ -165,18 +223,23 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
},
|
||||
},
|
||||
"conditional_else_true": {
|
||||
&Program{
|
||||
[]string{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"a",
|
||||
&NumberNode{
|
||||
0,
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&ConditionalNode{
|
||||
&BooleanNode{
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -184,10 +247,13 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
"a",
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -195,13 +261,20 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
"a",
|
||||
&NumberNode{
|
||||
2,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
"",
|
||||
},
|
||||
[]Value{
|
||||
&VariableValue{
|
||||
|
|
@ -212,41 +285,89 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
},
|
||||
},
|
||||
"addition": {
|
||||
&Program{
|
||||
[]string{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"a",
|
||||
&BinaryNode{
|
||||
BinaryAddition,
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
2,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
"",
|
||||
},
|
||||
[]Value{
|
||||
&VariableValue{
|
||||
"a",
|
||||
&NumberValue{3},
|
||||
0,
|
||||
},
|
||||
},
|
||||
"sum_function": {&BlockNode{
|
||||
},
|
||||
"sum_function": {
|
||||
&Program{
|
||||
[]string{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"sum",
|
||||
&FunctionNode{
|
||||
"sum",
|
||||
[]string{"a", "b"},
|
||||
[]FunctionParameter{
|
||||
{
|
||||
"a",
|
||||
&NumberSignature{},
|
||||
},
|
||||
{
|
||||
"b",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
&NumberSignature{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&ReturnNode{
|
||||
&BinaryNode{
|
||||
BinaryAddition,
|
||||
&ReferenceNode{"a"},
|
||||
&ReferenceNode{"b"},
|
||||
},
|
||||
&ReferenceNode{
|
||||
"a",
|
||||
0, 0,
|
||||
},
|
||||
&ReferenceNode{
|
||||
"b",
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
"",
|
||||
},
|
||||
[]Value{
|
||||
&VariableValue{
|
||||
|
|
@ -254,7 +375,17 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
|
||||
&FunctionValue{
|
||||
"sum",
|
||||
[]string{"a", "b"},
|
||||
[]FunctionParameter{
|
||||
{
|
||||
"a",
|
||||
&NumberSignature{},
|
||||
},
|
||||
{
|
||||
"b",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
&NumberSignature{},
|
||||
NewChunk(
|
||||
[]Bytecode{
|
||||
InstructionDescend,
|
||||
|
|
@ -275,43 +406,63 @@ func GetCompileTestData() map[string]CompileTestData {
|
|||
},
|
||||
},
|
||||
"remove_func_vars": {
|
||||
&Program{
|
||||
[]string{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"a",
|
||||
&FunctionNode{
|
||||
"a",
|
||||
[]string{},
|
||||
[]FunctionParameter{},
|
||||
&NumberSignature{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&AssignNode{
|
||||
"b",
|
||||
&NumberNode{1},
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&ReturnNode{
|
||||
&ReferenceNode{"b"},
|
||||
&ReferenceNode{
|
||||
"b",
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&CallNode{
|
||||
&ReferenceNode{
|
||||
"a",
|
||||
0, 0,
|
||||
},
|
||||
[]Node{},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
"",
|
||||
},
|
||||
[]Value{
|
||||
&VariableValue{
|
||||
"a",
|
||||
&FunctionValue{
|
||||
"a",
|
||||
[]string{},
|
||||
[]FunctionParameter{},
|
||||
&NumberSignature{},
|
||||
NewChunk(
|
||||
[]Bytecode{
|
||||
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 =-=")
|
||||
|
||||
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)
|
||||
if ok {
|
||||
|
|
@ -360,10 +563,10 @@ func TestCompile(t *testing.T) {
|
|||
for name, testCase := range data {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Log("Initializing compiler")
|
||||
c := NewCompiler()
|
||||
c := NewCompiler([]rune(testCase.program.String()))
|
||||
|
||||
t.Log("Compiling node tree")
|
||||
err := c.Compile(testCase.tree)
|
||||
err := c.Compile(testCase.program)
|
||||
if err != nil {
|
||||
t.Fatalf("Compiling failed: %v", err)
|
||||
}
|
||||
|
|
@ -389,8 +592,8 @@ func BenchmarkCompile(b *testing.B) {
|
|||
for name, testCase := range data {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
c := NewCompiler()
|
||||
_ = c.Compile(testCase.tree)
|
||||
c := NewCompiler([]rune{})
|
||||
_ = c.Compile(testCase.program)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -399,7 +602,7 @@ func BenchmarkCompile(b *testing.B) {
|
|||
func TestCompiler_AddU16(t *testing.T) {
|
||||
for i := 0; i <= 0xffff; i++ {
|
||||
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||
c := NewCompiler()
|
||||
c := NewCompiler([]rune{})
|
||||
c.addU16(uint16(i))
|
||||
|
||||
if c.Chunk.Bytecode[0] != Bytecode(i>>8) {
|
||||
|
|
@ -417,24 +620,9 @@ func TestCompiler_CleanStack(t *testing.T) {
|
|||
cases := GetCompileTestData()
|
||||
|
||||
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) {
|
||||
c := NewCompiler()
|
||||
err := c.Compile(tc.tree)
|
||||
c := NewCompiler([]rune(tc.program.String()))
|
||||
err := c.Compile(tc.program)
|
||||
if err != nil {
|
||||
t.Fatalf("Compiling failed: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ const (
|
|||
TokenSemicolon
|
||||
|
||||
TokenNumber
|
||||
TokenHexadecimal
|
||||
TokenString
|
||||
TokenName
|
||||
|
||||
|
|
@ -53,6 +54,7 @@ const (
|
|||
|
||||
TokenComma
|
||||
TokenDot
|
||||
TokenColon
|
||||
|
||||
TokenAssign
|
||||
TokenDeclare
|
||||
|
|
@ -64,6 +66,7 @@ const (
|
|||
TokenLessThanOrEqual
|
||||
|
||||
TokenDoubleAmpersand
|
||||
TokenPipe
|
||||
TokenDoublePipe
|
||||
|
||||
TokenBreakpoint
|
||||
|
|
@ -153,6 +156,12 @@ func (t TokenType) String() string {
|
|||
return "close bracket"
|
||||
case TokenImport:
|
||||
return "import"
|
||||
case TokenColon:
|
||||
return "colon"
|
||||
case TokenPipe:
|
||||
return "pipe"
|
||||
case TokenHexadecimal:
|
||||
return "hexadecimal"
|
||||
}
|
||||
|
||||
return "UNDEFINED TOKENTYPE STRING CONVERSION"
|
||||
|
|
@ -234,11 +243,11 @@ func (l *Lexer) NextToken() (Token, error) {
|
|||
case '.':
|
||||
return l.makeToken(TokenDot), nil
|
||||
case ':':
|
||||
if !l.accept('=') {
|
||||
return l.makeToken(TokenError), errors.New("malformed token (got ':', expected '=' to follow)")
|
||||
if l.accept('=') {
|
||||
return l.makeToken(TokenDeclare), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenDeclare), nil
|
||||
return l.makeToken(TokenColon), nil
|
||||
case '!':
|
||||
if l.accept('=') {
|
||||
return l.makeToken(TokenBangEquals), nil
|
||||
|
|
@ -276,7 +285,7 @@ func (l *Lexer) NextToken() (Token, error) {
|
|||
return l.makeToken(TokenDoublePipe), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenError), errors.New("malformed token (got '|', expected '|' to follow)")
|
||||
return l.makeToken(TokenPipe), nil
|
||||
|
||||
case '"':
|
||||
// include ending quote
|
||||
|
|
@ -327,6 +336,19 @@ func (l *Lexer) NextToken() (Token, error) {
|
|||
default:
|
||||
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) {
|
||||
for unicode.IsDigit(l.peek()) {
|
||||
l.advance()
|
||||
|
|
|
|||
227
core/nodes.go
227
core/nodes.go
|
|
@ -11,6 +11,8 @@ type NodeType int
|
|||
type Node interface {
|
||||
Type() NodeType
|
||||
String() string
|
||||
|
||||
Bounds() (Pos, Pos)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -21,6 +23,7 @@ const (
|
|||
NilNodeType
|
||||
ListNodeType
|
||||
BinaryNodeType
|
||||
UnaryNodeType
|
||||
BlockNodeType
|
||||
ConditionalNodeType
|
||||
LoopNodeType
|
||||
|
|
@ -29,7 +32,6 @@ const (
|
|||
FunctionNodeType
|
||||
ReturnNodeType
|
||||
AccessNodeType
|
||||
ImportNodeType
|
||||
BreakpointNodeType
|
||||
)
|
||||
|
||||
|
|
@ -67,8 +69,8 @@ func (n NodeType) String() string {
|
|||
return "Access"
|
||||
case BreakpointNodeType:
|
||||
return "Breakpoint"
|
||||
case ImportNodeType:
|
||||
return "Import"
|
||||
case UnaryNodeType:
|
||||
return "Unary"
|
||||
}
|
||||
return "Invalid Node Type"
|
||||
}
|
||||
|
|
@ -76,6 +78,9 @@ func (n NodeType) String() string {
|
|||
// ReferenceNode a reference to a variable on the stack
|
||||
type ReferenceNode struct {
|
||||
name string
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n ReferenceNode) Type() NodeType {
|
||||
|
|
@ -86,10 +91,17 @@ func (n ReferenceNode) String() string {
|
|||
return n.name
|
||||
}
|
||||
|
||||
func (n ReferenceNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// StringNode string/text values
|
||||
type StringNode struct {
|
||||
value string
|
||||
quoted string
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n StringNode) Type() NodeType {
|
||||
|
|
@ -100,8 +112,15 @@ func (n StringNode) String() string {
|
|||
return n.quoted
|
||||
}
|
||||
|
||||
func (n StringNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
type NumberNode struct {
|
||||
value float64
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n NumberNode) Type() NodeType {
|
||||
|
|
@ -112,9 +131,17 @@ func (n NumberNode) String() string {
|
|||
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)
|
||||
type ListNode struct {
|
||||
items []Node
|
||||
content TypeSignature
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n ListNode) Type() NodeType {
|
||||
|
|
@ -125,18 +152,25 @@ func (n ListNode) String() string {
|
|||
sb := strings.Builder{}
|
||||
sb.WriteString("[")
|
||||
for i, item := range n.items {
|
||||
sb.WriteString(item.String())
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(item.String())
|
||||
}
|
||||
sb.WriteString("]")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (n ListNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
type AccessNode struct {
|
||||
source Node
|
||||
property string
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n AccessNode) Type() NodeType {
|
||||
|
|
@ -147,6 +181,10 @@ func (n AccessNode) String() string {
|
|||
return fmt.Sprintf("(%s from %s)", n.property, n.source)
|
||||
}
|
||||
|
||||
func (n AccessNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
type BinaryOperation uint
|
||||
|
||||
func (n BinaryOperation) String() string {
|
||||
|
|
@ -198,11 +236,45 @@ const (
|
|||
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
|
||||
type BinaryNode struct {
|
||||
BinaryOperation
|
||||
Left Node
|
||||
Right Node
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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
|
||||
type BooleanNode struct {
|
||||
value bool
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n BooleanNode) Type() NodeType {
|
||||
|
|
@ -226,8 +354,15 @@ func (n BooleanNode) String() string {
|
|||
return strconv.FormatBool(n.value)
|
||||
}
|
||||
|
||||
func (n BooleanNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// NilNode nil value
|
||||
type NilNode struct{}
|
||||
type NilNode struct {
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n NilNode) Type() NodeType {
|
||||
return NilNodeType
|
||||
|
|
@ -237,9 +372,16 @@ func (n NilNode) String() string {
|
|||
return "nil"
|
||||
}
|
||||
|
||||
func (n NilNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// BlockNode block node with statements
|
||||
type BlockNode struct {
|
||||
statements []Node
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n BlockNode) Type() NodeType {
|
||||
|
|
@ -257,16 +399,8 @@ func (n BlockNode) String() string {
|
|||
return builder.String()
|
||||
}
|
||||
|
||||
type ImportNode struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (n ImportNode) Type() NodeType {
|
||||
return ImportNodeType
|
||||
}
|
||||
|
||||
func (n ImportNode) String() string {
|
||||
return fmt.Sprintf("import %s", n.path)
|
||||
func (n BlockNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// ConditionalNode conditionals (if statements)
|
||||
|
|
@ -274,6 +408,9 @@ type ConditionalNode struct {
|
|||
condition Node
|
||||
do Node
|
||||
otherwise Node
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n ConditionalNode) Type() NodeType {
|
||||
|
|
@ -281,13 +418,24 @@ func (n ConditionalNode) Type() NodeType {
|
|||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
func (n ConditionalNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// LoopNode Loops (for/while)
|
||||
type LoopNode struct {
|
||||
condition Node
|
||||
do Node
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
func (n LoopNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// AssignNode assignment
|
||||
type AssignNode struct {
|
||||
name string
|
||||
value Node
|
||||
declare bool
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (n AssignNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// CallNode function call
|
||||
type CallNode struct {
|
||||
source Node
|
||||
args []Node
|
||||
keep bool
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (n CallNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// FunctionNode definition of function
|
||||
type FunctionNode struct {
|
||||
name string
|
||||
params []string
|
||||
parameters []FunctionParameter
|
||||
yield TypeSignature
|
||||
logic Node
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
type FunctionParameter struct {
|
||||
Name string
|
||||
Signature TypeSignature
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
func (n FunctionNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
||||
// ReturnNode return a value out of this context
|
||||
type ReturnNode struct {
|
||||
value Node
|
||||
|
||||
start Pos
|
||||
end Pos
|
||||
}
|
||||
|
||||
func (n ReturnNode) Type() NodeType {
|
||||
|
|
@ -356,7 +538,14 @@ func (n ReturnNode) String() string {
|
|||
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 {
|
||||
return BreakpointNodeType
|
||||
|
|
@ -365,3 +554,7 @@ func (n BreakpointNode) Type() NodeType {
|
|||
func (n BreakpointNode) String() string {
|
||||
return "breakpoint"
|
||||
}
|
||||
|
||||
func (n BreakpointNode) Bounds() (Pos, Pos) {
|
||||
return n.start, n.end
|
||||
}
|
||||
|
|
|
|||
400
core/parser.go
400
core/parser.go
|
|
@ -8,17 +8,24 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type FormatedError interface {
|
||||
Error() string
|
||||
Format() string
|
||||
}
|
||||
|
||||
type ParsingError struct {
|
||||
Description string
|
||||
Causer *Token
|
||||
Source string
|
||||
}
|
||||
|
||||
func (p *ParsingError) Error() string {
|
||||
func (p ParsingError) Error() string {
|
||||
return p.Description
|
||||
}
|
||||
|
||||
// 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{}
|
||||
|
||||
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.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++ {
|
||||
builder.WriteRune(' ')
|
||||
}
|
||||
|
|
@ -58,20 +69,45 @@ func (p *ParsingError) Format(src []rune) string {
|
|||
}
|
||||
|
||||
type Parser struct {
|
||||
source string
|
||||
tokens []Token
|
||||
prev *Token
|
||||
curr *Token
|
||||
pos Pos
|
||||
}
|
||||
|
||||
func NewParser(tokens []Token) *Parser {
|
||||
func NewParser(source string, tokens []Token) *Parser {
|
||||
return &Parser{
|
||||
source: source,
|
||||
tokens: tokens,
|
||||
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
|
||||
statements := make([]Node, 0)
|
||||
|
||||
|
|
@ -79,17 +115,33 @@ func (p *Parser) Parse() (Node, error) {
|
|||
p.advance()
|
||||
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if b != nil {
|
||||
statements = append(statements, b)
|
||||
}
|
||||
}
|
||||
|
||||
return &BlockNode{
|
||||
statements: statements,
|
||||
return &Program{
|
||||
imports,
|
||||
&BlockNode{
|
||||
statements,
|
||||
0,
|
||||
p.curr.Start + p.curr.Length,
|
||||
},
|
||||
path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -107,9 +159,9 @@ func (p *Parser) accept(tokenType TokenType) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (p *Parser) expect(tokenType TokenType) error {
|
||||
func (p *Parser) expect(tokenType TokenType, reason string) error {
|
||||
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
|
||||
}
|
||||
|
|
@ -134,9 +186,10 @@ func (p *Parser) advance() {
|
|||
}
|
||||
|
||||
func (p *Parser) error(error string, causer *Token) error {
|
||||
return &ParsingError{
|
||||
return ParsingError{
|
||||
Description: error,
|
||||
Causer: causer,
|
||||
Source: p.source,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,6 +200,8 @@ func (p *Parser) factor() (Node, error) {
|
|||
return &StringNode{
|
||||
(*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1],
|
||||
(*p.prev).Lexeme,
|
||||
p.prev.Start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
|
||||
case TokenNumber:
|
||||
|
|
@ -159,17 +214,37 @@ func (p *Parser) factor() (Node, error) {
|
|||
|
||||
return &NumberNode{
|
||||
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
|
||||
|
||||
case TokenTrue:
|
||||
p.advance()
|
||||
return &BooleanNode{
|
||||
true,
|
||||
p.prev.Start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
case TokenFalse:
|
||||
p.advance()
|
||||
return &BooleanNode{
|
||||
false,
|
||||
p.prev.Start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
|
||||
case TokenNil:
|
||||
|
|
@ -178,11 +253,25 @@ func (p *Parser) factor() (Node, error) {
|
|||
|
||||
case TokenOpenBracket:
|
||||
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
|
||||
for !p.accept(TokenCloseBracket) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -198,24 +287,48 @@ func (p *Parser) factor() (Node, error) {
|
|||
|
||||
return &ListNode{
|
||||
values,
|
||||
nil,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
|
||||
// unary minus
|
||||
case TokenMinus:
|
||||
p.advance()
|
||||
first := p.prev
|
||||
|
||||
f, err := p.factor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BinaryNode{
|
||||
BinarySubtraction,
|
||||
&NumberNode{0},
|
||||
return &UnaryNode{
|
||||
UnaryNegate,
|
||||
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
|
||||
|
||||
case TokenName:
|
||||
p.advance()
|
||||
name := (*p.prev).Lexeme
|
||||
start := p.prev.Start
|
||||
nameEnd := start + p.prev.Length
|
||||
|
||||
if p.curr.Type == TokenOpenParenthesis {
|
||||
args, err := p.parseArgs()
|
||||
|
|
@ -226,23 +339,39 @@ func (p *Parser) factor() (Node, error) {
|
|||
return &CallNode{
|
||||
&ReferenceNode{
|
||||
name,
|
||||
start,
|
||||
nameEnd,
|
||||
},
|
||||
args,
|
||||
true,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ReferenceNode{
|
||||
name,
|
||||
start,
|
||||
nameEnd,
|
||||
}, nil
|
||||
|
||||
case TokenFunc:
|
||||
p.advance()
|
||||
start := p.prev.Start
|
||||
|
||||
params, err := p.parseParams()
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -251,7 +380,10 @@ func (p *Parser) factor() (Node, error) {
|
|||
return &FunctionNode{
|
||||
"*",
|
||||
params,
|
||||
sig,
|
||||
b,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
|
||||
case TokenOpenParenthesis:
|
||||
|
|
@ -260,20 +392,20 @@ func (p *Parser) factor() (Node, error) {
|
|||
if err != nil {
|
||||
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 v, nil
|
||||
|
||||
default:
|
||||
err := p.error("invalid factor", p.curr)
|
||||
p.advance()
|
||||
return nil, err
|
||||
return nil, p.error("invalid factor", p.curr)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) prop() (Node, error) {
|
||||
start := p.curr.Start
|
||||
|
||||
v, err := p.factor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -281,7 +413,7 @@ func (p *Parser) prop() (Node, error) {
|
|||
|
||||
// parse chains of prop-getting ( "".split().join().length.round() )
|
||||
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
|
||||
}
|
||||
property := (*p.prev).Lexeme
|
||||
|
|
@ -289,6 +421,8 @@ func (p *Parser) prop() (Node, error) {
|
|||
v = &AccessNode{
|
||||
v,
|
||||
property,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}
|
||||
|
||||
// if called, also add
|
||||
|
|
@ -302,6 +436,8 @@ func (p *Parser) prop() (Node, error) {
|
|||
v,
|
||||
args,
|
||||
true,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -310,6 +446,7 @@ func (p *Parser) prop() (Node, error) {
|
|||
}
|
||||
|
||||
func (p *Parser) product() (Node, error) {
|
||||
start := p.curr.Start
|
||||
left, err := p.prop()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -331,6 +468,8 @@ func (p *Parser) product() (Node, error) {
|
|||
op,
|
||||
left,
|
||||
f,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -338,6 +477,8 @@ func (p *Parser) product() (Node, error) {
|
|||
}
|
||||
|
||||
func (p *Parser) term() (Node, error) {
|
||||
start := p.curr.Start
|
||||
|
||||
left, err := p.product()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -359,6 +500,8 @@ func (p *Parser) term() (Node, error) {
|
|||
op,
|
||||
left,
|
||||
pr,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -366,6 +509,7 @@ func (p *Parser) term() (Node, error) {
|
|||
}
|
||||
|
||||
func (p *Parser) comparison() (Node, error) {
|
||||
start := p.curr.Start
|
||||
left, err := p.term()
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -403,10 +547,13 @@ func (p *Parser) comparison() (Node, error) {
|
|||
op,
|
||||
left,
|
||||
t,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Parser) condition() (Node, error) {
|
||||
start := p.curr.Start
|
||||
left, err := p.comparison()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -434,12 +581,15 @@ func (p *Parser) condition() (Node, error) {
|
|||
op,
|
||||
left,
|
||||
c,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Parser) statement() (Node, error) {
|
||||
switch (*p.curr).Type {
|
||||
case TokenIf:
|
||||
start := p.curr.Start
|
||||
p.advance()
|
||||
|
||||
condition, err := p.condition()
|
||||
|
|
@ -470,20 +620,25 @@ func (p *Parser) statement() (Node, error) {
|
|||
condition,
|
||||
then,
|
||||
otherwise,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
|
||||
case TokenName:
|
||||
p.advance()
|
||||
start := p.prev.Start
|
||||
name := (*p.prev).Lexeme
|
||||
|
||||
if (*p.curr).Type == TokenDot {
|
||||
var v Node = &ReferenceNode{
|
||||
name,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}
|
||||
|
||||
// parse chains of prop-getting ( "".split().join().length.round() )
|
||||
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
|
||||
}
|
||||
property := (*p.prev).Lexeme
|
||||
|
|
@ -491,6 +646,8 @@ func (p *Parser) statement() (Node, error) {
|
|||
v = &AccessNode{
|
||||
v,
|
||||
property,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}
|
||||
|
||||
// if called, also add
|
||||
|
|
@ -504,6 +661,8 @@ func (p *Parser) statement() (Node, error) {
|
|||
v,
|
||||
args,
|
||||
(*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{
|
||||
&ReferenceNode{
|
||||
name,
|
||||
start,
|
||||
start + Pos(len(name)),
|
||||
},
|
||||
args,
|
||||
false,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
} else if p.accept(TokenAssign) || p.accept(TokenDeclare) {
|
||||
isDeclaration := p.prev.Type == TokenDeclare
|
||||
|
|
@ -533,28 +696,19 @@ func (p *Parser) statement() (Node, error) {
|
|||
name,
|
||||
c,
|
||||
isDeclaration,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
} else {
|
||||
return p.condition()
|
||||
}
|
||||
|
||||
case TokenImport:
|
||||
p.advance()
|
||||
|
||||
if err := p.expect(TokenString); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path := p.prev.Lexeme[1 : len(p.prev.Lexeme)-1]
|
||||
|
||||
return &ImportNode{
|
||||
path,
|
||||
}, nil
|
||||
return nil, p.error("invalid statement", p.curr)
|
||||
|
||||
case TokenFunc:
|
||||
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
|
||||
}
|
||||
name := p.prev.Lexeme
|
||||
|
|
@ -564,6 +718,14 @@ func (p *Parser) statement() (Node, error) {
|
|||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -574,13 +736,19 @@ func (p *Parser) statement() (Node, error) {
|
|||
&FunctionNode{
|
||||
name,
|
||||
params,
|
||||
yield,
|
||||
b,
|
||||
funcStart,
|
||||
p.prev.Start + p.prev.Length,
|
||||
},
|
||||
true,
|
||||
funcStart,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
|
||||
case TokenWhile:
|
||||
p.advance()
|
||||
start := p.prev.Start
|
||||
|
||||
c, err := p.condition()
|
||||
if err != nil {
|
||||
|
|
@ -595,10 +763,13 @@ func (p *Parser) statement() (Node, error) {
|
|||
return &LoopNode{
|
||||
c,
|
||||
b,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
|
||||
case TokenReturn:
|
||||
p.advance()
|
||||
start := p.prev.Start
|
||||
|
||||
c, err := p.condition()
|
||||
if err != nil {
|
||||
|
|
@ -607,6 +778,8 @@ func (p *Parser) statement() (Node, error) {
|
|||
|
||||
return &ReturnNode{
|
||||
c,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
|
||||
case TokenBreakpoint:
|
||||
|
|
@ -614,24 +787,33 @@ func (p *Parser) statement() (Node, error) {
|
|||
|
||||
return &BreakpointNode{}, nil
|
||||
|
||||
case TokenImport:
|
||||
defer p.advance()
|
||||
return nil, p.error("import statements must be top-level", p.curr)
|
||||
|
||||
default:
|
||||
err := p.error("invalid statement", p.curr)
|
||||
p.advance()
|
||||
return nil, err
|
||||
defer p.advance()
|
||||
return nil, p.error("invalid statement", p.curr)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) block(canBeStatement bool) (Node, error) {
|
||||
if canBeStatement {
|
||||
if !p.accept(TokenOpenBrace) {
|
||||
if p.curr.Type == TokenEOF {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return p.statement()
|
||||
}
|
||||
} else {
|
||||
if err := p.expect(TokenOpenBrace); err != nil {
|
||||
if err := p.expect(TokenOpenBrace, "a block is required"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
start := p.prev.Start
|
||||
|
||||
statements := make([]Node, 0)
|
||||
|
||||
for !p.accept(TokenCloseBrace) {
|
||||
|
|
@ -646,13 +828,15 @@ func (p *Parser) block(canBeStatement bool) (Node, error) {
|
|||
|
||||
return &BlockNode{
|
||||
statements,
|
||||
start,
|
||||
p.prev.Start + p.prev.Length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseArgs() ([]Node, error) {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -663,7 +847,7 @@ func (p *Parser) parseArgs() ([]Node, error) {
|
|||
}
|
||||
args = append(args, c)
|
||||
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
|
||||
}
|
||||
c, err = p.condition()
|
||||
|
|
@ -678,30 +862,142 @@ func (p *Parser) parseArgs() ([]Node, error) {
|
|||
}
|
||||
|
||||
// parseParams parse parameters and parentheses
|
||||
func (p *Parser) parseParams() ([]string, error) {
|
||||
if err := p.expect(TokenOpenParenthesis); err != nil {
|
||||
func (p *Parser) parseParams() ([]FunctionParameter, error) {
|
||||
if err := p.expect(TokenOpenParenthesis, "parameters must be in parentheses"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params := make([]string, 0)
|
||||
params := make([]FunctionParameter, 0)
|
||||
|
||||
if p.accept(TokenName) {
|
||||
name := (*p.prev).Lexeme
|
||||
params = append(params, name)
|
||||
for !p.accept(TokenCloseParenthesis) {
|
||||
if err := p.expect(TokenComma); err != nil {
|
||||
if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil {
|
||||
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
|
||||
}
|
||||
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 {
|
||||
if err := p.expect(TokenCloseParenthesis); err != nil {
|
||||
if err := p.expect(TokenCloseParenthesis, "must close parameter list"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewParser(t *testing.T) {
|
||||
tokens := make([]Token, 0)
|
||||
|
||||
p := NewParser(tokens)
|
||||
p := NewParser("", tokens)
|
||||
|
||||
if p == nil {
|
||||
t.Fatal("parser should not be nil")
|
||||
|
|
@ -32,7 +34,7 @@ func TestNewParser(t *testing.T) {
|
|||
func BenchmarkNewParser(b *testing.B) {
|
||||
tokens := make([]Token, 0)
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewParser(tokens)
|
||||
_ = NewParser("", tokens)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,14 +68,19 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
BinaryAddition,
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
2,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"assignment": {
|
||||
|
|
@ -90,10 +97,13 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
&StringNode{
|
||||
"Hello world!",
|
||||
"\"Hello world!\"",
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"declaration": {
|
||||
|
|
@ -113,14 +123,19 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
BinaryAddition,
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
&ReferenceNode{
|
||||
"b",
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
// (2 + 1) * 5 + 3 / (6 - 2) - 10 / 2
|
||||
|
|
@ -167,30 +182,63 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
BinaryMultiplication,
|
||||
&BinaryNode{
|
||||
BinaryAddition,
|
||||
&NumberNode{2},
|
||||
&NumberNode{1},
|
||||
&NumberNode{
|
||||
2,
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{5},
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
5,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
&BinaryNode{
|
||||
BinaryDivision,
|
||||
&NumberNode{3},
|
||||
&NumberNode{
|
||||
3,
|
||||
0, 0,
|
||||
},
|
||||
&BinaryNode{
|
||||
BinarySubtraction,
|
||||
&NumberNode{6},
|
||||
&NumberNode{2},
|
||||
&NumberNode{
|
||||
6,
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
2,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
&BinaryNode{
|
||||
BinaryDivision,
|
||||
&NumberNode{10},
|
||||
&NumberNode{2},
|
||||
&NumberNode{
|
||||
10,
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
2,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"condition_equal": {
|
||||
|
|
@ -210,14 +258,19 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
BinaryEquality,
|
||||
&NumberNode{
|
||||
20,
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
15,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"if_statement": {
|
||||
|
|
@ -240,10 +293,13 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
BinaryEquality,
|
||||
&ReferenceNode{
|
||||
"a",
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
0,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
do: &BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -251,13 +307,17 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
"b",
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"if_else_statement": {
|
||||
|
|
@ -286,10 +346,13 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
BinaryEquality,
|
||||
&ReferenceNode{
|
||||
"a",
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
0,
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
do: &BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -297,10 +360,13 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
"b",
|
||||
&NumberNode{
|
||||
1,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
otherwise: &BlockNode{
|
||||
[]Node{
|
||||
|
|
@ -308,13 +374,17 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
"b",
|
||||
&NumberNode{
|
||||
0,
|
||||
0, 0,
|
||||
},
|
||||
false,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"empty_block": {
|
||||
|
|
@ -327,8 +397,10 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
[]Node{
|
||||
&BlockNode{
|
||||
[]Node{},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"lambda": { // a := func(a, b) { return a + b }
|
||||
|
|
@ -338,9 +410,14 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
NewToken(TokenFunc, 3, 4, 0, "func"),
|
||||
NewToken(TokenOpenParenthesis, 7, 1, 0, "("),
|
||||
NewToken(TokenName, 8, 1, 0, "a"),
|
||||
NewToken(TokenColon, 9, 1, 0, ":"),
|
||||
NewToken(TokenName, 10, 5, 0, "number"),
|
||||
NewToken(TokenComma, 9, 1, 0, ","),
|
||||
NewToken(TokenName, 10, 1, 0, "b"),
|
||||
NewToken(TokenColon, 9, 1, 0, ":"),
|
||||
NewToken(TokenName, 10, 5, 0, "number"),
|
||||
NewToken(TokenCloseParenthesis, 11, 1, 0, ")"),
|
||||
NewToken(TokenName, 10, 5, 0, "number"),
|
||||
|
||||
NewToken(TokenOpenBrace, 12, 1, 1, "{"),
|
||||
NewToken(TokenReturn, 13, 6, 1, "return"),
|
||||
|
|
@ -357,7 +434,17 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
"a",
|
||||
&FunctionNode{
|
||||
"*",
|
||||
[]string{"a", "b"},
|
||||
[]FunctionParameter{
|
||||
{
|
||||
"a",
|
||||
&NumberSignature{},
|
||||
},
|
||||
{
|
||||
"b",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
&NumberSignature{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&ReturnNode{
|
||||
|
|
@ -365,18 +452,26 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
BinaryAddition,
|
||||
&ReferenceNode{
|
||||
"a",
|
||||
0, 0,
|
||||
},
|
||||
&ReferenceNode{
|
||||
"b",
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"function_declaration": {
|
||||
|
|
@ -404,7 +499,17 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
"a",
|
||||
&FunctionNode{
|
||||
"a",
|
||||
[]string{"a", "b"},
|
||||
[]FunctionParameter{
|
||||
{
|
||||
"a",
|
||||
&NumberSignature{},
|
||||
},
|
||||
{
|
||||
"b",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
&NumberSignature{},
|
||||
&BlockNode{
|
||||
[]Node{
|
||||
&ReturnNode{
|
||||
|
|
@ -412,18 +517,26 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
BinaryAddition,
|
||||
&ReferenceNode{
|
||||
"a",
|
||||
0, 0,
|
||||
},
|
||||
&ReferenceNode{
|
||||
"b",
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"prop_getting": {
|
||||
|
|
@ -443,12 +556,16 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
&AccessNode{
|
||||
&ReferenceNode{
|
||||
"a",
|
||||
0, 0,
|
||||
},
|
||||
"b",
|
||||
0, 0,
|
||||
},
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
"list_init": {
|
||||
|
|
@ -488,27 +605,43 @@ func GetTokenTestData() map[string]TokenTestData {
|
|||
[]Node{
|
||||
&ReferenceNode{
|
||||
"a",
|
||||
0, 0,
|
||||
},
|
||||
&NumberNode{
|
||||
3.141,
|
||||
0, 0,
|
||||
},
|
||||
&StringNode{
|
||||
"Hello world!",
|
||||
"\"Hello world!\"",
|
||||
0, 0,
|
||||
},
|
||||
&BooleanNode{
|
||||
true,
|
||||
0, 0,
|
||||
},
|
||||
&ListNode{
|
||||
[]Node{
|
||||
&NumberNode{2}, &NumberNode{3},
|
||||
&NumberNode{
|
||||
2,
|
||||
0, 0,
|
||||
}, &NumberNode{
|
||||
3,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
0, 0,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
0, 0,
|
||||
},
|
||||
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)
|
||||
}
|
||||
|
||||
if len(n.params) != len(m.params) {
|
||||
t.Fatalf("Function node parameters count does not match (%d and %d)", 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.parameters), len(m.parameters))
|
||||
} 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 {
|
||||
if n.params[i] != p {
|
||||
t.Errorf("Function node parameter %d does not match: %s and %s", i, p, m.params)
|
||||
for i, p := range m.parameters {
|
||||
if !n.parameters[i].Signature.Matches(p.Signature) {
|
||||
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 {
|
||||
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) {
|
||||
t.Logf("Getting test data")
|
||||
tokenData := GetTokenTestData()
|
||||
|
|
@ -676,17 +910,17 @@ func TestParser_Parse(t *testing.T) {
|
|||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Logf("Initializing parser")
|
||||
p := NewParser(data.tokens)
|
||||
p := NewParser("", data.tokens)
|
||||
|
||||
t.Logf("Parsing main")
|
||||
tree, err := p.Parse()
|
||||
tree, err := p.Parse("")
|
||||
|
||||
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")
|
||||
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 {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p := NewParser(data.tokens)
|
||||
p := NewParser("", data.tokens)
|
||||
|
||||
_, _ = p.Parse()
|
||||
_, _ = p.Parse("")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,24 +2,27 @@ package core
|
|||
|
||||
type Stack[T any] struct {
|
||||
Current Pos
|
||||
Size Pos
|
||||
Capacity Pos
|
||||
|
||||
items []T
|
||||
}
|
||||
|
||||
func NewStack[T any](size Pos) *Stack[T] {
|
||||
func NewStack[T any](capacity Pos) *Stack[T] {
|
||||
return &Stack[T]{
|
||||
items: make([]T, size),
|
||||
Size: size,
|
||||
items: make([]T, 16),
|
||||
Capacity: capacity,
|
||||
Current: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Push(items ...T) {
|
||||
for _, item := range items {
|
||||
if s.Current >= s.Size {
|
||||
if s.Current >= s.Capacity {
|
||||
panic("stack overflow")
|
||||
}
|
||||
if int(s.Current) == len(s.items) {
|
||||
s.items = append(s.items, item)
|
||||
}
|
||||
|
||||
s.items[s.Current] = item
|
||||
s.Current++
|
||||
|
|
@ -45,7 +48,7 @@ func (s *Stack[T]) Peek() T {
|
|||
|
||||
// check whether the stack is invalid (stack over-/underflow)
|
||||
func (s *Stack[T]) check() {
|
||||
if s.Current >= s.Size {
|
||||
if s.Current >= s.Capacity {
|
||||
panic("stack underflow")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,16 +25,10 @@ func TestNewStack(t *testing.T) {
|
|||
|
||||
s := NewStack[any](Pos(size))
|
||||
|
||||
if s.Size != Pos(size) {
|
||||
t.Errorf("Stack size (%d) does not match expected size (%d)", s.Size, size)
|
||||
if s.Capacity != Pos(size) {
|
||||
t.Errorf("Stack size (%d) does not match expected size (%d)", s.Capacity, size)
|
||||
} else {
|
||||
t.Logf("Stack size is expected size (%d)", s.Size)
|
||||
}
|
||||
|
||||
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))
|
||||
t.Logf("Stack size is expected size (%d)", s.Capacity)
|
||||
}
|
||||
|
||||
if s.Current != 0 {
|
||||
|
|
|
|||
349
core/types.go
Normal file
349
core/types.go
Normal 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"
|
||||
}
|
||||
275
core/values.go
275
core/values.go
|
|
@ -68,15 +68,6 @@ func GoToValue(gov interface{}) Value {
|
|||
return &StringValue{
|
||||
v,
|
||||
}
|
||||
case []interface{}:
|
||||
values := make([]Value, len(v))
|
||||
for i, value := range v {
|
||||
values[i] = GoToValue(value)
|
||||
}
|
||||
|
||||
return &ListValue{
|
||||
values,
|
||||
}
|
||||
case map[string]interface{}:
|
||||
values := map[string]Value{}
|
||||
for key, value := range v {
|
||||
|
|
@ -86,9 +77,17 @@ func GoToValue(gov interface{}) Value {
|
|||
return &ObjectValue{
|
||||
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 {
|
||||
|
|
@ -106,6 +105,9 @@ type Value interface {
|
|||
|
||||
// Get a member from the value. An error is returned if the member does not exist
|
||||
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{}
|
||||
|
|
@ -130,8 +132,12 @@ func (v *NilValue) Get(_ string) (Value, error) {
|
|||
return nil, errors.New("nil has no properties")
|
||||
}
|
||||
|
||||
func (v *NilValue) Clone() Value {
|
||||
return &NilValue{}
|
||||
}
|
||||
|
||||
type BoolValue struct {
|
||||
bool
|
||||
Boolean bool
|
||||
}
|
||||
|
||||
func (v *BoolValue) Type() ValueType {
|
||||
|
|
@ -139,7 +145,7 @@ func (v *BoolValue) Type() ValueType {
|
|||
}
|
||||
|
||||
func (v *BoolValue) String() string {
|
||||
if v.bool {
|
||||
if v.Boolean {
|
||||
return "true"
|
||||
} else {
|
||||
return "false"
|
||||
|
|
@ -151,16 +157,22 @@ func (v *BoolValue) DebugString() string {
|
|||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
type ObjectValue struct {
|
||||
members map[string]Value
|
||||
Members map[string]Value
|
||||
}
|
||||
|
||||
func (v *ObjectValue) Type() ValueType {
|
||||
|
|
@ -169,12 +181,12 @@ func (v *ObjectValue) Type() ValueType {
|
|||
|
||||
func (v *ObjectValue) String() string {
|
||||
out := "{"
|
||||
for key, value := range v.members {
|
||||
for key, value := range v.Members {
|
||||
if out != "{" {
|
||||
out += ", "
|
||||
}
|
||||
|
||||
out += fmt.Sprintf("%q=%s", key, value.String())
|
||||
out += fmt.Sprintf("%q=%s", key, value.DebugString())
|
||||
}
|
||||
out += "}"
|
||||
|
||||
|
|
@ -191,8 +203,8 @@ func (v *ObjectValue) Equals(other Value) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
for key, value := range v.members {
|
||||
if !object.members[key].Equals(value) {
|
||||
for key, value := range v.Members {
|
||||
if !object.Members[key].Equals(value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -203,26 +215,30 @@ func (v *ObjectValue) Equals(other Value) bool {
|
|||
var ObjectPrototype = map[string]Value{
|
||||
"set": &BuiltinFunctionValue{
|
||||
"set",
|
||||
[]string{"property", "value"},
|
||||
func(vm *VM, _this Value, params map[string]Value) (Value, error) {
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{&StringSignature{}, &ListSignature{}},
|
||||
&NilSignature{},
|
||||
},
|
||||
func(vm *VM, _this Value, params []Value) (Value, error) {
|
||||
this := _this.(*ObjectValue)
|
||||
|
||||
p := params["property"]
|
||||
v, ok := params["value"].(*StringValue)
|
||||
p := params[1]
|
||||
v, ok := params[0].(*StringValue)
|
||||
if !ok {
|
||||
return nil, errors.New("property is not a string")
|
||||
}
|
||||
|
||||
this.members[v.string] = p
|
||||
this.Members[v.Text] = p
|
||||
|
||||
return &NilValue{}, nil
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
} else if p, ok := ObjectPrototype[key]; ok {
|
||||
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
|
||||
type NumberValue struct {
|
||||
float64
|
||||
Number float64
|
||||
}
|
||||
|
||||
const NumberSize int = 64
|
||||
|
|
@ -243,7 +271,7 @@ func (v *NumberValue) Type() ValueType {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -251,7 +279,7 @@ func (v *NumberValue) DebugString() string {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -259,8 +287,14 @@ func (v *NumberValue) Get(_ string) (Value, error) {
|
|||
return nil, errors.New("numbers have no properties")
|
||||
}
|
||||
|
||||
func (v *NumberValue) Clone() Value {
|
||||
return &NumberValue{
|
||||
v.Number,
|
||||
}
|
||||
}
|
||||
|
||||
type StringValue struct {
|
||||
string
|
||||
Text string
|
||||
}
|
||||
|
||||
func (v *StringValue) Type() ValueType {
|
||||
|
|
@ -268,7 +302,7 @@ func (v *StringValue) Type() ValueType {
|
|||
}
|
||||
|
||||
func (v *StringValue) String() string {
|
||||
return v.string
|
||||
return v.Text
|
||||
}
|
||||
|
||||
func (v *StringValue) DebugString() string {
|
||||
|
|
@ -276,31 +310,47 @@ func (v *StringValue) DebugString() string {
|
|||
}
|
||||
|
||||
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{
|
||||
"split": {
|
||||
"split",
|
||||
[]string{"seperator"},
|
||||
func(vm *VM, this Value, m map[string]Value) (Value, error) {
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{&StringSignature{}},
|
||||
&ListSignature{
|
||||
&StringSignature{},
|
||||
},
|
||||
},
|
||||
func(vm *VM, this Value, v []Value) (Value, error) {
|
||||
str := this.(*StringValue).String()
|
||||
sep := m["seperator"].(*StringValue).String()
|
||||
sep := v[0].(*StringValue).String()
|
||||
|
||||
var out []string
|
||||
var out []Value
|
||||
tmp := strings.Builder{}
|
||||
for i := 0; i < len(str)-len(sep); i++ {
|
||||
tmp.WriteRune([]rune(str)[i])
|
||||
|
||||
if str[i:i+len(sep)] == sep {
|
||||
out = append(out, tmp.String())
|
||||
out = append(out, &StringValue{tmp.String()})
|
||||
tmp.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
return GoToValue(out), nil
|
||||
return &ListValue{out}, 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))
|
||||
}
|
||||
|
||||
func (v *StringValue) Clone() Value {
|
||||
return &StringValue{
|
||||
v.Text,
|
||||
}
|
||||
}
|
||||
|
||||
// ListValue a dynamic list of values
|
||||
type ListValue struct {
|
||||
items []Value
|
||||
Items []Value
|
||||
}
|
||||
|
||||
func (v *ListValue) Type() ValueType {
|
||||
|
|
@ -323,7 +379,7 @@ func (v *ListValue) Type() ValueType {
|
|||
|
||||
func (v *ListValue) String() string {
|
||||
out := "["
|
||||
for i, item := range v.items {
|
||||
for i, item := range v.Items {
|
||||
if i != 0 {
|
||||
out += ", "
|
||||
}
|
||||
|
|
@ -345,12 +401,12 @@ func (v *ListValue) Equals(other Value) bool {
|
|||
|
||||
l := other.(*ListValue)
|
||||
|
||||
if len(v.items) != len(l.items) {
|
||||
if len(v.Items) != len(l.Items) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, item := range l.items {
|
||||
if !item.Equals(l.items[i]) {
|
||||
for i, item := range v.Items {
|
||||
if !item.Equals(l.Items[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -361,19 +417,28 @@ func (v *ListValue) Equals(other Value) bool {
|
|||
var ListPrototype = map[string]*BuiltinFunctionValue{
|
||||
"append": {
|
||||
"append",
|
||||
[]string{"item"},
|
||||
func(_ *VM, this Value, p map[string]Value) (Value, error) {
|
||||
this.(*ListValue).items = append(this.(*ListValue).items, p["item"])
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{&AnySignature{}},
|
||||
&NilSignature{},
|
||||
},
|
||||
func(_ *VM, this Value, v []Value) (Value, error) {
|
||||
this.(*ListValue).Items = append(this.(*ListValue).Items, v[0])
|
||||
return &NilValue{}, nil
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
"at": {
|
||||
"at",
|
||||
[]string{"index"},
|
||||
func(_ *VM, this Value, p map[string]Value) (Value, error) {
|
||||
items := this.(*ListValue).items
|
||||
index := int(p["index"].(*NumberValue).float64)
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{
|
||||
&NumberSignature{},
|
||||
},
|
||||
&InnerSignature{},
|
||||
},
|
||||
func(_ *VM, this Value, p []Value) (Value, error) {
|
||||
items := this.(*ListValue).Items
|
||||
index := int(p[0].(*NumberValue).Number)
|
||||
|
||||
if index >= len(items) {
|
||||
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
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
"length": {
|
||||
"length",
|
||||
[]string{},
|
||||
func(_ *VM, this Value, p map[string]Value) (Value, error) {
|
||||
return GoToValue(len(this.(*ListValue).items)), nil
|
||||
},
|
||||
nil,
|
||||
},
|
||||
"map": {
|
||||
"map",
|
||||
[]string{"f"},
|
||||
func(vm *VM, value Value, m map[string]Value) (Value, error) {
|
||||
list := value.(*ListValue)
|
||||
|
||||
v := m["f"]
|
||||
var f Value
|
||||
f, ok := v.(*FunctionValue)
|
||||
if !ok {
|
||||
f, ok = v.(*BuiltinFunctionValue)
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New(fmt.Sprintf("not a function to apply: %s", v))
|
||||
}
|
||||
}
|
||||
|
||||
for i, item := range list.items {
|
||||
var err error
|
||||
list.items[i], err = vm.Call(f, []Value{
|
||||
item,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return list, nil
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{},
|
||||
&NumberSignature{},
|
||||
},
|
||||
func(_ *VM, this Value, _ []Value) (Value, error) {
|
||||
return GoToValue(len(this.(*ListValue).Items)), nil
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
"reduce": {
|
||||
"reduce",
|
||||
[]string{"f", "start"},
|
||||
func(vm *VM, value Value, m map[string]Value) (Value, error) {
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{
|
||||
&AnySignature{},
|
||||
&AnySignature{},
|
||||
},
|
||||
&AnySignature{},
|
||||
},
|
||||
&AnySignature{},
|
||||
},
|
||||
&AnySignature{},
|
||||
},
|
||||
func(vm *VM, value Value, m []Value) (Value, error) {
|
||||
list := value.(*ListValue)
|
||||
f := m["f"]
|
||||
sum := m["start"]
|
||||
f := m[0]
|
||||
sum := m[1]
|
||||
|
||||
for _, v := range list.items {
|
||||
for _, v := range list.Items {
|
||||
result, err := vm.Call(f, []Value{sum, v})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -442,6 +492,7 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
|
|||
return sum, 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))
|
||||
}
|
||||
|
||||
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 {
|
||||
Name string
|
||||
Params []string
|
||||
Params []FunctionParameter
|
||||
Yield TypeSignature
|
||||
Chunk *Chunk
|
||||
Parent Value
|
||||
}
|
||||
|
|
@ -482,11 +546,22 @@ func (v *FunctionValue) Get(_ string) (Value, error) {
|
|||
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 {
|
||||
Name string
|
||||
Parameters []string
|
||||
F func(*VM, Value, map[string]Value) (Value, error)
|
||||
Signature *FunctionSignature
|
||||
F func(*VM, Value, []Value) (Value, error)
|
||||
Parent Value
|
||||
Constant bool
|
||||
}
|
||||
|
||||
func (v *BuiltinFunctionValue) Type() ValueType {
|
||||
|
|
@ -510,6 +585,16 @@ func (v *BuiltinFunctionValue) Get(_ string) (Value, error) {
|
|||
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
|
||||
type VariableValue struct {
|
||||
name string
|
||||
|
|
@ -541,3 +626,11 @@ func (v *VariableValue) Equals(other Value) bool {
|
|||
func (v *VariableValue) Get(_ string) (Value, error) {
|
||||
return nil, errors.New("variables have no properties")
|
||||
}
|
||||
|
||||
func (v *VariableValue) Clone() Value {
|
||||
return &VariableValue{
|
||||
v.name,
|
||||
v.value.Clone(),
|
||||
v.scope,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,19 @@ func CompareValues(t *testing.T, got Value, want Value) {
|
|||
t.Logf("Both are nil")
|
||||
return
|
||||
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))
|
||||
} else {
|
||||
t.Logf("Both are same boolean (%s)", want.(*BoolValue).String())
|
||||
}
|
||||
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))
|
||||
} else {
|
||||
t.Logf("Both are same number (%s)", got.(*NumberValue).String())
|
||||
}
|
||||
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))
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
if len(n.Parameters) != len(m.Parameters) {
|
||||
t.Errorf("builtin function parameter count mismatch: got %v, want %v", n.Parameters, m.Parameters)
|
||||
if !n.Signature.Matches(m.Signature) {
|
||||
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:
|
||||
n := got.(*VariableValue)
|
||||
m := want.(*VariableValue)
|
||||
|
|
@ -87,6 +78,32 @@ func CompareValues(t *testing.T, got Value, want 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:
|
||||
panic("unimplemented comparison")
|
||||
}
|
||||
|
|
|
|||
325
core/vm.go
325
core/vm.go
|
|
@ -6,6 +6,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -26,6 +28,8 @@ const (
|
|||
InstructionMul
|
||||
// InstructionDiv pop two and divide the second by the first
|
||||
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
|
||||
// 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
|
||||
// to on the stack; the top value on the stack is the last in the list.
|
||||
InstructionFormList
|
||||
// InstructionConcatLists concatenate lists, producing a new list with the values of both lists. Pops two lists.
|
||||
InstructionConcatLists
|
||||
|
||||
// InstructionBreakpoint for debugging purposes
|
||||
InstructionBreakpoint
|
||||
|
|
@ -118,6 +124,8 @@ func (b Bytecode) String() string {
|
|||
return "MUL"
|
||||
case InstructionDiv:
|
||||
return "DIV"
|
||||
case InstructionNegate:
|
||||
return "NEGATE"
|
||||
case InstructionEquals:
|
||||
return "EQUALS"
|
||||
case InstructionNotEqual:
|
||||
|
|
@ -182,6 +190,8 @@ func (b Bytecode) String() string {
|
|||
return "APPEND"
|
||||
case InstructionAccessProperty:
|
||||
return "ACCESS_PROPERTY"
|
||||
case InstructionConcatLists:
|
||||
return "CONCAT_LISTS"
|
||||
}
|
||||
return "UNDEFINED"
|
||||
}
|
||||
|
|
@ -202,7 +212,7 @@ func (c Chunk) String() string {
|
|||
b.WriteString("=-= constants =-=\n")
|
||||
|
||||
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)
|
||||
if ok {
|
||||
|
|
@ -228,6 +238,16 @@ func RegisterGOBTypes() {
|
|||
Params: 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 {
|
||||
|
|
@ -287,38 +307,108 @@ type Call struct {
|
|||
var DefaultGlobals = map[string]Value{
|
||||
"write": &BuiltinFunctionValue{
|
||||
"write", // always remember where you come from...
|
||||
[]string{"value"},
|
||||
func(_ *VM, this Value, v map[string]Value) (Value, error) {
|
||||
println(v["value"].String())
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{&StringSignature{}},
|
||||
&NilSignature{},
|
||||
},
|
||||
func(_ *VM, this Value, v []Value) (Value, error) {
|
||||
println(v[0].String())
|
||||
return nil, nil
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
"print": &BuiltinFunctionValue{
|
||||
"print",
|
||||
[]string{"value"},
|
||||
func(_ *VM, this Value, v map[string]Value) (Value, error) {
|
||||
print(v["value"].String())
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{&StringSignature{}},
|
||||
&NilSignature{},
|
||||
},
|
||||
func(_ *VM, this Value, v []Value) (Value, error) {
|
||||
print(v[0].String())
|
||||
return nil, nil
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
"format": &BuiltinFunctionValue{
|
||||
"format",
|
||||
[]string{"format_string", "values"},
|
||||
func(vm *VM, value Value, m map[string]Value) (Value, error) {
|
||||
valuies := m["values"].(*ListValue).items
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{
|
||||
&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,
|
||||
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",
|
||||
[]string{"a", "b"},
|
||||
func(vm *VM, this Value, params map[string]Value) (Value, error) {
|
||||
a := params["a"]
|
||||
b := params["b"]
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{
|
||||
&AnySignature{},
|
||||
&AnySignature{},
|
||||
},
|
||||
&NilSignature{},
|
||||
},
|
||||
func(vm *VM, this Value, params []Value) (Value, error) {
|
||||
a := params[0]
|
||||
b := params[1]
|
||||
|
||||
if !a.Equals(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
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
"assertNotEq": &BuiltinFunctionValue{
|
||||
"assertNotEq",
|
||||
[]string{"a", "b"},
|
||||
func(vm *VM, this Value, params map[string]Value) (Value, error) {
|
||||
a := params["a"]
|
||||
b := params["b"]
|
||||
&FunctionSignature{
|
||||
[]TypeSignature{
|
||||
&AnySignature{},
|
||||
&AnySignature{},
|
||||
},
|
||||
&NilSignature{},
|
||||
},
|
||||
func(vm *VM, this Value, params []Value) (Value, error) {
|
||||
a := params[0]
|
||||
b := params[1]
|
||||
|
||||
if a.Equals(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
|
||||
},
|
||||
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())
|
||||
|
||||
case InstructionAdd:
|
||||
r := vm.stack.Pop().(*NumberValue).float64
|
||||
l := vm.stack.Pop().(*NumberValue).float64
|
||||
r := vm.stack.Pop().(*NumberValue).Number
|
||||
l := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&NumberValue{l + r})
|
||||
|
||||
case InstructionSub:
|
||||
r := vm.stack.Pop().(*NumberValue).float64
|
||||
l := vm.stack.Pop().(*NumberValue).float64
|
||||
r := vm.stack.Pop().(*NumberValue).Number
|
||||
l := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&NumberValue{l - r})
|
||||
|
||||
case InstructionMul:
|
||||
r := vm.stack.Pop().(*NumberValue).float64
|
||||
l := vm.stack.Pop().(*NumberValue).float64
|
||||
r := vm.stack.Pop().(*NumberValue).Number
|
||||
l := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&NumberValue{l * r})
|
||||
|
||||
case InstructionDiv:
|
||||
r := vm.stack.Pop().(*NumberValue).float64
|
||||
l := vm.stack.Pop().(*NumberValue).float64
|
||||
r := vm.stack.Pop().(*NumberValue).Number
|
||||
l := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&NumberValue{l / r})
|
||||
|
||||
case InstructionNegate:
|
||||
v := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&NumberValue{-v})
|
||||
|
||||
case InstructionEquals:
|
||||
vm.stack.Push(
|
||||
&BoolValue{vm.stack.Pop().Equals(vm.stack.Pop())},
|
||||
|
|
@ -427,40 +608,40 @@ func (vm *VM) Next() bool {
|
|||
)
|
||||
|
||||
case InstructionNot:
|
||||
b := vm.stack.Pop().(*BoolValue).bool
|
||||
b := vm.stack.Pop().(*BoolValue).Boolean
|
||||
vm.stack.Push(&BoolValue{!b})
|
||||
|
||||
case InstructionAnd:
|
||||
r := vm.stack.Pop().(*BoolValue).bool
|
||||
l := vm.stack.Pop().(*BoolValue).bool
|
||||
r := vm.stack.Pop().(*BoolValue).Boolean
|
||||
l := vm.stack.Pop().(*BoolValue).Boolean
|
||||
vm.stack.Push(&BoolValue{l && r})
|
||||
|
||||
case InstructionOr:
|
||||
r := vm.stack.Pop().(*BoolValue).bool
|
||||
l := vm.stack.Pop().(*BoolValue).bool
|
||||
r := vm.stack.Pop().(*BoolValue).Boolean
|
||||
l := vm.stack.Pop().(*BoolValue).Boolean
|
||||
vm.stack.Push(&BoolValue{l || r})
|
||||
|
||||
case InstructionLess:
|
||||
r := vm.stack.Pop().(*NumberValue).float64
|
||||
l := vm.stack.Pop().(*NumberValue).float64
|
||||
r := vm.stack.Pop().(*NumberValue).Number
|
||||
l := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&BoolValue{l < r})
|
||||
|
||||
case InstructionLessOrEqual:
|
||||
r := vm.stack.Pop().(*NumberValue).float64
|
||||
l := vm.stack.Pop().(*NumberValue).float64
|
||||
r := vm.stack.Pop().(*NumberValue).Number
|
||||
l := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&BoolValue{l <= r})
|
||||
|
||||
case InstructionGreater:
|
||||
r := vm.stack.Pop().(*NumberValue).float64
|
||||
l := vm.stack.Pop().(*NumberValue).float64
|
||||
r := vm.stack.Pop().(*NumberValue).Number
|
||||
l := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&BoolValue{l > r})
|
||||
|
||||
case InstructionGreaterOrEqual:
|
||||
r := vm.stack.Pop().(*NumberValue).float64
|
||||
l := vm.stack.Pop().(*NumberValue).float64
|
||||
r := vm.stack.Pop().(*NumberValue).Number
|
||||
l := vm.stack.Pop().(*NumberValue).Number
|
||||
|
||||
vm.stack.Push(&BoolValue{l >= r})
|
||||
|
||||
|
|
@ -479,7 +660,7 @@ func (vm *VM) Next() bool {
|
|||
for i := len(f.Params) - 1; i >= 0; i-- {
|
||||
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
|
||||
vm.stack.items[p] = &VariableValue{
|
||||
f.Params[i],
|
||||
f.Params[i].Name,
|
||||
vm.stack.items[p],
|
||||
vm.scope,
|
||||
}
|
||||
|
|
@ -494,10 +675,10 @@ func (vm *VM) Next() bool {
|
|||
vm.chunk = f.Chunk
|
||||
vm.ip = 0
|
||||
case *BuiltinFunctionValue:
|
||||
args := map[string]Value{}
|
||||
args := make([]Value, len(f.Signature.In))
|
||||
|
||||
for i := len(f.Parameters) - 1; i >= 0; i-- {
|
||||
args[f.Parameters[i]] = vm.stack.Pop()
|
||||
for i := len(f.Signature.In) - 1; i >= 0; i-- {
|
||||
args[i] = vm.stack.Pop()
|
||||
}
|
||||
|
||||
v, err := f.F(vm, f.Parent, args)
|
||||
|
|
@ -519,12 +700,12 @@ func (vm *VM) Next() bool {
|
|||
|
||||
case InstructionJumpFalse:
|
||||
n := vm.NextU16()
|
||||
if !vm.stack.Pop().(*BoolValue).bool {
|
||||
if !vm.stack.Pop().(*BoolValue).Boolean {
|
||||
vm.ip += Pos(n)
|
||||
}
|
||||
|
||||
case InstructionGetLocal:
|
||||
name := vm.GetConstant(vm.NextByte()).(*StringValue).string
|
||||
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
|
||||
v := vm.getVar(name)
|
||||
|
||||
if v == nil {
|
||||
|
|
@ -536,7 +717,7 @@ func (vm *VM) Next() bool {
|
|||
|
||||
case InstructionSetLocal:
|
||||
value := vm.stack.Pop().(Value)
|
||||
name := vm.GetConstant(vm.NextByte()).(*StringValue).string
|
||||
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
|
||||
|
||||
v := vm.getVar(name)
|
||||
|
||||
|
|
@ -544,19 +725,19 @@ func (vm *VM) Next() bool {
|
|||
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
|
||||
}
|
||||
|
||||
v.value = value
|
||||
v.value = value.Clone()
|
||||
|
||||
case InstructionDeclareLocal:
|
||||
vm.addVar(
|
||||
vm.GetConstant(vm.NextByte()).(*StringValue).string,
|
||||
vm.stack.Pop().(Value),
|
||||
vm.GetConstant(vm.NextByte()).(*StringValue).Text,
|
||||
vm.stack.Pop().Clone(),
|
||||
)
|
||||
|
||||
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:
|
||||
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string] = vm.stack.Pop()
|
||||
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text] = vm.stack.Pop()
|
||||
|
||||
case InstructionTrue:
|
||||
vm.stack.Push(&BoolValue{true})
|
||||
|
|
@ -570,20 +751,32 @@ func (vm *VM) Next() bool {
|
|||
case InstructionFormList:
|
||||
n := int(vm.NextU16())
|
||||
|
||||
items := make([]Value, n+1)
|
||||
for i := 0; i <= n; i++ {
|
||||
items[n-i] = vm.stack.Pop()
|
||||
items := make([]Value, n)
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
items[i] = vm.stack.Pop()
|
||||
}
|
||||
|
||||
vm.stack.Push(&ListValue{
|
||||
items,
|
||||
})
|
||||
|
||||
case InstructionNewList:
|
||||
vm.stack.Push(&ListValue{[]Value{}})
|
||||
|
||||
case InstructionAppend:
|
||||
value := vm.stack.Pop()
|
||||
list := vm.stack.Pop().(*ListValue)
|
||||
list.items = append(list.items, value)
|
||||
list.Items = append(list.Items, value)
|
||||
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:
|
||||
vm.descend()
|
||||
|
||||
|
|
@ -595,8 +788,8 @@ func (vm *VM) Next() bool {
|
|||
vm.stack.Push(&StringValue{v.String()})
|
||||
|
||||
case InstructionStringConcatenation:
|
||||
r := vm.stack.Pop().(*StringValue).string
|
||||
l := vm.stack.Pop().(*StringValue).string
|
||||
r := vm.stack.Pop().(*StringValue).Text
|
||||
l := vm.stack.Pop().(*StringValue).Text
|
||||
|
||||
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++ {
|
||||
vm.addVar(f.Params[i], args[i])
|
||||
vm.addVar(f.Params[i].Name, args[i])
|
||||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
if vm.HasNext() {
|
||||
vm.Next()
|
||||
}
|
||||
|
||||
return vm.stack.Pop(), nil
|
||||
|
||||
case *BuiltinFunctionValue:
|
||||
argies := map[string]Value{}
|
||||
|
||||
for i, arg := range args {
|
||||
argies[f.Parameters[i]] = arg
|
||||
}
|
||||
|
||||
return f.F(vm, f.Parent, argies)
|
||||
return f.F(vm, f.Parent, args)
|
||||
}
|
||||
|
||||
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) {
|
||||
if !vm.HasNext() {
|
||||
return 0, errors.New("there are no more instructions")
|
||||
|
|
|
|||
|
|
@ -76,13 +76,13 @@ func TestNewVM(t *testing.T) {
|
|||
}
|
||||
|
||||
// should have given stack size
|
||||
if vm.stack.Size != stackSize {
|
||||
t.Errorf("vm.stack.Size = %d, want %d", vm.stack.Size, stackSize)
|
||||
if vm.stack.Capacity != stackSize {
|
||||
t.Errorf("vm.stack.Capacity = %d, want %d", vm.stack.Capacity, stackSize)
|
||||
}
|
||||
|
||||
// should have given call stack size
|
||||
if vm.call.Size != callstackSize {
|
||||
t.Errorf("vm.call.Size = %d, want %d", vm.call.Size, callstackSize)
|
||||
if vm.call.Capacity != callstackSize {
|
||||
t.Errorf("vm.call.Capacity = %d, want %d", vm.call.Capacity, callstackSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -442,7 +442,16 @@ func GetExecutionTestData() map[string]struct {
|
|||
&NumberValue{2},
|
||||
&FunctionValue{
|
||||
Name: "sum",
|
||||
Params: []string{"a", "b"},
|
||||
Params: []FunctionParameter{
|
||||
{
|
||||
"a",
|
||||
&NumberSignature{},
|
||||
},
|
||||
{
|
||||
"b",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
Chunk: NewChunk(
|
||||
[]Bytecode{
|
||||
InstructionGetLocal, 0,
|
||||
|
|
@ -476,7 +485,16 @@ func GetExecutionTestData() map[string]struct {
|
|||
&NumberValue{2},
|
||||
&FunctionValue{
|
||||
Name: "sum",
|
||||
Params: []string{"a", "b"},
|
||||
Params: []FunctionParameter{
|
||||
{
|
||||
"a",
|
||||
&NumberSignature{},
|
||||
},
|
||||
{
|
||||
"b",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
Chunk: NewChunk(
|
||||
[]Bytecode{
|
||||
InstructionGetLocal, 0,
|
||||
|
|
@ -493,7 +511,12 @@ func GetExecutionTestData() map[string]struct {
|
|||
},
|
||||
&FunctionValue{
|
||||
Name: "square",
|
||||
Params: []string{"n"},
|
||||
Params: []FunctionParameter{
|
||||
{
|
||||
"n",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
Chunk: NewChunk(
|
||||
[]Bytecode{
|
||||
InstructionGetLocal, 0,
|
||||
|
|
@ -514,7 +537,12 @@ func GetExecutionTestData() map[string]struct {
|
|||
"square",
|
||||
&FunctionValue{
|
||||
Name: "square",
|
||||
Params: []string{"n"},
|
||||
Params: []FunctionParameter{
|
||||
{
|
||||
"n",
|
||||
&NumberSignature{},
|
||||
},
|
||||
},
|
||||
Chunk: NewChunk(
|
||||
[]Bytecode{
|
||||
InstructionGetLocal, 0,
|
||||
|
|
@ -532,6 +560,37 @@ func GetExecutionTestData() map[string]struct {
|
|||
&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
2
emoji.ang
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
write(char(0x12) + char(0x85) + char(0x07))
|
||||
0
examples/brainfuck.ang
Normal file
0
examples/brainfuck.ang
Normal file
|
|
@ -18,7 +18,7 @@ while n <= terms {
|
|||
tot = tot * 6
|
||||
|
||||
# get the absolute value of a number
|
||||
func abs(x) {
|
||||
func abs(x: number) number {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ func abs(x) {
|
|||
# see: https://en.wikipedia.org/wiki/Newton's_method
|
||||
# The required accuracy
|
||||
SQRT_ACC := 0.00000001
|
||||
func sqrt(x) {
|
||||
func sqrt(x: number) number {
|
||||
pg := 0 # previous guess
|
||||
g := 1 # current guess
|
||||
|
||||
|
|
@ -45,4 +45,4 @@ func sqrt(x) {
|
|||
tot = sqrt(tot)
|
||||
|
||||
# output the result
|
||||
write(tot)
|
||||
write(str(tot))
|
||||
|
|
|
|||
36
examples/pi-approx.py
Normal file
36
examples/pi-approx.py
Normal 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
16
examples/pøck.ang
Normal 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()
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
|
||||
# fibonacci sequence
|
||||
func fib(n) {
|
||||
if n <= 1 {
|
||||
return n
|
||||
# This program computes the fibonacci numbers using recursion (O(2^n))
|
||||
# It is very slow
|
||||
func fib(x: number) number {
|
||||
if x <= 1 {
|
||||
return x
|
||||
}
|
||||
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
return fib(x - 1) + fib(x - 2)
|
||||
}
|
||||
|
||||
n := 0
|
||||
while n < 10 {
|
||||
write(fib(n))
|
||||
while n < 100 {
|
||||
write(str(fib(n)))
|
||||
n = n + 1
|
||||
}
|
||||
|
|
|
|||
13
examples/solving.ang
Normal file
13
examples/solving.ang
Normal 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
4
fails.ang
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import "lib/honning.ang"
|
||||
|
||||
write(_bell+_italic+"Hello "+_underline+"world "+_strike+"micheal"+_reset)
|
||||
|
||||
15
imp.ang
Normal file
15
imp.ang
Normal 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
15
lib/honning.ang
Normal 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
12
lib/list.ang
Normal 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
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ E := 2.718281828459045235360287471352
|
|||
# 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
|
||||
# returned value is x.
|
||||
func abs(x) {
|
||||
func abs(x: number) number {
|
||||
# if the number is negative
|
||||
if x < 0 {
|
||||
# negate it so it's positive
|
||||
|
|
@ -17,13 +17,31 @@ func abs(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
|
||||
|
||||
# sqrt(x)
|
||||
# x: number
|
||||
# Calculate the approximate square root using newton's method until
|
||||
# the accuracy has increased by less than the variable `MAX_SQRT_DX`.
|
||||
func sqrt(x) {
|
||||
func sqrt(x: number) number {
|
||||
ng := x
|
||||
g := 1
|
||||
|
||||
|
|
@ -42,23 +60,19 @@ func sqrt(x) {
|
|||
# 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
|
||||
# whole number which is less than or equal to x is returned.
|
||||
func floor(x) {
|
||||
# todo
|
||||
}
|
||||
|
||||
|
||||
# ceil(x)
|
||||
# x: 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
|
||||
# whole number which is greater than or equal to x is returned.
|
||||
func ceil(x) {
|
||||
# todo
|
||||
}
|
||||
|
||||
|
||||
# round(x)
|
||||
# x: number
|
||||
# Return the closest whole number to the value x.
|
||||
func round(x) {
|
||||
func round(x: number) number {
|
||||
f := floor(x)
|
||||
|
||||
if x - f > 0.5 {
|
||||
|
|
@ -68,15 +82,128 @@ func round(x) {
|
|||
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)
|
||||
# x: number; an angle in radians
|
||||
# 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
|
||||
func sin(x) {
|
||||
func sin(x: number) number {
|
||||
f := 1
|
||||
x = mod(x, 2*PI)
|
||||
if x > PI {
|
||||
x = -x
|
||||
x = PI - x
|
||||
f = -1
|
||||
}
|
||||
|
||||
|
|
@ -101,106 +228,13 @@ func sin(x) {
|
|||
# cos(x)
|
||||
# x: number; an angle in radians
|
||||
# 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
|
||||
}
|
||||
|
||||
# tan(x)
|
||||
# x: number; an angle in radians
|
||||
# Get the tangent of an angle. https://en.wikipedia.org/wiki/Tangent
|
||||
func tan(x) {
|
||||
func tan(x: number) number {
|
||||
# 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
25
lib/testing.ang
Normal 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
4
lib/util.ang
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
func memoize(f) {
|
||||
|
||||
}
|
||||
56
test_all.sh
56
test_all.sh
|
|
@ -1,22 +1,46 @@
|
|||
#!/bin/zsh
|
||||
#!/bin/bash
|
||||
|
||||
echo '== Building CLI =='
|
||||
cd cli
|
||||
go build .
|
||||
cd ..
|
||||
|
||||
|
||||
echo '== Testing anglais =='
|
||||
|
||||
for file in ./tests/*.ang; do
|
||||
echo "-- Test-running file $file --"
|
||||
|
||||
if ! ./cli/cli run "$file"; then
|
||||
echo "-- Error --"
|
||||
echo '=== Building CLI ==='
|
||||
cd cli || exit 1
|
||||
if ! go build .; then
|
||||
echo "=== Had error building CLI ==="
|
||||
exit 1
|
||||
else
|
||||
echo "-- Success --"
|
||||
echo "=+= Successfully built CLI =+="
|
||||
fi
|
||||
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 ..
|
||||
|
||||
errors=()
|
||||
|
||||
echo '=== Testing anglais ==='
|
||||
|
||||
# read files
|
||||
for file in $(find tests -type f); do
|
||||
echo "-v- Test-running file $file -v-"
|
||||
|
||||
if ! ./cli/cli run "$file"; then
|
||||
echo "-x- Error -x-"
|
||||
errors+=("$file")
|
||||
else
|
||||
echo "-+- Success -+-"
|
||||
fi
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ assertEq(1, 1)
|
|||
assertEq(0, 0)
|
||||
assertEq("", "")
|
||||
|
||||
assertEq([], [])
|
||||
assertEq([]number, []number)
|
||||
assertEq([3, 1, 4, 1], [3, 1, 4, 1])
|
||||
assertEq([true, 1024, nil, "Hello world!"], [true, 1024, nil, "Hello world!"])
|
||||
|
||||
# Inequality
|
||||
assertNotEq(2, 3)
|
||||
|
|
|
|||
3
tests/hex.ang
Normal file
3
tests/hex.ang
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
assertEq(0x00, 0)
|
||||
assertEq(0xFF, 255)
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
list := []
|
||||
list := []number
|
||||
|
||||
x := 1
|
||||
while x <= 1000 {
|
||||
list.append(x)
|
||||
assertEq(list.reduce(func(tot, a){
|
||||
assertEq(list.reduce(func(tot: number, a: number) number {
|
||||
return tot + a
|
||||
}, 0), x*(x + 1)/2)
|
||||
|
||||
x = x + 1
|
||||
}
|
||||
|
||||
func sum(a, b) {
|
||||
func sum(a: number, b: number) number {
|
||||
return a + b
|
||||
}
|
||||
|
||||
list = []
|
||||
list = []number
|
||||
x = 1
|
||||
while x <= 100 {
|
||||
list.append(2*x - 1)
|
||||
|
|
@ -22,3 +22,6 @@ while x <= 100 {
|
|||
|
||||
x = x + 1
|
||||
}
|
||||
|
||||
assertEq([1, 2] + [3], [1, 2, 3])
|
||||
assertEq(["Eny", "meanie"] + ["minie", "moe"], ["Eny", "meanie", "minie", "moe"])
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@
|
|||
fibonacci_numbers := [
|
||||
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
|
||||
610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657,
|
||||
46368, 75025, 121393, 196418, 317811, 514229, 832040,
|
||||
1346269, 2178309, 3524578, 5702887, 9227465, 14930352
|
||||
46368, 75025, 121393, 196418, 317811, 514229, 832040
|
||||
]
|
||||
|
||||
func fib(n) {
|
||||
func fib(n: number) number {
|
||||
if n < 2 {
|
||||
return n
|
||||
}
|
||||
|
|
@ -16,11 +15,12 @@ func fib(n) {
|
|||
|
||||
n := 0
|
||||
while n < fibonacci_numbers.length() {
|
||||
print("_")
|
||||
print("-")
|
||||
n = n + 1
|
||||
}
|
||||
|
||||
write("")
|
||||
# return to start of line (with carriage return \r)
|
||||
print(char(0x0D))
|
||||
|
||||
x := 0
|
||||
while x < fibonacci_numbers.length() {
|
||||
|
|
|
|||
13
tests/refs.ang
Normal file
13
tests/refs.ang
Normal 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])
|
||||
|
|
@ -4,9 +4,12 @@ a := 2
|
|||
{
|
||||
a := 3
|
||||
assertEq(a, 3)
|
||||
breakpoint
|
||||
|
||||
a = 4
|
||||
assertEq(a, 4)
|
||||
breakpoint
|
||||
}
|
||||
|
||||
assertEq(a, 2)
|
||||
breakpoint
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
func sum(a, b) {
|
||||
func sum(a: number, b: number) number {
|
||||
return a + b
|
||||
}
|
||||
|
||||
|
|
|
|||
8
tests/types.ang
Normal file
8
tests/types.ang
Normal 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]")
|
||||
76
wasm/wasm.go
76
wasm/wasm.go
|
|
@ -13,32 +13,22 @@ type JsResolver struct {
|
|||
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)
|
||||
|
||||
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 {
|
||||
return nil, errors.New("invalid value for source: " + jsv.String())
|
||||
return "", errors.New("invalid value for source: " + jsv.String())
|
||||
}
|
||||
|
||||
source := jsv.String()
|
||||
|
||||
l := core.NewLexer(source)
|
||||
tokens, err := l.Tokenize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := core.NewParser(tokens)
|
||||
tree, err := p.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tree, nil
|
||||
return jsv.String(), nil
|
||||
}
|
||||
|
||||
func jsError(err error) interface{} {
|
||||
|
|
@ -55,7 +45,8 @@ func jsErrorOfString(err string) interface{} {
|
|||
func run(_ js.Value, args []js.Value) interface{} {
|
||||
source := args[0].String()
|
||||
outputHandler := args[1]
|
||||
resolver := args[2]
|
||||
errorHandler := args[2]
|
||||
resolver := args[3]
|
||||
log.Printf("got source: %s", source)
|
||||
|
||||
lexer := core.NewLexer(source)
|
||||
|
|
@ -67,17 +58,25 @@ func run(_ js.Value, args []js.Value) interface{} {
|
|||
|
||||
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 {
|
||||
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())
|
||||
|
||||
compiler := core.NewCompiler()
|
||||
compiler := core.NewCompiler([]rune(source))
|
||||
|
||||
log.Println("Set imports resolver")
|
||||
|
||||
compiler.SetImportsResolver(&JsResolver{
|
||||
resolver,
|
||||
|
|
@ -91,6 +90,13 @@ func run(_ js.Value, args []js.Value) interface{} {
|
|||
|
||||
err = compiler.Compile(tree)
|
||||
if err != nil {
|
||||
var e core.CompilerError
|
||||
if errors.As(err, &e) {
|
||||
errorHandler.Invoke(e.Format())
|
||||
return nil
|
||||
}
|
||||
|
||||
errorHandler.Invoke(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -101,19 +107,27 @@ func run(_ js.Value, args []js.Value) interface{} {
|
|||
// overwrite output
|
||||
vm.SetGlobal("write", &core.BuiltinFunctionValue{
|
||||
Name: "write",
|
||||
Parameters: []string{"value"},
|
||||
F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) {
|
||||
log.Printf("Writing value: %s", v["value"].String())
|
||||
outputHandler.Invoke(js.ValueOf(v["value"].String() + "\n"))
|
||||
Signature: &core.FunctionSignature{
|
||||
In: []core.TypeSignature{&core.StringSignature{}},
|
||||
Out: &core.NilSignature{},
|
||||
},
|
||||
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
|
||||
},
|
||||
})
|
||||
vm.SetGlobal("print", &core.BuiltinFunctionValue{
|
||||
Name: "print",
|
||||
Parameters: []string{"value"},
|
||||
F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) {
|
||||
log.Printf("Printing value: %s", v["value"].String())
|
||||
outputHandler.Invoke(js.ValueOf(v["value"].String()))
|
||||
Signature: &core.FunctionSignature{
|
||||
In: []core.TypeSignature{&core.StringSignature{}},
|
||||
Out: &core.NilSignature{},
|
||||
},
|
||||
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
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue