we in era3 boys; overhauled expression system, and variables are now in maps

This commit is contained in:
Neemek 2026-07-09 23:10:49 +02:00
parent bf29e6c3dd
commit 43e450c207
Signed by: neemek
GPG key ID: 84FFE4D7D40AB25E
14 changed files with 945 additions and 452 deletions

View file

@ -104,6 +104,8 @@ const (
// InstructionSwap swap the two top items on the stack (1, 2 -> 2, 1)
InstructionSwap
// InstructionDuplicate push a copy of the item on top of the stack (1 -> 1, 1)
InstructionDuplicate
// InstructionAnd pop two booleans and push true if both are true
InstructionAnd
@ -234,6 +236,8 @@ func (b Bytecode) String() string {
return "ACCESS_PROPERTY"
case InstructionConcatLists:
return "CONCAT_LISTS"
case InstructionDuplicate:
return "DUPLICATE"
}
return "UNDEFINED"
}
@ -267,6 +271,30 @@ func (c Chunk) String() string {
return b.String()
}
func (c *Chunk) Equals(other *Chunk) bool {
if len(c.Bytecode) != len(other.Bytecode) {
return false
}
for i, bc := range c.Bytecode {
if other.Bytecode[i] != bc {
return false
}
}
if len(c.Constants) != len(other.Constants) {
return false
}
for i := 0; i < len(c.Constants); i++ {
if other.Constants[i] != c.Constants[i] {
return false
}
}
return true
}
func NewChunk(bytecode []Bytecode, constants []Value) *Chunk {
return &Chunk{bytecode, constants}
}
@ -327,23 +355,26 @@ type VM struct {
chunk *Chunk
// instruction pointer
ip Pos
scope Pos
ip Pos
// global variable storage
globals map[string]Value
variableEnd Pos
globals map[string]Value
// local variable storage
scope *Scope
stack *Stack[Value]
call *Stack[Call]
}
type Scope struct {
current map[string]Value
parent *Scope
}
type Call struct {
chunk *Chunk
ip Pos
stackEnd Pos
variableEnd Pos
scope Pos
chunk *Chunk
ip Pos
scope *Scope
}
var DefaultGlobals = map[string]Value{
@ -355,7 +386,7 @@ var DefaultGlobals = map[string]Value{
},
func(_ *VM, this Value, v []Value) (Value, error) {
println(v[0].String())
return nil, nil
return &NilValue{}, nil
},
nil,
false,
@ -368,7 +399,7 @@ var DefaultGlobals = map[string]Value{
},
func(_ *VM, this Value, v []Value) (Value, error) {
print(v[0].String())
return nil, nil
return &NilValue{}, nil
},
nil,
false,
@ -630,6 +661,9 @@ func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
call: NewStack[Call](callstackSize),
globals: DefaultGlobals,
scope: &Scope{
current: map[string]Value{},
},
}
return vm
@ -646,24 +680,20 @@ func (vm *VM) Next() bool {
case InstructionReturn:
if vm.call.Current == 0 {
return false
} else {
v := vm.stack.Pop()
c := vm.call.Pop()
// reset stack current and variable end and scope
vm.variableEnd = c.variableEnd
vm.stack.Current = c.stackEnd
vm.scope = c.scope
// reset to calling position
vm.ip = c.ip
vm.chunk = c.chunk
vm.purgeVars()
vm.stack.Push(v)
}
v := vm.stack.Pop()
c := vm.call.Pop()
// reset stack current and variable end and scope
vm.scope = c.scope
// reset to calling position
vm.ip = c.ip
vm.chunk = c.chunk
vm.stack.Push(v)
case InstructionPop:
vm.stack.Pop()
@ -805,28 +835,21 @@ func (vm *VM) Next() bool {
switch f := v.(type) {
case *FunctionValue:
vm.call.Push(Call{
chunk: vm.chunk,
ip: vm.ip,
stackEnd: vm.stack.Current - Pos(len(f.Params)),
variableEnd: vm.variableEnd,
scope: vm.scope,
chunk: vm.chunk,
ip: vm.ip,
scope: vm.scope,
})
vm.descend()
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].Name,
vm.stack.items[p],
vm.scope,
}
vm.addVar(f.Params[i].Name, vm.stack.Pop())
}
if f.Parent != nil {
vm.addVar("this", f.Parent)
}
vm.variableEnd = vm.stack.Current
vm.chunk = f.Chunk
vm.ip = 0
case *BuiltinFunctionValue:
@ -868,24 +891,18 @@ func (vm *VM) Next() bool {
return false
}
vm.stack.Push(v.value)
vm.stack.Push(v)
case InstructionSetLocal:
value := vm.stack.Pop().(Value)
value := vm.stack.Peek().Clone()
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name)
if v == nil {
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
}
v.value = value.Clone()
vm.setVar(name, value)
case InstructionDeclareLocal:
vm.addVar(
vm.GetConstant(vm.NextByte()).(*StringValue).Text,
vm.stack.Pop().Clone(),
vm.stack.Peek().Clone(),
)
case InstructionGetGlobal:
@ -954,6 +971,9 @@ func (vm *VM) Next() bool {
vm.stack.Push(r, l)
case InstructionDuplicate:
vm.stack.Push(vm.stack.Peek().Clone())
case InstructionAccessProperty:
source := vm.stack.Pop()
property := vm.ReadConstant()
@ -973,6 +993,7 @@ func (vm *VM) Next() bool {
vm.stack.Push(member)
case InstructionBreakpoint:
vm.stack.Push(&NilValue{})
default:
panic("invalid byte code")
@ -985,11 +1006,9 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
switch f := v.(type) {
case *FunctionValue:
vm.call.Push(Call{
chunk: vm.chunk,
ip: vm.ip,
stackEnd: vm.stack.Current,
variableEnd: vm.variableEnd,
scope: vm.scope,
chunk: vm.chunk,
ip: vm.ip,
scope: vm.scope,
})
for i := 0; i < len(f.Params); i++ {
@ -1000,8 +1019,6 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
vm.addVar("this", f.Parent)
}
vm.variableEnd = vm.stack.Current
vm.chunk = f.Chunk
vm.ip = 0
@ -1028,6 +1045,14 @@ func (vm *VM) TryNextByte() (Bytecode, error) {
return 0, errors.New("there are no more instructions")
}
for int(vm.ip) >= len(vm.chunk.Bytecode) && vm.call.Current > 0 {
c := vm.call.Pop()
vm.ip = c.ip
vm.chunk = c.chunk
vm.scope = c.scope
}
v := vm.chunk.Bytecode[vm.ip]
vm.ip++
@ -1045,53 +1070,55 @@ func (vm *VM) NextByte() Bytecode {
}
func (vm *VM) ascend() {
vm.scope--
if vm.scope < 0 {
if vm.scope.parent == nil {
panic("invalid scope")
}
vm.purgeVars()
}
// purgeVars remove all variables not within scope
func (vm *VM) purgeVars() {
for ; vm.variableEnd > 0 && vm.stack.items[vm.variableEnd-1].(*VariableValue).scope > vm.scope; vm.variableEnd-- {
vm.stack.Pop()
}
vm.scope = vm.scope.parent
}
func (vm *VM) descend() {
vm.scope++
old := vm.scope
vm.scope = &Scope{
map[string]Value{},
old,
}
}
func (vm *VM) addVar(name string, value Value) {
vm.variableEnd++
vm.stack.Push(&VariableValue{
name,
value,
vm.scope,
})
vm.scope.current[name] = value
}
func (vm *VM) getVar(name string) *VariableValue {
for i := vm.variableEnd - 1; i >= 0; i-- {
v, ok := vm.stack.items[i].(*VariableValue)
func (vm *VM) getVar(name string) Value {
s := vm.scope
if !ok {
continue
}
if v.name == name {
for s != nil {
if v, ok := s.current[name]; ok {
return v
}
s = s.parent
}
return nil
}
func (vm *VM) setVar(name string, v Value) {
s := vm.scope
for s != nil {
if _, ok := s.current[name]; ok {
s.current[name] = v
break
}
s = s.parent
}
}
func (vm *VM) HasNext() bool {
return vm.ip < Pos(len(vm.chunk.Bytecode))
return vm.ip < Pos(len(vm.chunk.Bytecode)) || vm.call.Current > 0
}
func (vm *VM) GetConstant(id Bytecode) Value {