Add set index in list, and update lib+examples+tests

This commit is contained in:
Neemek 2026-08-20 13:44:10 +02:00
parent 09ac16a42d
commit 3bccc227ba
Signed by: neemek
GPG key ID: 84FFE4D7D40AB25E
12 changed files with 117 additions and 60 deletions

View file

@ -977,6 +977,41 @@ func (c *Compiler) compileAssignFromStack(to Node, sig TypeSignature, declare bo
} }
return sig, nil return sig, nil
case IndexNodeType:
if declare {
return nil, c.error(fmt.Sprintf("cannot declare an indexed item"), to)
}
n := to.(*IndexNode)
ssig, err := c.compile(n.source)
if err != nil {
return nil, err
}
isig, err := c.compile(n.index)
if err != nil {
return nil, err
}
if isig.Type() != TypeInteger {
return nil, c.error(fmt.Sprintf("cannot index into list with non-integer (%s)", isig), n.index)
}
switch ssig.Type() {
case TypeList:
lsig := ssig.(*ListSignature)
if !c.typeMatches(sig, lsig.Contents) {
return nil, c.error(fmt.Sprintf("cannot assign value of type %s to list with items of type %s", sig, lsig.Contents), to)
}
c.add(InstructionSetIndexList)
default:
return nil, c.error(fmt.Sprintf("cannot set index of %s", ssig.Type()), n.source)
}
return sig, nil
default: default:
return nil, c.error(fmt.Sprintf("cannot assign to %s", to.Type()), to) return nil, c.error(fmt.Sprintf("cannot assign to %s", to.Type()), to)
} }

View file

@ -117,8 +117,12 @@ type Value interface {
// Get a member from the value. An error is returned if the member does not exist // Get a member from the value. An error is returned if the member does not exist
Get(string) (Value, error) Get(string) (Value, error)
// Clone create a clone of the value. The returned value is a pointer to a new value of the same type as the value. // Copy create a copy of the value. A copy is a direct copy for small data (numbers) and a copy of pointer
Clone() Value // for bigger data (lists, tuples, dicts)
Copy() Value
// Clone create a clone of the value. A clone is new data for all data.
//Clone() Value
} }
type NilValue struct{} type NilValue struct{}
@ -143,7 +147,7 @@ func (v *NilValue) Get(_ string) (Value, error) {
return nil, errors.New("nil has no properties") return nil, errors.New("nil has no properties")
} }
func (v *NilValue) Clone() Value { func (v *NilValue) Copy() Value {
return &NilValue{} return &NilValue{}
} }
@ -175,7 +179,7 @@ func (v *BoolValue) Get(_ string) (Value, error) {
return nil, errors.New("booleans have no properties") return nil, errors.New("booleans have no properties")
} }
func (v *BoolValue) Clone() Value { func (v *BoolValue) Copy() Value {
return &BoolValue{ return &BoolValue{
v.Boolean, v.Boolean,
} }
@ -258,11 +262,11 @@ func (v *ObjectValue) Get(key string) (Value, error) {
} }
} }
func (v *ObjectValue) Clone() Value { func (v *ObjectValue) Copy() Value {
m := make(map[string]Value, len(v.Members)) m := make(map[string]Value, len(v.Members))
for name, mem := range v.Members { for name, mem := range v.Members {
m[name] = mem.Clone() m[name] = mem.Copy()
} }
return &ObjectValue{ return &ObjectValue{
@ -303,7 +307,7 @@ func (v *FloatValue) Get(_ string) (Value, error) {
return nil, errors.New("numbers have no properties") return nil, errors.New("numbers have no properties")
} }
func (v *FloatValue) Clone() Value { func (v *FloatValue) Copy() Value {
return &FloatValue{ return &FloatValue{
v.Number, v.Number,
} }
@ -334,7 +338,7 @@ func (v *IntegerValue) Get(_ string) (Value, error) {
return nil, errors.New("numbers have no properties") return nil, errors.New("numbers have no properties")
} }
func (v *IntegerValue) Clone() Value { func (v *IntegerValue) Copy() Value {
return &IntegerValue{ return &IntegerValue{
new(big.Int).Set(v.Number), new(big.Int).Set(v.Number),
} }
@ -427,7 +431,7 @@ func (v *StringValue) Get(key string) (Value, error) {
return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key)) return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key))
} }
func (v *StringValue) Clone() Value { func (v *StringValue) Copy() Value {
return &StringValue{ return &StringValue{
v.Text, v.Text,
} }
@ -594,15 +598,9 @@ func (v *ListValue) Get(key string) (Value, error) {
return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key)) return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key))
} }
func (v *ListValue) Clone() Value { func (v *ListValue) Copy() Value {
n := make([]Value, len(v.Items))
for i, item := range v.Items {
n[i] = item.Clone()
}
return &ListValue{ return &ListValue{
n, v.Items,
} }
} }
@ -635,13 +633,9 @@ func (v *TupleValue) DebugString() string {
return v.String() return v.String()
} }
func (v *TupleValue) Clone() Value { func (v *TupleValue) Copy() Value {
n := make([]Value, len(v.Items))
for i, item := range v.Items {
n[i] = item.Clone()
}
return &TupleValue{ return &TupleValue{
n, v.Items,
} }
} }
@ -718,7 +712,7 @@ func (v *FunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties") return nil, errors.New("functions have no properties")
} }
func (v *FunctionValue) Clone() Value { func (v *FunctionValue) Copy() Value {
return &FunctionValue{ return &FunctionValue{
v.Name, v.Name,
v.Params, v.Params,
@ -758,7 +752,7 @@ func (v *BuiltinFunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties") return nil, errors.New("functions have no properties")
} }
func (v *BuiltinFunctionValue) Clone() Value { func (v *BuiltinFunctionValue) Copy() Value {
return &BuiltinFunctionValue{ return &BuiltinFunctionValue{
v.Name, v.Name,
v.Signature, v.Signature,
@ -807,7 +801,7 @@ func (v *RecordValue) DebugString() string {
return v.String() return v.String()
} }
func (v *RecordValue) Clone() Value { func (v *RecordValue) Copy() Value {
return &RecordValue{ return &RecordValue{
v.Entries, v.Entries,
} }

View file

@ -152,10 +152,14 @@ const (
// is the index. [..., container, index] -> [..., item] // is the index. [..., container, index] -> [..., item]
InstructionIndexTuple InstructionIndexTuple
// InstructionIndexString index into a string. The lower item is the container, and the top item // InstructionIndexString index into a string. The lower item is the container, and the top item
// is the index. [..., container, index] -> [..., item]. Produces a new string with the character // is the index. [..., container, index] -> [..., item]. Produces a new string with only the character
// at the position // at the indexed position
InstructionIndexString InstructionIndexString
// InstructionSetIndexList set the item at a given index in a list.
// [..., item, container, index] -> [..., item]
InstructionSetIndexList
// InstructionBreakpoint for debugging purposes // InstructionBreakpoint for debugging purposes
InstructionBreakpoint InstructionBreakpoint
) )
@ -638,7 +642,7 @@ var DefaultGlobals = map[string]Value{
n, _ := v.Number.Float64() n, _ := v.Number.Float64()
return &FloatValue{n}, nil return &FloatValue{n}, nil
case *FloatValue: case *FloatValue:
return v.Clone(), nil return v.Copy(), nil
case *StringValue: case *StringValue:
num, err := strconv.ParseFloat(v.Text, FloatSize) num, err := strconv.ParseFloat(v.Text, FloatSize)
if err != nil { if err != nil {
@ -978,7 +982,7 @@ func (vm *VM) Next() bool {
vm.Stack.Push(v) vm.Stack.Push(v)
case InstructionSetLocal: case InstructionSetLocal:
value := vm.Stack.Peek().Clone() value := vm.Stack.Peek().Copy()
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
vm.setVar(name, value) vm.setVar(name, value)
@ -986,7 +990,7 @@ func (vm *VM) Next() bool {
case InstructionDeclareLocal: case InstructionDeclareLocal:
vm.addVar( vm.addVar(
vm.GetConstant(vm.NextByte()).(*StringValue).Text, vm.GetConstant(vm.NextByte()).(*StringValue).Text,
vm.Stack.Peek().Clone(), vm.Stack.Peek().Copy(),
) )
case InstructionGetGlobal: case InstructionGetGlobal:
@ -1070,7 +1074,7 @@ func (vm *VM) Next() bool {
vm.Stack.Push(r, l) vm.Stack.Push(r, l)
case InstructionDuplicate: case InstructionDuplicate:
vm.Stack.Push(vm.Stack.Peek().Clone()) vm.Stack.Push(vm.Stack.Peek().Copy())
case InstructionAccessProperty: case InstructionAccessProperty:
source := vm.Stack.Pop() source := vm.Stack.Pop()
@ -1112,7 +1116,7 @@ func (vm *VM) Next() bool {
vm.error(fmt.Sprintf("index %d out of bounds", n)) vm.error(fmt.Sprintf("index %d out of bounds", n))
} }
vm.Stack.Push(l.Items[n].Clone()) vm.Stack.Push(l.Items[n].Copy())
case InstructionIndexTuple: case InstructionIndexTuple:
i := vm.Stack.Pop().(*IntegerValue) i := vm.Stack.Pop().(*IntegerValue)
@ -1124,7 +1128,7 @@ func (vm *VM) Next() bool {
vm.error(fmt.Sprintf("index %d out of bounds", n)) vm.error(fmt.Sprintf("index %d out of bounds", n))
} }
vm.Stack.Push(t.Items[n].Clone()) vm.Stack.Push(t.Items[n].Copy())
case InstructionIndexString: case InstructionIndexString:
i := vm.Stack.Pop().(*IntegerValue) i := vm.Stack.Pop().(*IntegerValue)
@ -1138,6 +1142,18 @@ func (vm *VM) Next() bool {
vm.Stack.Push(&StringValue{string(s.Text[n])}) vm.Stack.Push(&StringValue{string(s.Text[n])})
case InstructionSetIndexList:
n := vm.Stack.Pop().(*IntegerValue)
l := vm.Stack.Pop().(*ListValue)
i := n.Number.Int64()
if i < 0 || int64(len(l.Items)) <= i {
vm.error(fmt.Sprintf("index %d out of bounds", i))
}
l.Items[i] = vm.Stack.Peek().Copy()
case InstructionBreakpoint: case InstructionBreakpoint:
/* /*
// I'm keeping this // I'm keeping this
@ -1284,7 +1300,7 @@ func (vm *VM) HasNext() bool {
} }
func (vm *VM) GetConstant(id Bytecode) Value { func (vm *VM) GetConstant(id Bytecode) Value {
return vm.chunk.Constants[id].Clone() return vm.chunk.Constants[id].Copy()
} }
func (vm *VM) ReadConstant() Value { func (vm *VM) ReadConstant() Value {

View file

@ -3,14 +3,9 @@
fn range(from: int, to: int) -> (fn() -> (int, bool)) { fn range(from: int, to: int) -> (fn() -> (int, bool)) {
i := from - 1 i := from - 1
end := to - 1
fn() -> (int, bool) { fn() -> (int, bool) {
if i < end { (i = i+1, i+1 < to)
(i = i+1, true)
} else {
(-1, false)
}
} }
} }

View file

@ -1,6 +1,6 @@
# Empty list # Empty list
println([]) println([]any)
# List with items # List with items
println([3, 1, 4, 1, 5, 9, 2, 6, 5]) println([3, 1, 4, 1, 5, 9, 2, 6, 5])
@ -8,25 +8,25 @@ println([3, 1, 4, 1, 5, 9, 2, 6, 5])
# List with items of different types # List with items of different types
println(["", "私はかっこいいです。", true, nil, nil, 1, 2]) println(["", "私はかっこいいです。", true, nil, nil, 1, 2])
a := [] a := []int
a = a + [1] a.push(1)
a = a + [2] a.push(2)
println(a) println(a)
list := [] list := []int
x := 0 x := 0
for n in 0..100 { for n in 0..100 {
x = x + 2*n + 1 x = x + 2*n + 1
list = list + [x] list.push(x)
} }
println(list) println(list)
println(list.map(func(a) { println(list.map(fn(a: int) -> int {
return a - 1 return a - 1
})) }))
println(list.length()) println(list.length())

View file

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

0
foo.ang Normal file
View file

View file

@ -1,5 +1,5 @@
fn<T, R> ([T]) map(f: fn(T) -> R) -> [R] { fn map<T, R>(list: [T], f: fn(T) -> R) -> [R] {
out := [] out := []
for v in list.iter() { for v in list.iter() {

View file

@ -249,3 +249,5 @@ fn tan(x: float) -> float {
# todo # todo
0.0 0.0
} }
(E:, PI:, sqrt:, log:, ln:, exp:, pow:)

View file

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

View file

@ -34,3 +34,10 @@ assertEq("the, first, time".split(", "), ["the", "first", "time"])
# list indexing # list indexing
assertEq([1, 2, 3].at(1), 2) assertEq([1, 2, 3].at(1), 2)
assertEq(["a", "b", "c"].at(2), "c") assertEq(["a", "b", "c"].at(2), "c")
# mutating list
a := [1, 2, 3]
assertEq(a, [1, 2, 3])
a[1] = 4
assertEq(a, [1, 4, 3])

View file

@ -9,3 +9,10 @@ fn neighbours(n: int) -> (int, int) {
} }
assertEq(neighbours(2), (1, 3)) assertEq(neighbours(2), (1, 3))
a := (1, 2)
assertEq(a, (1, 2))
(x, y) := a
assertEq(x, 1)
assertEq(y, 2)