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:
Neemek 2024-12-13 11:33:47 +01:00
parent 7498c85424
commit 62656c6dff
Signed by: neemek
GPG key ID: 28360A8951CD0E9B
19 changed files with 202 additions and 99 deletions

10
cli/go.mod Normal file
View 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

View file

@ -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")

View 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() {
} }

View file

@ -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))
} }
} }

View file

@ -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
View file

@ -0,0 +1,3 @@
module neemek.com/anglais/core
go 1.23.0

View file

View file

@ -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:

View file

@ -1,4 +1,4 @@
package main package core
import ( import (
"testing" "testing"

View file

@ -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

View file

@ -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:

View file

@ -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")

View file

@ -1,4 +1,4 @@
package main package core
type Stack[T any] struct { type Stack[T any] struct {
Current Pos Current Pos

View file

@ -1,4 +1,4 @@
package main package core
import ( import (
"fmt" "fmt"

View file

@ -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

View file

@ -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)

View file

@ -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]
}

View file

@ -1,4 +1,4 @@
package main package core
import ( import (
"fmt" "fmt"

4
go.mod
View file

@ -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