separate core from cli into submodules, add support for boolean and and or operations, make more fields public, fix passing arguments to functions
This commit is contained in:
parent
7498c85424
commit
62656c6dff
19 changed files with 202 additions and 99 deletions
10
cli/go.mod
Normal file
10
cli/go.mod
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
module neemek.com/angalis/cli
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/kong v1.5.1
|
||||||
|
neemek.com/anglais/core v0.0.0-00010101000000-000000000000
|
||||||
|
)
|
||||||
|
|
||||||
|
replace neemek.com/anglais/core => ../core
|
||||||
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"log"
|
"log"
|
||||||
|
"neemek.com/anglais/core"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -26,14 +27,14 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var chunk *Chunk
|
var chunk *core.Chunk
|
||||||
if !cmd.Bytecode {
|
if !cmd.Bytecode {
|
||||||
src := string(f)
|
src := string(f)
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initialized lexer")
|
log.Println("Initialized lexer")
|
||||||
}
|
}
|
||||||
l := NewLexer(src)
|
l := core.NewLexer(src)
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Lexing all tokens")
|
log.Println("Lexing all tokens")
|
||||||
|
|
@ -52,7 +53,7 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
||||||
log.Printf("Lexed %d tokens", len(tokens))
|
log.Printf("Lexed %d tokens", len(tokens))
|
||||||
|
|
||||||
}
|
}
|
||||||
p := NewParser(tokens)
|
p := core.NewParser(tokens)
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initialized parser")
|
log.Println("Initialized parser")
|
||||||
|
|
@ -63,8 +64,8 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
||||||
if r != "no more tokens" { // if the panic was not caused by the parser, it should not be recovered.
|
if r != "no more tokens" { // if the panic was not caused by the parser, it should not be recovered.
|
||||||
panic(r)
|
panic(r)
|
||||||
}
|
}
|
||||||
for _, e := range p.errors {
|
for _, e := range p.Errors {
|
||||||
e.Print(src)
|
print(e.Format(src))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -72,9 +73,9 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
||||||
tree := p.Parse()
|
tree := p.Parse()
|
||||||
|
|
||||||
// if there were parsing errors, print them out
|
// if there were parsing errors, print them out
|
||||||
if len(p.errors) > 0 {
|
if len(p.Errors) > 0 {
|
||||||
for _, e := range p.errors {
|
for _, e := range p.Errors {
|
||||||
e.Print(src)
|
print(e.Format(src))
|
||||||
}
|
}
|
||||||
log.Fatal("Parsing had errors")
|
log.Fatal("Parsing had errors")
|
||||||
}
|
}
|
||||||
|
|
@ -82,26 +83,26 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initialized compiler")
|
log.Println("Initialized compiler")
|
||||||
}
|
}
|
||||||
c := NewCompiler()
|
c := core.NewCompiler()
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Compiling parse tree")
|
log.Println("Compiling parse tree")
|
||||||
}
|
}
|
||||||
c.Compile(tree)
|
c.Compile(tree)
|
||||||
|
|
||||||
chunk = c.chunk
|
chunk = c.Chunk
|
||||||
} else {
|
} else {
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Registering GOB types")
|
log.Println("Registering GOB types")
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterGOBTypes()
|
core.RegisterGOBTypes()
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Deserializing file")
|
log.Println("Deserializing file")
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk = DeserializeChunk(f)
|
chunk = core.DeserializeChunk(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
|
|
@ -111,7 +112,7 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
||||||
|
|
||||||
log.Println("Initialized VM")
|
log.Println("Initialized VM")
|
||||||
}
|
}
|
||||||
vm := NewVM(chunk, 256, 256)
|
vm := core.NewVM(chunk, 256, 256)
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Executing bytecode")
|
log.Println("Executing bytecode")
|
||||||
|
|
@ -129,7 +130,7 @@ type CompileCmd struct {
|
||||||
Output string `arg:"" name:"output" optional:"" help:"File path to output bytecode to" type:"path"`
|
Output string `arg:"" name:"output" optional:"" help:"File path to output bytecode to" type:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *CompileCmd) Run(ctx *Context) error {
|
func (cmd *CompileCmd) Compile(ctx *Context) error {
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Reading file")
|
log.Println("Reading file")
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +146,7 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initializing lexer")
|
log.Println("Initializing lexer")
|
||||||
}
|
}
|
||||||
l := NewLexer(src)
|
l := core.NewLexer(src)
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Lexing all tokens")
|
log.Println("Lexing all tokens")
|
||||||
|
|
@ -159,7 +160,7 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initializing parser")
|
log.Println("Initializing parser")
|
||||||
}
|
}
|
||||||
p := NewParser(tokens)
|
p := core.NewParser(tokens)
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Parsing tree")
|
log.Println("Parsing tree")
|
||||||
|
|
@ -169,7 +170,7 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initialized compiler")
|
log.Println("Initialized compiler")
|
||||||
}
|
}
|
||||||
c := NewCompiler()
|
c := core.NewCompiler()
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Compiling parse tree")
|
log.Println("Compiling parse tree")
|
||||||
|
|
@ -181,13 +182,13 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
|
||||||
log.Println("Registering GOB types")
|
log.Println("Registering GOB types")
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterGOBTypes()
|
core.RegisterGOBTypes()
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Serializing chunk")
|
log.Println("Serializing chunk")
|
||||||
}
|
}
|
||||||
|
|
||||||
serialized := c.chunk.Serialize()
|
serialized := c.Chunk.Serialize()
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Writing file")
|
log.Println("Writing file")
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -46,8 +46,8 @@ func TestAll(t *testing.T) {
|
||||||
tree := p.Parse()
|
tree := p.Parse()
|
||||||
|
|
||||||
if p.hadError {
|
if p.hadError {
|
||||||
for _, e := range p.errors {
|
for _, e := range p.Errors {
|
||||||
e.Print(tc.src)
|
print(e.Format(tc.src))
|
||||||
}
|
}
|
||||||
t.Fatalf("parser had error(s)")
|
t.Fatalf("parser had error(s)")
|
||||||
}
|
}
|
||||||
|
|
@ -58,10 +58,10 @@ func TestAll(t *testing.T) {
|
||||||
t.Log("Compiling parse tree")
|
t.Log("Compiling parse tree")
|
||||||
c.Compile(tree)
|
c.Compile(tree)
|
||||||
|
|
||||||
printChunk(t, name, c.chunk)
|
printChunk(t, name, c.Chunk)
|
||||||
|
|
||||||
t.Log("Initializing vm")
|
t.Log("Initializing vm")
|
||||||
vm := NewVM(c.chunk, 256, 256)
|
vm := NewVM(c.Chunk, 256, 256)
|
||||||
|
|
||||||
t.Log("Running bytecode")
|
t.Log("Running bytecode")
|
||||||
for vm.HasNext() && vm.Next() {
|
for vm.HasNext() && vm.Next() {
|
||||||
|
|
@ -87,7 +87,7 @@ func BenchmarkAll(b *testing.B) {
|
||||||
c := NewCompiler()
|
c := NewCompiler()
|
||||||
c.Compile(tree)
|
c.Compile(tree)
|
||||||
|
|
||||||
vm := NewVM(c.chunk, 256, 256)
|
vm := NewVM(c.Chunk, 256, 256)
|
||||||
|
|
||||||
for vm.HasNext() && vm.Next() {
|
for vm.HasNext() && vm.Next() {
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
type Compiler struct {
|
type Compiler struct {
|
||||||
chunk *Chunk
|
Chunk *Chunk
|
||||||
ip Pos
|
ip Pos
|
||||||
scope Pos
|
scope Pos
|
||||||
|
|
||||||
|
|
@ -17,7 +15,7 @@ type LocalVariable struct {
|
||||||
|
|
||||||
func NewCompiler() *Compiler {
|
func NewCompiler() *Compiler {
|
||||||
c := &Compiler{
|
c := &Compiler{
|
||||||
chunk: NewChunk(make([]Bytecode, 0), make([]Value, 0)),
|
Chunk: NewChunk(make([]Bytecode, 0), make([]Value, 0)),
|
||||||
ip: 0,
|
ip: 0,
|
||||||
scope: 0,
|
scope: 0,
|
||||||
stack: NewStack[LocalVariable](256),
|
stack: NewStack[LocalVariable](256),
|
||||||
|
|
@ -27,17 +25,17 @@ func NewCompiler() *Compiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) add(instruction Bytecode) {
|
func (c *Compiler) add(instruction Bytecode) {
|
||||||
for len(c.chunk.Bytecode) <= int(c.ip) {
|
for len(c.Chunk.Bytecode) <= int(c.ip) {
|
||||||
c.chunk.Bytecode = append(c.chunk.Bytecode, 0)
|
c.Chunk.Bytecode = append(c.Chunk.Bytecode, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.chunk.Bytecode[c.ip] = instruction
|
c.Chunk.Bytecode[c.ip] = instruction
|
||||||
|
|
||||||
c.advance(1)
|
c.advance(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) addConstant(value Value) {
|
func (c *Compiler) addConstant(value Value) {
|
||||||
chunk := c.chunk
|
chunk := c.Chunk
|
||||||
for i := 0; i < len(chunk.Constants); i++ {
|
for i := 0; i < len(chunk.Constants); i++ {
|
||||||
if chunk.Constants[i] == value {
|
if chunk.Constants[i] == value {
|
||||||
c.add(Bytecode(i))
|
c.add(Bytecode(i))
|
||||||
|
|
@ -168,19 +166,19 @@ func (c *Compiler) Compile(tree Node) {
|
||||||
case FunctionNodeType:
|
case FunctionNodeType:
|
||||||
n := tree.(*FunctionNode)
|
n := tree.(*FunctionNode)
|
||||||
|
|
||||||
fi := len(c.chunk.Constants)
|
fi := len(c.Chunk.Constants)
|
||||||
c.chunk.Constants = append(c.chunk.Constants, nil)
|
c.Chunk.Constants = append(c.Chunk.Constants, nil)
|
||||||
|
|
||||||
c.add(InstructionConstant)
|
c.add(InstructionConstant)
|
||||||
c.add(Bytecode(fi))
|
c.add(Bytecode(fi))
|
||||||
|
|
||||||
// keep track of main chunk
|
// keep track of main chunk
|
||||||
mc := c.chunk
|
mc := c.Chunk
|
||||||
// and ip
|
// and ip
|
||||||
mip := c.ip
|
mip := c.ip
|
||||||
|
|
||||||
// assign a new empty chunk
|
// assign a new empty chunk
|
||||||
c.chunk = NewChunk(make([]Bytecode, 0), make([]Value, 0))
|
c.Chunk = NewChunk(make([]Bytecode, 0), make([]Value, 0))
|
||||||
// reset instruction pointer (ip)
|
// reset instruction pointer (ip)
|
||||||
c.ip = 0
|
c.ip = 0
|
||||||
|
|
||||||
|
|
@ -195,16 +193,19 @@ func (c *Compiler) Compile(tree Node) {
|
||||||
mc.Constants[fi] = FunctionValue{
|
mc.Constants[fi] = FunctionValue{
|
||||||
n.name,
|
n.name,
|
||||||
n.params,
|
n.params,
|
||||||
c.chunk,
|
c.Chunk,
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore old chunk and ip
|
// restore old chunk and ip
|
||||||
c.chunk = mc
|
c.Chunk = mc
|
||||||
c.ip = mip
|
c.ip = mip
|
||||||
|
|
||||||
case ReturnNodeType:
|
case ReturnNodeType:
|
||||||
c.Compile(tree.(*ReturnNode).value)
|
c.Compile(tree.(*ReturnNode).value)
|
||||||
c.add(InstructionReturn)
|
c.add(InstructionReturn)
|
||||||
|
|
||||||
|
case BreakpointNodeType:
|
||||||
|
c.add(InstructionBreakpoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,18 +234,20 @@ func (c *Compiler) compileBinary(binary *BinaryNode) {
|
||||||
c.add(InstructionLessOrEqual)
|
c.add(InstructionLessOrEqual)
|
||||||
case BinaryGreaterEqual:
|
case BinaryGreaterEqual:
|
||||||
c.add(InstructionGreaterOrEqual)
|
c.add(InstructionGreaterOrEqual)
|
||||||
|
case BinaryAnd:
|
||||||
|
c.add(InstructionAnd)
|
||||||
|
case BinaryOr:
|
||||||
|
c.add(InstructionOr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) getVar(name string) {
|
func (c *Compiler) getVar(name string) {
|
||||||
if c.isLocal(name) {
|
if c.isGlobal(name) {
|
||||||
c.add(InstructionGetLocal)
|
|
||||||
c.addConstant(StringValue(name))
|
|
||||||
} else if c.isGlobal(name) {
|
|
||||||
c.add(InstructionGetGlobal)
|
c.add(InstructionGetGlobal)
|
||||||
c.addConstant(StringValue(name))
|
c.addConstant(StringValue(name))
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("compiling: undefined variable %s", name)
|
c.add(InstructionGetLocal)
|
||||||
|
c.addConstant(StringValue(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -16,7 +16,7 @@ func TestNewCompiler(t *testing.T) {
|
||||||
t.Error("compiler ip doesn't start at zero")
|
t.Error("compiler ip doesn't start at zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.chunk == nil {
|
if c.Chunk == nil {
|
||||||
t.Error("compiler chunk initialized to nil")
|
t.Error("compiler chunk initialized to nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -361,10 +361,10 @@ func TestCompile(t *testing.T) {
|
||||||
c.Compile(testCase.tree)
|
c.Compile(testCase.tree)
|
||||||
|
|
||||||
t.Log("Initializing vm")
|
t.Log("Initializing vm")
|
||||||
vm := NewVM(c.chunk, 256, 256)
|
vm := NewVM(c.Chunk, 256, 256)
|
||||||
|
|
||||||
t.Log("Printing chunk for debug")
|
t.Log("Printing chunk for debug")
|
||||||
printChunk(t, name, c.chunk)
|
printChunk(t, name, c.Chunk)
|
||||||
|
|
||||||
t.Log("Executing bytecode")
|
t.Log("Executing bytecode")
|
||||||
for vm.HasNext() && vm.Next() {
|
for vm.HasNext() && vm.Next() {
|
||||||
|
|
@ -394,12 +394,12 @@ func TestCompiler_AddU16(t *testing.T) {
|
||||||
c := NewCompiler()
|
c := NewCompiler()
|
||||||
c.addU16(uint16(i))
|
c.addU16(uint16(i))
|
||||||
|
|
||||||
if c.chunk.Bytecode[0] != Bytecode(i>>8) {
|
if c.Chunk.Bytecode[0] != Bytecode(i>>8) {
|
||||||
t.Errorf("first 8 bits don't match (got %s, expected %b)", c.chunk.Bytecode[0], byte(i>>8))
|
t.Errorf("first 8 bits don't match (got %s, expected %b)", c.Chunk.Bytecode[0], byte(i>>8))
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.chunk.Bytecode[1] != Bytecode(i&0xff) {
|
if c.Chunk.Bytecode[1] != Bytecode(i&0xff) {
|
||||||
t.Errorf("last 8 bits don't match (got %s, expected %b)", c.chunk.Bytecode[1], byte(i&0xff))
|
t.Errorf("last 8 bits don't match (got %s, expected %b)", c.Chunk.Bytecode[1], byte(i&0xff))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -432,7 +432,7 @@ func TestCompiler_CleanStack(t *testing.T) {
|
||||||
c := NewCompiler()
|
c := NewCompiler()
|
||||||
c.Compile(tc.tree)
|
c.Compile(tc.tree)
|
||||||
|
|
||||||
vm := NewVM(c.chunk, 256, 256)
|
vm := NewVM(c.Chunk, 256, 256)
|
||||||
for vm.HasNext() && vm.Next() {
|
for vm.HasNext() && vm.Next() {
|
||||||
}
|
}
|
||||||
|
|
||||||
3
core/go.mod
Normal file
3
core/go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module neemek.com/anglais/core
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -60,6 +60,10 @@ const (
|
||||||
TokenGreaterThanOrEqual
|
TokenGreaterThanOrEqual
|
||||||
TokenLessThanOrEqual
|
TokenLessThanOrEqual
|
||||||
|
|
||||||
|
TokenDoubleAmpersand
|
||||||
|
TokenDoublePipe
|
||||||
|
|
||||||
|
TokenBreakpoint
|
||||||
TokenEOF
|
TokenEOF
|
||||||
TokenError
|
TokenError
|
||||||
)
|
)
|
||||||
|
|
@ -134,6 +138,12 @@ func (t TokenType) String() string {
|
||||||
return "comma"
|
return "comma"
|
||||||
case TokenDot:
|
case TokenDot:
|
||||||
return "dot"
|
return "dot"
|
||||||
|
case TokenBreakpoint:
|
||||||
|
return "breakpoint"
|
||||||
|
case TokenDoubleAmpersand:
|
||||||
|
return "double ampersand"
|
||||||
|
case TokenDoublePipe:
|
||||||
|
return "double pipe"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "UNDEFINED TOKENTYPE STRING CONVERSION"
|
return "UNDEFINED TOKENTYPE STRING CONVERSION"
|
||||||
|
|
@ -241,6 +251,20 @@ func (l *Lexer) NextToken() (Token, error) {
|
||||||
|
|
||||||
return l.makeToken(TokenLessThan), nil
|
return l.makeToken(TokenLessThan), nil
|
||||||
|
|
||||||
|
case '&':
|
||||||
|
if l.accept('&') {
|
||||||
|
return l.makeToken(TokenDoubleAmpersand), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.makeToken(TokenError), errors.New("malformed token (got '&', expected '&' to follow)")
|
||||||
|
|
||||||
|
case '|':
|
||||||
|
if l.accept('|') {
|
||||||
|
return l.makeToken(TokenDoublePipe), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.makeToken(TokenError), errors.New("malformed token (got '|', expected '|' to follow)")
|
||||||
|
|
||||||
case '"':
|
case '"':
|
||||||
// include ending quote
|
// include ending quote
|
||||||
for !l.accept('"') {
|
for !l.accept('"') {
|
||||||
|
|
@ -281,6 +305,8 @@ func (l *Lexer) NextToken() (Token, error) {
|
||||||
return l.makeToken(TokenFunc), nil
|
return l.makeToken(TokenFunc), nil
|
||||||
case "while":
|
case "while":
|
||||||
return l.makeToken(TokenWhile), nil
|
return l.makeToken(TokenWhile), nil
|
||||||
|
case "breakpoint":
|
||||||
|
return l.makeToken(TokenBreakpoint), nil
|
||||||
case "return":
|
case "return":
|
||||||
return l.makeToken(TokenReturn), nil
|
return l.makeToken(TokenReturn), nil
|
||||||
default:
|
default:
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -135,6 +135,9 @@ const (
|
||||||
BinaryMultiplication
|
BinaryMultiplication
|
||||||
BinaryDivision
|
BinaryDivision
|
||||||
|
|
||||||
|
BinaryAnd
|
||||||
|
BinaryOr
|
||||||
|
|
||||||
// Comparison
|
// Comparison
|
||||||
BinaryEquality
|
BinaryEquality
|
||||||
BinaryInequality
|
BinaryInequality
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ParsingError struct {
|
type ParsingError struct {
|
||||||
|
|
@ -13,7 +14,9 @@ type ParsingError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print a rich and informative error
|
// Print a rich and informative error
|
||||||
func (p *ParsingError) Print(src string) {
|
func (p *ParsingError) Format(src string) string {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
|
||||||
lineNumber := 1
|
lineNumber := 1
|
||||||
lineBeginning := 0
|
lineBeginning := 0
|
||||||
for i := 0; i < int(p.Causer.Start); i++ {
|
for i := 0; i < int(p.Causer.Start); i++ {
|
||||||
|
|
@ -31,20 +34,23 @@ func (p *ParsingError) Print(src string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print(" \t v ")
|
builder.WriteString(" \t v ")
|
||||||
println(p.Description)
|
builder.WriteString(p.Description)
|
||||||
|
builder.WriteRune('\n')
|
||||||
|
|
||||||
println(fmt.Sprintf(" %d:%d\t | %s", lineNumber, int(p.Causer.Start)-lineBeginning+1, src[lineBeginning:lineEnd]))
|
builder.WriteString(fmt.Sprintf(" %d:%d\t | %s", lineNumber, int(p.Causer.Start)-lineBeginning+1, src[lineBeginning:lineEnd]))
|
||||||
|
|
||||||
print("\t ^")
|
builder.WriteString("\t ^")
|
||||||
for i := lineBeginning; i <= int(p.Causer.Start); i++ {
|
for i := lineBeginning; i <= int(p.Causer.Start); i++ {
|
||||||
print(" ")
|
builder.WriteRune(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < int(p.Causer.Length); i++ {
|
for i := 0; i < int(p.Causer.Length); i++ {
|
||||||
print("^")
|
builder.WriteRune('^')
|
||||||
}
|
}
|
||||||
println()
|
builder.WriteRune('\n')
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
|
|
@ -54,7 +60,7 @@ type Parser struct {
|
||||||
pos Pos
|
pos Pos
|
||||||
|
|
||||||
hadError bool
|
hadError bool
|
||||||
errors []ParsingError
|
Errors []ParsingError
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParser(tokens []Token) *Parser {
|
func NewParser(tokens []Token) *Parser {
|
||||||
|
|
@ -62,7 +68,7 @@ func NewParser(tokens []Token) *Parser {
|
||||||
tokens: tokens,
|
tokens: tokens,
|
||||||
pos: 0,
|
pos: 0,
|
||||||
hadError: false,
|
hadError: false,
|
||||||
errors: make([]ParsingError, 0),
|
Errors: make([]ParsingError, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +105,7 @@ func (p *Parser) accept(tokenType TokenType) bool {
|
||||||
func (p *Parser) expect(tokenType TokenType) {
|
func (p *Parser) expect(tokenType TokenType) {
|
||||||
if !p.accept(tokenType) {
|
if !p.accept(tokenType) {
|
||||||
p.error("Expected token "+tokenType.String()+", got "+p.curr.Type.String(), p.curr)
|
p.error("Expected token "+tokenType.String()+", got "+p.curr.Type.String(), p.curr)
|
||||||
p.advance() // just move on
|
p.advance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +130,7 @@ func (p *Parser) advance() {
|
||||||
|
|
||||||
func (p *Parser) error(error string, causer *Token) {
|
func (p *Parser) error(error string, causer *Token) {
|
||||||
p.hadError = true
|
p.hadError = true
|
||||||
p.errors = append(p.errors, ParsingError{
|
p.Errors = append(p.Errors, ParsingError{
|
||||||
Description: error,
|
Description: error,
|
||||||
Causer: causer,
|
Causer: causer,
|
||||||
})
|
})
|
||||||
|
|
@ -257,7 +263,7 @@ func (p *Parser) term() Node {
|
||||||
return left
|
return left
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) condition() Node {
|
func (p *Parser) comparison() Node {
|
||||||
left := p.term()
|
left := p.term()
|
||||||
op := BinaryEquality
|
op := BinaryEquality
|
||||||
|
|
||||||
|
|
@ -287,6 +293,28 @@ func (p *Parser) condition() Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Parser) condition() Node {
|
||||||
|
left := p.comparison()
|
||||||
|
op := BinaryEquality
|
||||||
|
|
||||||
|
switch (*p.curr).Type {
|
||||||
|
case TokenDoubleAmpersand:
|
||||||
|
op = BinaryAnd
|
||||||
|
case TokenDoublePipe:
|
||||||
|
op = BinaryOr
|
||||||
|
default:
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
p.advance()
|
||||||
|
|
||||||
|
return &BinaryNode{
|
||||||
|
op,
|
||||||
|
left,
|
||||||
|
p.comparison(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Parser) statement() Node {
|
func (p *Parser) statement() Node {
|
||||||
switch (*p.curr).Type {
|
switch (*p.curr).Type {
|
||||||
case TokenIf:
|
case TokenIf:
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -605,7 +605,7 @@ func TestParser_Parse(t *testing.T) {
|
||||||
tree := p.Parse()
|
tree := p.Parse()
|
||||||
|
|
||||||
if p.hadError {
|
if p.hadError {
|
||||||
t.Fatalf("Unexpected error(s): %s", p.errors)
|
t.Fatalf("Unexpected error(s): %s", p.Errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("Checking parsed tree")
|
t.Logf("Checking parsed tree")
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
type Stack[T any] struct {
|
type Stack[T any] struct {
|
||||||
Current Pos
|
Current Pos
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -126,9 +126,9 @@ func (v FunctionValue) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BuiltinFunctionValue struct {
|
type BuiltinFunctionValue struct {
|
||||||
name string
|
Name string
|
||||||
parameters []string
|
Parameters []string
|
||||||
f func(map[string]Value) Value
|
F func(map[string]Value) Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v BuiltinFunctionValue) Type() ValueType {
|
func (v BuiltinFunctionValue) Type() ValueType {
|
||||||
|
|
@ -136,7 +136,7 @@ func (v BuiltinFunctionValue) Type() ValueType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v BuiltinFunctionValue) String() string {
|
func (v BuiltinFunctionValue) String() string {
|
||||||
return fmt.Sprintf("<function name=%s builtin>", v.name)
|
return fmt.Sprintf("<function name=%s builtin>", v.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VariableValue a value wrapper for variables kept on the stack
|
// VariableValue a value wrapper for variables kept on the stack
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
|
|
@ -56,22 +56,22 @@ func CompareValues(t *testing.T, got Value, want Value) {
|
||||||
n := got.(BuiltinFunctionValue)
|
n := got.(BuiltinFunctionValue)
|
||||||
m := want.(BuiltinFunctionValue)
|
m := want.(BuiltinFunctionValue)
|
||||||
|
|
||||||
if n.name != m.name {
|
if n.Name != m.Name {
|
||||||
t.Errorf("builtin function name mismatch: got %v, want %v", n.name, m.name)
|
t.Errorf("builtin function name mismatch: got %v, want %v", n.Name, m.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(n.parameters) != len(m.parameters) {
|
if len(n.Parameters) != len(m.Parameters) {
|
||||||
t.Errorf("builtin function parameter count mismatch: got %v, want %v", n.parameters, m.parameters)
|
t.Errorf("builtin function parameter count mismatch: got %v, want %v", n.Parameters, m.Parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, v := range n.parameters {
|
for i, v := range n.Parameters {
|
||||||
if v != m.parameters[i] {
|
if v != m.Parameters[i] {
|
||||||
t.Errorf("builtin function parameter %d mismatch: got %v, want %v", i, 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 {
|
if &n.F != &m.F {
|
||||||
t.Errorf("builtin function f mismatch: got %v, want %v", &n.f, &m.f)
|
t.Errorf("builtin function f mismatch: got %v, want %v", &n.F, &m.F)
|
||||||
}
|
}
|
||||||
case VariableValueType:
|
case VariableValueType:
|
||||||
n := got.(*VariableValue)
|
n := got.(*VariableValue)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
@ -75,6 +75,11 @@ const (
|
||||||
// InstructionSwap swap the two top items on the stack (1, 2 -> 2, 1)
|
// InstructionSwap swap the two top items on the stack (1, 2 -> 2, 1)
|
||||||
InstructionSwap
|
InstructionSwap
|
||||||
|
|
||||||
|
// InstructionAnd pop two booleans and push true if both are true
|
||||||
|
InstructionAnd
|
||||||
|
// InstructionOr pop two booleans and push true if either are true
|
||||||
|
InstructionOr
|
||||||
|
|
||||||
// InstructionConstant Push a constant to the stack (2 bytes, second = constant index)
|
// InstructionConstant Push a constant to the stack (2 bytes, second = constant index)
|
||||||
InstructionConstant
|
InstructionConstant
|
||||||
// InstructionTrue Push a true literal to the stack
|
// InstructionTrue Push a true literal to the stack
|
||||||
|
|
@ -152,6 +157,10 @@ func (b Bytecode) String() string {
|
||||||
return "STRING_CONCATENATION"
|
return "STRING_CONCATENATION"
|
||||||
case InstructionSwap:
|
case InstructionSwap:
|
||||||
return "SWAP"
|
return "SWAP"
|
||||||
|
case InstructionAnd:
|
||||||
|
return "AND"
|
||||||
|
case InstructionOr:
|
||||||
|
return "OR"
|
||||||
case InstructionBreakpoint:
|
case InstructionBreakpoint:
|
||||||
return "BREAKPOINT"
|
return "BREAKPOINT"
|
||||||
}
|
}
|
||||||
|
|
@ -263,6 +272,14 @@ var DefaultGlobals = map[string]Value{
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"print": BuiltinFunctionValue{
|
||||||
|
"print",
|
||||||
|
[]string{"value"},
|
||||||
|
func(v map[string]Value) Value {
|
||||||
|
print(v["value"].String())
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
|
func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
|
||||||
|
|
@ -345,6 +362,12 @@ func (vm *VM) Next() bool {
|
||||||
b := vm.stack.Pop().(BoolValue)
|
b := vm.stack.Pop().(BoolValue)
|
||||||
vm.stack.Push(!b)
|
vm.stack.Push(!b)
|
||||||
|
|
||||||
|
case InstructionAnd:
|
||||||
|
vm.stack.Push(vm.stack.Pop().(BoolValue) && vm.stack.Pop().(BoolValue))
|
||||||
|
|
||||||
|
case InstructionOr:
|
||||||
|
vm.stack.Push(vm.stack.Pop().(BoolValue) || vm.stack.Pop().(BoolValue))
|
||||||
|
|
||||||
case InstructionLess:
|
case InstructionLess:
|
||||||
r := vm.stack.Pop().(NumberValue)
|
r := vm.stack.Pop().(NumberValue)
|
||||||
l := vm.stack.Pop().(NumberValue)
|
l := vm.stack.Pop().(NumberValue)
|
||||||
|
|
@ -381,7 +404,7 @@ func (vm *VM) Next() bool {
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := len(f.Params) - 1; i >= 0; i-- {
|
for i := len(f.Params) - 1; i >= 0; i-- {
|
||||||
p := vm.stack.Current - Pos(i) - 1
|
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
|
||||||
vm.stack.items[p] = &VariableValue{
|
vm.stack.items[p] = &VariableValue{
|
||||||
f.Params[i],
|
f.Params[i],
|
||||||
vm.stack.items[p],
|
vm.stack.items[p],
|
||||||
|
|
@ -396,11 +419,11 @@ func (vm *VM) Next() bool {
|
||||||
case BuiltinFunctionValue:
|
case BuiltinFunctionValue:
|
||||||
args := map[string]Value{}
|
args := map[string]Value{}
|
||||||
|
|
||||||
for i := len(f.parameters) - 1; i >= 0; i-- {
|
for i := len(f.Parameters) - 1; i >= 0; i-- {
|
||||||
args[f.parameters[i]] = vm.stack.Pop()
|
args[f.Parameters[i]] = vm.stack.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.stack.Push(f.f(args))
|
vm.stack.Push(f.F(args))
|
||||||
default:
|
default:
|
||||||
vm.error(fmt.Sprintf("value called is not a function (%s)", v.String()))
|
vm.error(fmt.Sprintf("value called is not a function (%s)", v.String()))
|
||||||
return false
|
return false
|
||||||
|
|
@ -570,3 +593,11 @@ func (vm *VM) NextU16() uint16 {
|
||||||
func (vm *VM) error(error string) {
|
func (vm *VM) error(error string) {
|
||||||
log.Fatal(error)
|
log.Fatal(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vm *VM) SetGlobal(name string, value Value) {
|
||||||
|
vm.globals[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VM) GetGlobal(name string) Value {
|
||||||
|
return vm.globals[name]
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
4
go.mod
4
go.mod
|
|
@ -1,5 +1,3 @@
|
||||||
module neemek.com/anglais
|
module neemek.com/anglais
|
||||||
|
|
||||||
go 1.23.0
|
go 1.23
|
||||||
|
|
||||||
require github.com/alecthomas/kong v1.2.1
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue