diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9c2e211..54384c0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,4 +25,6 @@ jobs: run: go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic - name: Codecov - uses: codecov/codecov-action@v2.1.0 + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/codecov.yml b/codecov.yml index 6c1171b..fdb4a2c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,6 @@ +comment: + layout: "reach, diff, flags, files" + behavior: default + ignore: - "**/main.go" diff --git a/engine/atom.go b/engine/atom.go index 23c9bdd..75474f1 100644 --- a/engine/atom.go +++ b/engine/atom.go @@ -234,7 +234,7 @@ func NewAtom(name string) Atom { // WriteTerm outputs the Atom to an io.Writer. func (a Atom) WriteTerm(w io.Writer, opts *WriteOptions, _ *Env) error { ew := errWriter{w: w} - openClose := (opts.left != (operator{}) || opts.right != (operator{})) && opts.ops.defined(a) + openClose := (opts.left != (operator{}) || opts.right != (operator{})) && opts.getOps().defined(a) if openClose { if opts.left.name != 0 && opts.left.specifier.class() == operatorClassPrefix { diff --git a/engine/atom_test.go b/engine/atom_test.go index 89819c4..2041a9d 100644 --- a/engine/atom_test.go +++ b/engine/atom_test.go @@ -3,6 +3,7 @@ package engine import ( "bytes" "github.com/stretchr/testify/assert" + orderedmap "github.com/wk8/go-ordered-map/v2" "testing" ) @@ -23,8 +24,8 @@ func TestAtom_WriteTerm(t *testing.T) { {name: `{}`, opts: WriteOptions{quoted: false}, output: `{}`}, {name: `{}`, opts: WriteOptions{quoted: true}, output: `{}`}, {name: `-`, output: `-`}, - {name: `-`, opts: WriteOptions{ops: operators{atomPlus: {}, atomMinus: {}}, left: operator{specifier: operatorSpecifierFY, name: atomPlus}}, output: ` (-)`}, - {name: `-`, opts: WriteOptions{ops: operators{atomPlus: {}, atomMinus: {}}, right: operator{name: atomPlus}}, output: `(-)`}, + {name: `-`, opts: WriteOptions{_ops: &operators{OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator](orderedmap.WithInitialData(orderedmap.Pair[Atom, [_operatorClassLen]operator]{Key: atomPlus, Value: [3]operator{}}, orderedmap.Pair[Atom, [_operatorClassLen]operator]{Key: atomMinus, Value: [3]operator{}}))}, left: operator{specifier: operatorSpecifierFY, name: atomPlus}}, output: ` (-)`}, + {name: `-`, opts: WriteOptions{_ops: &operators{OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator](orderedmap.WithInitialData(orderedmap.Pair[Atom, [_operatorClassLen]operator]{Key: atomPlus, Value: [3]operator{}}, orderedmap.Pair[Atom, [_operatorClassLen]operator]{Key: atomMinus, Value: [3]operator{}}))}, right: operator{name: atomPlus}}, output: `(-)`}, {name: `X`, opts: WriteOptions{quoted: true, left: operator{name: NewAtom(`F`)}}, output: ` 'X'`}, // So that it won't be 'F''X'. {name: `X`, opts: WriteOptions{quoted: true, right: operator{name: NewAtom(`F`)}}, output: `'X' `}, // So that it won't be 'X''F'. {name: `foo`, opts: WriteOptions{left: operator{name: NewAtom(`bar`)}}, output: ` foo`}, // So that it won't be barfoo. diff --git a/engine/builtin.go b/engine/builtin.go index 8bdb5d5..4d00e40 100644 --- a/engine/builtin.go +++ b/engine/builtin.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + orderedmap "github.com/wk8/go-ordered-map/v2" "io" "io/fs" "os" @@ -560,11 +561,11 @@ func Op(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise { } for _, name := range names { - if class := spec.class(); vm.operators.definedInClass(name, spec.class()) { - vm.operators.remove(name, class) + if class := spec.class(); vm.getOperators().definedInClass(name, spec.class()) { + vm.getOperators().remove(name, class) } - vm.operators.define(p, spec, name) + vm.getOperators().define(p, spec, name) } return k(env) @@ -573,13 +574,13 @@ func Op(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise { func validateOp(vm *VM, p Integer, spec operatorSpecifier, name Atom, env *Env) *Promise { switch name { case atomComma: - if vm.operators.definedInClass(name, operatorClassInfix) { + if vm.getOperators().definedInClass(name, operatorClassInfix) { return Error(permissionError(operationModify, permissionTypeOperator, name, env)) } case atomBar: if spec.class() != operatorClassInfix || (p > 0 && p < 1001) { op := operationCreate - if vm.operators.definedInClass(name, operatorClassInfix) { + if vm.getOperators().definedInClass(name, operatorClassInfix) { op = operationModify } return Error(permissionError(op, permissionTypeOperator, name, env)) @@ -591,11 +592,11 @@ func validateOp(vm *VM, p Integer, spec operatorSpecifier, name Atom, env *Env) // 6.3.4.3 There shall not be an infix and a postfix Operator with the same name. switch spec.class() { case operatorClassInfix: - if vm.operators.definedInClass(name, operatorClassPostfix) { + if vm.getOperators().definedInClass(name, operatorClassPostfix) { return Error(permissionError(operationCreate, permissionTypeOperator, name, env)) } case operatorClassPostfix: - if vm.operators.definedInClass(name, operatorClassInfix) { + if vm.getOperators().definedInClass(name, operatorClassInfix) { return Error(permissionError(operationCreate, permissionTypeOperator, name, env)) } } @@ -652,9 +653,9 @@ func CurrentOp(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise } pattern := tuple(priority, specifier, op) - ks := make([]func(context.Context) *Promise, 0, len(vm.operators)*int(_operatorClassLen)) - for _, ops := range vm.operators { - for _, op := range ops { + ks := make([]func(context.Context) *Promise, 0, vm.getOperators().Len()*int(_operatorClassLen)) + for ops := vm.getOperators().Oldest(); ops != nil; ops = ops.Next() { + for _, op := range ops.Value { op := op if op == (operator{}) { continue @@ -701,12 +702,12 @@ func assertMerge(vm *VM, t Term, merge func([]clause, []clause) []clause, env *E } if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = orderedmap.New[procedureIndicator, procedure]() } - p, ok := vm.procedures[pi] + p, ok := vm.getProcedure(pi) if !ok { p = &userDefined{public: true, dynamic: true} - vm.procedures[pi] = p + vm.setProcedure(pi, p) } added, err := compile(t, env) @@ -1066,11 +1067,11 @@ func CurrentPredicate(vm *VM, pi Term, k Cont, env *Env) *Promise { return Error(typeError(validTypePredicateIndicator, pi, env)) } - ks := make([]func(context.Context) *Promise, 0, len(vm.procedures)) - for key, p := range vm.procedures { - switch p.(type) { + ks := make([]func(context.Context) *Promise, 0, vm.procedures.Len()) + for element := vm.procedures.Oldest(); element != nil; element = element.Next() { + switch element.Value.(type) { case *userDefined: - c := key.Term() + c := element.Key.Term() ks = append(ks, func(context.Context) *Promise { return Unify(vm, pi, c, k, env) }) @@ -1091,7 +1092,7 @@ func Retract(vm *VM, t Term, k Cont, env *Env) *Promise { return Error(err) } - p, ok := vm.procedures[pi] + p, ok := vm.getProcedure(pi) if !ok { return Bool(false) } @@ -1142,10 +1143,11 @@ func Abolish(vm *VM, pi Term, k Cont, env *Env) *Promise { return Error(domainError(validDomainNotLessThanZero, arity, env)) } key := procedureIndicator{name: name, arity: arity} - if u, ok := vm.procedures[key].(*userDefined); !ok || !u.dynamic { + p, _ := vm.getProcedure(key) + if u, ok := p.(*userDefined); !ok || !u.dynamic { return Error(permissionError(operationModify, permissionTypeStaticProcedure, key.Term(), env)) } - delete(vm.procedures, key) + vm.procedures.Delete(key) return k(env) default: return Error(typeError(validTypeInteger, arity, env)) @@ -1461,7 +1463,7 @@ func WriteTerm(vm *VM, streamOrAlias, t, options Term, k Cont, env *Env) *Promis } opts := WriteOptions{ - ops: vm.operators, + _ops: vm.getOperators(), priority: 1200, } iter := ListIterator{List: options, Env: env} @@ -1982,7 +1984,7 @@ func Clause(vm *VM, head, body Term, k Cont, env *Env) *Promise { return Error(typeError(validTypeCallable, body, env)) } - p, ok := vm.procedures[pi] + p, ok := vm.getProcedure(pi) if !ok { return Bool(false) } @@ -2749,7 +2751,7 @@ func ExpandTerm(vm *VM, term1, term2 Term, k Cont, env *Env) *Promise { } func expand(vm *VM, term Term, env *Env) (Term, error) { - if _, ok := vm.procedures[procedureIndicator{name: atomTermExpansion, arity: 2}]; ok { + if _, ok := vm.getProcedure(procedureIndicator{name: atomTermExpansion, arity: 2}); ok { var ret Term v := NewVariable() ok, err := Call(vm, atomTermExpansion.Apply(term, v), func(env *Env) *Promise { diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 970d02a..d747fce 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + orderedmap "github.com/wk8/go-ordered-map/v2" "io" "math" "os" @@ -17,6 +18,8 @@ import ( "unicode/utf8" ) +type procedurePair orderedmap.Pair[procedureIndicator, procedure] + func TestCall(t *testing.T) { var vm VM vm.Register0(atomFail, func(_ *VM, f Cont, env *Env) *Promise { @@ -91,11 +94,12 @@ func TestCall1(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 2}: Predicate2(func(_ *VM, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: buildOrderedMap(procedurePair{ + Key: procedureIndicator{name: NewAtom("p"), arity: 2}, + Value: Predicate2(func(_ *VM, _, _ Term, k Cont, env *Env) *Promise { return k(env) }), - }} + })} ok, err := Call1(&vm, tt.closure, tt.additional[0], Success, nil).Force(context.Background()) assert.Equal(t, tt.ok, ok) assert.Equal(t, tt.err, err) @@ -122,11 +126,12 @@ func TestCall2(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 3}: Predicate3(func(_ *VM, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: buildOrderedMap(procedurePair{ + Key: procedureIndicator{name: NewAtom("p"), arity: 3}, + Value: Predicate3(func(_ *VM, _, _, _ Term, k Cont, env *Env) *Promise { return k(env) }), - }} + })} ok, err := Call2(&vm, tt.closure, tt.additional[0], tt.additional[1], Success, nil).Force(context.Background()) assert.Equal(t, tt.ok, ok) assert.Equal(t, tt.err, err) @@ -153,11 +158,12 @@ func TestCall3(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 4}: Predicate4(func(_ *VM, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: buildOrderedMap(procedurePair{ + Key: procedureIndicator{name: NewAtom("p"), arity: 4}, + Value: Predicate4(func(_ *VM, _, _, _, _ Term, k Cont, env *Env) *Promise { return k(env) }), - }} + })} ok, err := Call3(&vm, tt.closure, tt.additional[0], tt.additional[1], tt.additional[2], Success, nil).Force(context.Background()) assert.Equal(t, tt.ok, ok) assert.Equal(t, tt.err, err) @@ -184,11 +190,12 @@ func TestCall4(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 5}: Predicate5(func(_ *VM, _, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: buildOrderedMap(procedurePair{ + Key: procedureIndicator{name: NewAtom("p"), arity: 5}, + Value: Predicate5(func(_ *VM, _, _, _, _, _ Term, k Cont, env *Env) *Promise { return k(env) }), - }} + })} ok, err := Call4(&vm, tt.closure, tt.additional[0], tt.additional[1], tt.additional[2], tt.additional[3], Success, nil).Force(context.Background()) assert.Equal(t, tt.ok, ok) assert.Equal(t, tt.err, err) @@ -215,11 +222,12 @@ func TestCall5(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 6}: Predicate6(func(_ *VM, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: buildOrderedMap(procedurePair{ + Key: procedureIndicator{name: NewAtom("p"), arity: 6}, + Value: Predicate6(func(_ *VM, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { return k(env) }), - }} + })} ok, err := Call5(&vm, tt.closure, tt.additional[0], tt.additional[1], tt.additional[2], tt.additional[3], tt.additional[4], Success, nil).Force(context.Background()) assert.Equal(t, tt.ok, ok) assert.Equal(t, tt.err, err) @@ -246,11 +254,12 @@ func TestCall6(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 7}: Predicate7(func(_ *VM, _, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: buildOrderedMap(procedurePair{ + Key: procedureIndicator{name: NewAtom("p"), arity: 7}, + Value: Predicate7(func(_ *VM, _, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { return k(env) }), - }} + })} ok, err := Call6(&vm, tt.closure, tt.additional[0], tt.additional[1], tt.additional[2], tt.additional[3], tt.additional[4], tt.additional[5], Success, nil).Force(context.Background()) assert.Equal(t, tt.ok, ok) assert.Equal(t, tt.err, err) @@ -277,11 +286,12 @@ func TestCall7(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 8}: Predicate8(func(_ *VM, _, _, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: buildOrderedMap(procedurePair{ + Key: procedureIndicator{name: NewAtom("p"), arity: 8}, + Value: Predicate8(func(_ *VM, _, _, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { return k(env) }), - }} + })} ok, err := Call7(&vm, tt.closure, tt.additional[0], tt.additional[1], tt.additional[2], tt.additional[3], tt.additional[4], tt.additional[5], tt.additional[6], Success, nil).Force(context.Background()) assert.Equal(t, tt.ok, ok) assert.Equal(t, tt.err, err) @@ -291,17 +301,20 @@ func TestCall7(t *testing.T) { func TestCallNth(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: Predicate0(func(_ *VM, k Cont, env *Env) *Promise { - return Delay(func(context.Context) *Promise { - return k(env) - }, func(context.Context) *Promise { - return k(env) - }, func(context.Context) *Promise { - return Error(errors.New("three")) - }) - }), - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 0}, + Value: Predicate0(func(_ *VM, k Cont, env *Env) *Promise { + return Delay(func(context.Context) *Promise { + return k(env) + }, func(context.Context) *Promise { + return k(env) + }, func(context.Context) *Promise { + return Error(errors.New("three")) + }) + }), + }, + ), } t.Run("ok", func(t *testing.T) { @@ -939,134 +952,167 @@ func TestTermVariables(t *testing.T) { func TestOp(t *testing.T) { t.Run("insert", func(t *testing.T) { t.Run("atom", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(900, operatorSpecifierXFX, NewAtom(`+++`)) - vm.operators.define(1100, operatorSpecifierXFX, NewAtom(`+`)) + varCounter = 1 + + vm := VM{_operators: newOperators()} + vm.getOperators().define(900, operatorSpecifierXFX, NewAtom(`+++`)) + vm.getOperators().define(1100, operatorSpecifierXFX, NewAtom(`+`)) ok, err := Op(&vm, Integer(1000), atomXFX, NewAtom("++"), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, operators{ - NewAtom(`+++`): { - operatorClassInfix: { - priority: 900, - specifier: operatorSpecifierXFX, - name: NewAtom("+++"), - }, - }, - NewAtom(`++`): { - operatorClassInfix: { - priority: 1000, - specifier: operatorSpecifierXFX, - name: NewAtom("++"), - }, - }, - NewAtom(`+`): { - operatorClassInfix: { - priority: 1100, - specifier: operatorSpecifierXFX, - name: atomPlus, - }, - }, - }, vm.operators) + assert.EqualValues(t, &operators{ + OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator]( + orderedmap.WithInitialData( + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+++`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 900, + specifier: operatorSpecifierXFX, + name: NewAtom("+++"), + }, + }}, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 1100, + specifier: operatorSpecifierXFX, + name: atomPlus, + }, + }}, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`++`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 1000, + specifier: operatorSpecifierXFX, + name: NewAtom("++"), + }, + }}))}, vm.getOperators()) }) t.Run("list", func(t *testing.T) { vm := VM{ - operators: operators{ - NewAtom(`+++`): { - operatorClassInfix: { - priority: 900, - specifier: operatorSpecifierXFX, - name: NewAtom("+++"), - }, - }, - NewAtom(`+`): { - operatorClassInfix: { - priority: 1100, - specifier: operatorSpecifierXFX, - name: atomPlus, - }, - }, - }, + _operators: &operators{ + OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator]( + orderedmap.WithInitialData( + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+++`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 900, + specifier: operatorSpecifierXFX, + name: NewAtom("+++"), + }, + }}, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 1100, + specifier: operatorSpecifierXFX, + name: atomPlus, + }, + }}))}, } ok, err := Op(&vm, Integer(1000), atomXFX, List(NewAtom("++"), NewAtom("++")), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, operators{ - NewAtom(`+++`): { - operatorClassInfix: { - priority: 900, - specifier: operatorSpecifierXFX, - name: NewAtom("+++"), - }, - }, - NewAtom(`++`): { - operatorClassInfix: { - priority: 1000, - specifier: operatorSpecifierXFX, - name: NewAtom("++"), - }, - }, - NewAtom(`+`): { - operatorClassInfix: { - priority: 1100, - specifier: operatorSpecifierXFX, - name: atomPlus, - }, - }, - }, vm.operators) + assert.EqualValues(t, &operators{ + OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator]( + orderedmap.WithInitialData( + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+++`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 900, + specifier: operatorSpecifierXFX, + name: NewAtom("+++"), + }, + }}, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 1100, + specifier: operatorSpecifierXFX, + name: atomPlus, + }, + }}, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`++`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 1000, + specifier: operatorSpecifierXFX, + name: NewAtom("++"), + }, + }}))}, vm.getOperators()) }) }) t.Run("remove", func(t *testing.T) { vm := VM{ - operators: operators{ - NewAtom(`+++`): { - operatorClassInfix: { - priority: 900, - specifier: operatorSpecifierXFX, - name: NewAtom("+++"), - }, - }, - NewAtom(`++`): { - operatorClassInfix: { - priority: 1000, - specifier: operatorSpecifierXFX, - name: NewAtom("++"), - }, - }, - NewAtom(`+`): { - operatorClassInfix: { - priority: 1100, - specifier: operatorSpecifierXFX, - name: atomPlus, - }, - }, - }, + _operators: &operators{ + OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator]( + orderedmap.WithInitialData( + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+++`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 900, + specifier: operatorSpecifierXFX, + name: NewAtom("+++"), + }, + }}, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`++`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 1000, + specifier: operatorSpecifierXFX, + name: NewAtom("++"), + }, + }}, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 1100, + specifier: operatorSpecifierXFX, + name: atomPlus, + }, + }}))}, } ok, err := Op(&vm, Integer(0), atomXFX, NewAtom("++"), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, operators{ - NewAtom(`+++`): { - operatorClassInfix: { - priority: 900, - specifier: operatorSpecifierXFX, - name: NewAtom("+++"), - }, - }, - NewAtom(`+`): { - operatorClassInfix: { - priority: 1100, - specifier: operatorSpecifierXFX, - name: atomPlus, - }, - }, - }, vm.operators) + assert.EqualValues(t, &operators{ + OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator]( + orderedmap.WithInitialData( + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+++`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 900, + specifier: operatorSpecifierXFX, + name: NewAtom("+++"), + }, + }}, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: NewAtom(`+`), + Value: [_operatorClassLen]operator{ + operatorClassInfix: { + priority: 1100, + specifier: operatorSpecifierXFX, + name: atomPlus, + }, + }}))}, vm.getOperators()) }) t.Run("priority is a variable", func(t *testing.T) { @@ -1135,16 +1181,16 @@ func TestOp(t *testing.T) { }) t.Run("operator is ','", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(1000, operatorSpecifierXFY, NewAtom(`,`)) + vm := VM{_operators: newOperators()} + vm.getOperators().define(1000, operatorSpecifierXFY, NewAtom(`,`)) ok, err := Op(&vm, Integer(1000), atomXFY, atomComma, Success, nil).Force(context.Background()) assert.Equal(t, permissionError(operationModify, permissionTypeOperator, atomComma, nil), err) assert.False(t, ok) }) t.Run("an element of the operator list is ','", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(1000, operatorSpecifierXFY, NewAtom(`,`)) + vm := VM{_operators: newOperators()} + vm.getOperators().define(1000, operatorSpecifierXFY, NewAtom(`,`)) ok, err := Op(&vm, Integer(1000), atomXFY, List(atomComma), Success, nil).Force(context.Background()) assert.Equal(t, permissionError(operationModify, permissionTypeOperator, atomComma, nil), err) assert.False(t, ok) @@ -1174,8 +1220,8 @@ func TestOp(t *testing.T) { }) t.Run("modify", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(1001, operatorSpecifierXFY, NewAtom(`|`)) + vm := VM{_operators: newOperators()} + vm.getOperators().define(1001, operatorSpecifierXFY, NewAtom(`|`)) ok, err := Op(&vm, Integer(1000), atomXFY, atomBar, Success, nil).Force(context.Background()) assert.Equal(t, permissionError(operationModify, permissionTypeOperator, atomBar, nil), err) assert.False(t, ok) @@ -1207,8 +1253,8 @@ func TestOp(t *testing.T) { }) t.Run("modify", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(101, operatorSpecifierXFY, NewAtom(`|`)) + vm := VM{_operators: newOperators()} + vm.getOperators().define(101, operatorSpecifierXFY, NewAtom(`|`)) ok, err := Op(&vm, Integer(1000), atomXFY, List(atomBar), Success, nil).Force(context.Background()) assert.Equal(t, permissionError(operationModify, permissionTypeOperator, atomBar, nil), err) assert.False(t, ok) @@ -1218,16 +1264,16 @@ func TestOp(t *testing.T) { t.Run("There shall not be an infix and a postfix operator with the same name.", func(t *testing.T) { t.Run("infix", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(200, operatorSpecifierYF, NewAtom(`+`)) + vm := VM{_operators: newOperators()} + vm.getOperators().define(200, operatorSpecifierYF, NewAtom(`+`)) ok, err := Op(&vm, Integer(500), atomYFX, List(atomPlus), Success, nil).Force(context.Background()) assert.Equal(t, permissionError(operationCreate, permissionTypeOperator, atomPlus, nil), err) assert.False(t, ok) }) t.Run("postfix", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(500, operatorSpecifierYFX, NewAtom(`+`)) + vm := VM{_operators: newOperators()} + vm.getOperators().define(500, operatorSpecifierYFX, NewAtom(`+`)) ok, err := Op(&vm, Integer(200), atomYF, List(atomPlus), Success, nil).Force(context.Background()) assert.Equal(t, permissionError(operationCreate, permissionTypeOperator, atomPlus, nil), err) assert.False(t, ok) @@ -1236,10 +1282,10 @@ func TestOp(t *testing.T) { } func TestCurrentOp(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(900, operatorSpecifierXFX, NewAtom(`+++`)) - vm.operators.define(1000, operatorSpecifierXFX, NewAtom(`++`)) - vm.operators.define(1100, operatorSpecifierXFX, NewAtom(`+`)) + vm := VM{_operators: newOperators()} + vm.getOperators().define(900, operatorSpecifierXFX, NewAtom(`+++`)) + vm.getOperators().define(1000, operatorSpecifierXFX, NewAtom(`++`)) + vm.getOperators().define(1100, operatorSpecifierXFX, NewAtom(`+`)) t.Run("single solution", func(t *testing.T) { ok, err := CurrentOp(&vm, Integer(1100), atomXFX, atomPlus, Success, nil).Force(context.Background()) @@ -2443,9 +2489,14 @@ func TestCatch(t *testing.T) { func TestCurrentPredicate(t *testing.T) { t.Run("user defined predicate", func(t *testing.T) { - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{}, - }} + vm := VM{ + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{}, + }, + ), + } ok, err := CurrentPredicate(&vm, &compound{ functor: atomSlash, args: []Term{ @@ -2462,11 +2513,22 @@ func TestCurrentPredicate(t *testing.T) { v := NewVariable() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{}, - {name: NewAtom("bar"), arity: 1}: &userDefined{}, - {name: NewAtom("baz"), arity: 1}: &userDefined{}, - }} + vm := VM{ + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{}, + }, + procedurePair{ + Key: procedureIndicator{name: NewAtom("bar"), arity: 1}, + Value: &userDefined{}, + }, + procedurePair{ + Key: procedureIndicator{name: NewAtom("baz"), arity: 1}, + Value: &userDefined{}, + }, + ), + } ok, err := CurrentPredicate(&vm, v, func(env *Env) *Promise { c, ok := env.Resolve(v).(*compound) assert.True(t, ok) @@ -2493,10 +2555,65 @@ func TestCurrentPredicate(t *testing.T) { assert.True(t, baz) }) + t.Run("ordered current predicate", func(t *testing.T) { + var foo, bar, baz int + + for i := 0; i < 10; i++ { + v := NewVariable() + vm := VM{ + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{}, + }, + procedurePair{ + Key: procedureIndicator{name: NewAtom("bar"), arity: 1}, + Value: &userDefined{}, + }, + procedurePair{ + Key: procedureIndicator{name: NewAtom("baz"), arity: 1}, + Value: &userDefined{}, + }, + ), + } + index := 0 + ok, err := CurrentPredicate(&vm, v, func(env *Env) *Promise { + c, ok := env.Resolve(v).(*compound) + assert.True(t, ok) + assert.Equal(t, atomSlash, c.Functor()) + assert.Equal(t, 2, c.Arity()) + assert.Equal(t, Integer(1), c.Arg(1)) + switch c.Arg(0) { + case NewAtom("foo"): + foo = index + case NewAtom("bar"): + bar = index + case NewAtom("baz"): + baz = index + default: + assert.Fail(t, "unreachable") + } + index += 1 + return Bool(false) + }, nil).Force(context.Background()) + assert.NoError(t, err) + assert.False(t, ok) + + assert.Equal(t, 0, foo) + assert.Equal(t, 1, bar) + assert.Equal(t, 2, baz) + } + }) + t.Run("builtin predicate", func(t *testing.T) { - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: atomEqual, arity: 2}: Predicate2(Unify), - }} + vm := VM{ + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: atomEqual, arity: 2}, + Value: Predicate2(Unify), + }, + ), + } ok, err := CurrentPredicate(&vm, &compound{ functor: atomSlash, args: []Term{ @@ -2606,10 +2723,10 @@ func TestAssertz(t *testing.T) { {opcode: opExit}, }, }, - }}, vm.procedures[procedureIndicator{ + }}, vm.procedures.GetPair(procedureIndicator{ name: NewAtom("foo"), arity: 1, - }]) + }).Value) }) t.Run("clause is a variable", func(t *testing.T) { @@ -2673,9 +2790,12 @@ func TestAssertz(t *testing.T) { t.Run("static", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{dynamic: false}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 0}, + Value: &userDefined{dynamic: false}, + }, + ), } ok, err := Assertz(&vm, NewAtom("foo"), Success, nil).Force(context.Background()) @@ -2730,7 +2850,7 @@ func TestAsserta(t *testing.T) { {opcode: opExit}, }, }, - }}, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}]) + }}, vm.procedures.GetPair(procedureIndicator{name: NewAtom("foo"), arity: 1}).Value) }) t.Run("rule", func(t *testing.T) { @@ -2804,7 +2924,7 @@ func TestAsserta(t *testing.T) { {opcode: opExit}, }, }, - }}, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 0}]) + }}, vm.procedures.GetPair(procedureIndicator{name: NewAtom("foo"), arity: 0}).Value) }) t.Run("clause is a variable", func(t *testing.T) { @@ -2876,9 +2996,12 @@ func TestAsserta(t *testing.T) { t.Run("static", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{dynamic: false}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 0}, + Value: &userDefined{dynamic: false}, + }, + ), } ok, err := Asserta(&vm, NewAtom("foo"), Success, nil).Force(context.Background()) @@ -2909,13 +3032,16 @@ func TestAsserta(t *testing.T) { func TestRetract(t *testing.T) { t.Run("retract the first one", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{dynamic: true, clauses: []clause{ - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, - }}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{dynamic: true, clauses: []clause{ + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, + }}, + }, + ), } ok, err := Retract(&vm, &compound{ @@ -2928,18 +3054,21 @@ func TestRetract(t *testing.T) { assert.Equal(t, &userDefined{dynamic: true, clauses: []clause{ {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, - }}, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}]) + }}, vm.procedures.GetPair(procedureIndicator{name: NewAtom("foo"), arity: 1}).Value) }) t.Run("retract the specific one", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{dynamic: true, clauses: []clause{ - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, - }}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{dynamic: true, clauses: []clause{ + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, + }}, + }, + ), } ok, err := Retract(&vm, &compound{ @@ -2952,18 +3081,21 @@ func TestRetract(t *testing.T) { assert.Equal(t, &userDefined{dynamic: true, clauses: []clause{ {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, - }}, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}]) + }}, vm.procedures.GetPair(procedureIndicator{name: NewAtom("foo"), arity: 1}).Value) }) t.Run("retract all", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{dynamic: true, clauses: []clause{ - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, - }}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{dynamic: true, clauses: []clause{ + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, + }}, + }, + ), } ok, err := Retract(&vm, &compound{ @@ -2972,7 +3104,8 @@ func TestRetract(t *testing.T) { }, Failure, nil).Force(context.Background()) assert.NoError(t, err) assert.False(t, ok) - assert.Empty(t, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}].(*userDefined).clauses) + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 1}) + assert.Empty(t, p.(*userDefined).clauses) }) t.Run("variable", func(t *testing.T) { @@ -3002,9 +3135,10 @@ func TestRetract(t *testing.T) { t.Run("static", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{dynamic: false}, - }, + procedures: buildOrderedMap(procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 0}, + Value: &userDefined{dynamic: false}, + }), } ok, err := Retract(&vm, NewAtom("foo"), Success, nil).Force(context.Background()) @@ -3017,11 +3151,14 @@ func TestRetract(t *testing.T) { t.Run("exception in continuation", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{dynamic: true, clauses: []clause{ - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, - }}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{dynamic: true, clauses: []clause{ + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, + }}, + }, + ), } ok, err := Retract(&vm, &compound{ @@ -3034,20 +3171,24 @@ func TestRetract(t *testing.T) { assert.False(t, ok) // removed - assert.Empty(t, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}].(*userDefined).clauses) + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 1}) + assert.Empty(t, p.(*userDefined).clauses) }) } func TestAbolish(t *testing.T) { t.Run("ok", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{dynamic: true, clauses: []clause{ - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, - {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, - }}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{dynamic: true, clauses: []clause{ + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}}, + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}}, + }}, + }, + ), } ok, err := Abolish(&vm, &compound{ @@ -3057,7 +3198,7 @@ func TestAbolish(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - _, ok = vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}] + _, ok = vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 1}) assert.False(t, ok) }) @@ -3138,9 +3279,12 @@ func TestAbolish(t *testing.T) { t.Run("The predicate indicator pi is that of a static procedure", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{dynamic: false}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 0}, + Value: &userDefined{dynamic: false}, + }, + ), } ok, err := Abolish(&vm, &compound{ functor: atomSlash, @@ -4058,9 +4202,9 @@ func TestWriteTerm(t *testing.T) { } var vm VM - vm.operators.define(500, operatorSpecifierYFX, atomPlus) - vm.operators.define(200, operatorSpecifierFY, atomPlus) - vm.operators.define(200, operatorSpecifierYF, atomMinus) + vm.getOperators().define(500, operatorSpecifierYFX, atomPlus) + vm.getOperators().define(200, operatorSpecifierFY, atomPlus) + vm.getOperators().define(200, operatorSpecifierYF, atomMinus) for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { buf.Reset() @@ -5400,17 +5544,20 @@ func TestClause(t *testing.T) { var c int vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("green"), arity: 1}: &userDefined{public: true, clauses: []clause{ - {raw: &compound{ - functor: atomIf, args: []Term{ - &compound{functor: NewAtom("green"), args: []Term{x}}, - &compound{functor: NewAtom("moldy"), args: []Term{x}}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("green"), arity: 1}, + Value: &userDefined{public: true, clauses: []clause{ + {raw: &compound{ + functor: atomIf, args: []Term{ + &compound{functor: NewAtom("green"), args: []Term{x}}, + &compound{functor: NewAtom("moldy"), args: []Term{x}}, + }, + }}, + {raw: &compound{functor: NewAtom("green"), args: []Term{NewAtom("kermit")}}}, }}, - {raw: &compound{functor: NewAtom("green"), args: []Term{NewAtom("kermit")}}}, - }}, - }, + }, + ), } ok, err := Clause(&vm, &compound{ functor: NewAtom("green"), @@ -5460,11 +5607,14 @@ func TestClause(t *testing.T) { what, body := NewVariable(), NewVariable() vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("green"), arity: 1}: Predicate1(func(_ *VM, t Term, f Cont, env *Env) *Promise { - return Bool(true) - }), - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("green"), arity: 1}, + Value: Predicate1(func(_ *VM, t Term, f Cont, env *Env) *Promise { + return Bool(true) + }), + }, + ), } ok, err := Clause(&vm, &compound{ functor: NewAtom("green"), @@ -5488,11 +5638,14 @@ func TestClause(t *testing.T) { defer setMemFree(1)() vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("green"), arity: 1}: &userDefined{public: true, clauses: []clause{ - {raw: NewAtom("green").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable())}, - }}, - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("green"), arity: 1}, + Value: &userDefined{public: true, clauses: []clause{ + {raw: NewAtom("green").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable())}, + }}, + }, + ), } ok, err := Clause(&vm, NewAtom("green").Apply(NewVariable()), NewVariable(), Success, nil).Force(context.Background()) assert.Equal(t, resourceError(resourceMemory, nil), err) @@ -7588,3 +7741,12 @@ func setMemFree(n int64) func() { memFree = orig } } + +func buildOrderedMap(pairs ...procedurePair) *orderedmap.OrderedMap[procedureIndicator, procedure] { + procedures := orderedmap.New[procedureIndicator, procedure]() + // Type alias for pairProcedure (aka orderedmap.Pair[procedureIndicator, procedure]) not working, compiler didn't accept it thinking is not the same, but it is 😢 + for _, p := range pairs { + procedures.AddPairs(orderedmap.Pair[procedureIndicator, procedure]{Key: p.Key, Value: p.Value}) + } + return procedures +} diff --git a/engine/compound.go b/engine/compound.go index 075e8fc..1cc1684 100644 --- a/engine/compound.go +++ b/engine/compound.go @@ -45,9 +45,11 @@ func WriteCompound(w io.Writer, c Compound, opts *WriteOptions, env *Env) error return writeCompoundFunctionalNotation(w, c, opts, env) } - for _, o := range opts.ops[c.Functor()] { - if o.specifier.arity() == c.Arity() { - return writeCompoundOp(w, c, opts, env, &o) + if ops, ok := opts.getOps().Get(c.Functor()); ok { + for _, o := range ops { + if o.specifier.arity() == c.Arity() { + return writeCompoundOp(w, c, opts, env, &o) + } } } diff --git a/engine/compound_test.go b/engine/compound_test.go index d883104..781c131 100644 --- a/engine/compound_test.go +++ b/engine/compound_test.go @@ -14,7 +14,7 @@ func TestWriteCompound(t *testing.T) { r := f.Apply(w) env := NewEnv().bind(v, l).bind(w, r) - ops := operators{} + ops := newOperators() ops.define(1200, operatorSpecifierXFX, NewAtom(`:-`)) ops.define(1200, operatorSpecifierFX, NewAtom(`:-`)) ops.define(1200, operatorSpecifierXF, NewAtom(`-:`)) @@ -37,20 +37,20 @@ func TestWriteCompound(t *testing.T) { {title: "list-ish", term: PartialList(NewAtom(`rest`), NewAtom(`a`), NewAtom(`b`)), output: `[a,b|rest]`}, {title: "circular list", term: l, output: `[a,b,a|...]`}, {title: "curly brackets", term: atomEmptyBlock.Apply(NewAtom(`foo`)), output: `{foo}`}, - {title: "fx", term: atomIf.Apply(atomIf.Apply(NewAtom(`foo`))), opts: WriteOptions{ops: ops, priority: 1201}, output: `:- (:-foo)`}, - {title: "fy", term: atomNegation.Apply(atomMinus.Apply(atomNegation.Apply(NewAtom(`foo`)))), opts: WriteOptions{ops: ops, priority: 1201}, output: `\+ - (\+foo)`}, - {title: "xf", term: NewAtom(`-:`).Apply(NewAtom(`-:`).Apply(NewAtom(`foo`))), opts: WriteOptions{ops: ops, priority: 1201}, output: `(foo-:)-:`}, - {title: "yf", term: NewAtom(`+/`).Apply(NewAtom(`--`).Apply(NewAtom(`+/`).Apply(NewAtom(`foo`)))), opts: WriteOptions{ops: ops, priority: 1201}, output: `(foo+/)-- +/`}, - {title: "xfx", term: atomIf.Apply(NewAtom("foo"), atomIf.Apply(NewAtom("bar"), NewAtom("baz"))), opts: WriteOptions{ops: ops, priority: 1201}, output: `foo:-(bar:-baz)`}, - {title: "yfx", term: atomAsterisk.Apply(Integer(2), atomPlus.Apply(Integer(2), Integer(2))), opts: WriteOptions{ops: ops, priority: 1201}, output: `2*(2+2)`}, - {title: "xfy", term: atomComma.Apply(Integer(2), atomBar.Apply(Integer(2), Integer(2))), opts: WriteOptions{ops: ops, priority: 1201}, output: `2,(2|2)`}, - {title: "ignore_ops(false)", term: atomPlus.Apply(Integer(2), Integer(-2)), opts: WriteOptions{ignoreOps: false, ops: ops, priority: 1201}, output: `2+ -2`}, - {title: "ignore_ops(true)", term: atomPlus.Apply(Integer(2), Integer(-2)), opts: WriteOptions{ignoreOps: true, ops: ops, priority: 1201}, output: `+(2,-2)`}, - {title: "number_vars(false)", term: f.Apply(atomVar.Apply(Integer(0)), atomVar.Apply(Integer(1)), atomVar.Apply(Integer(25)), atomVar.Apply(Integer(26)), atomVar.Apply(Integer(27))), opts: WriteOptions{quoted: true, numberVars: false, ops: ops, priority: 1201}, output: `f('$VAR'(0),'$VAR'(1),'$VAR'(25),'$VAR'(26),'$VAR'(27))`}, - {title: "number_vars(true)", term: f.Apply(atomVar.Apply(Integer(0)), atomVar.Apply(Integer(1)), atomVar.Apply(Integer(25)), atomVar.Apply(Integer(26)), atomVar.Apply(Integer(27))), opts: WriteOptions{quoted: true, numberVars: true, ops: ops, priority: 1201}, output: `f(A,B,Z,A1,B1)`}, - {title: "prefix: spacing between operators", term: atomAsterisk.Apply(NewAtom("a"), atomMinus.Apply(NewAtom("b"))), opts: WriteOptions{ops: ops, priority: 1201}, output: `a* -b`}, - {title: "postfix: spacing between unary minus and open/close", term: atomMinus.Apply(NewAtom(`+/`).Apply(NewAtom("a"))), opts: WriteOptions{ops: ops, priority: 1201}, output: `- (a+/)`}, - {title: "infix: spacing between unary minus and open/close", term: atomMinus.Apply(atomAsterisk.Apply(NewAtom("a"), NewAtom("b"))), opts: WriteOptions{ops: ops, priority: 1201}, output: `- (a*b)`}, + {title: "fx", term: atomIf.Apply(atomIf.Apply(NewAtom(`foo`))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `:- (:-foo)`}, + {title: "fy", term: atomNegation.Apply(atomMinus.Apply(atomNegation.Apply(NewAtom(`foo`)))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `\+ - (\+foo)`}, + {title: "xf", term: NewAtom(`-:`).Apply(NewAtom(`-:`).Apply(NewAtom(`foo`))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `(foo-:)-:`}, + {title: "yf", term: NewAtom(`+/`).Apply(NewAtom(`--`).Apply(NewAtom(`+/`).Apply(NewAtom(`foo`)))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `(foo+/)-- +/`}, + {title: "xfx", term: atomIf.Apply(NewAtom("foo"), atomIf.Apply(NewAtom("bar"), NewAtom("baz"))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `foo:-(bar:-baz)`}, + {title: "yfx", term: atomAsterisk.Apply(Integer(2), atomPlus.Apply(Integer(2), Integer(2))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `2*(2+2)`}, + {title: "xfy", term: atomComma.Apply(Integer(2), atomBar.Apply(Integer(2), Integer(2))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `2,(2|2)`}, + {title: "ignore_ops(false)", term: atomPlus.Apply(Integer(2), Integer(-2)), opts: WriteOptions{ignoreOps: false, _ops: ops, priority: 1201}, output: `2+ -2`}, + {title: "ignore_ops(true)", term: atomPlus.Apply(Integer(2), Integer(-2)), opts: WriteOptions{ignoreOps: true, _ops: ops, priority: 1201}, output: `+(2,-2)`}, + {title: "number_vars(false)", term: f.Apply(atomVar.Apply(Integer(0)), atomVar.Apply(Integer(1)), atomVar.Apply(Integer(25)), atomVar.Apply(Integer(26)), atomVar.Apply(Integer(27))), opts: WriteOptions{quoted: true, numberVars: false, _ops: ops, priority: 1201}, output: `f('$VAR'(0),'$VAR'(1),'$VAR'(25),'$VAR'(26),'$VAR'(27))`}, + {title: "number_vars(true)", term: f.Apply(atomVar.Apply(Integer(0)), atomVar.Apply(Integer(1)), atomVar.Apply(Integer(25)), atomVar.Apply(Integer(26)), atomVar.Apply(Integer(27))), opts: WriteOptions{quoted: true, numberVars: true, _ops: ops, priority: 1201}, output: `f(A,B,Z,A1,B1)`}, + {title: "prefix: spacing between operators", term: atomAsterisk.Apply(NewAtom("a"), atomMinus.Apply(NewAtom("b"))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `a* -b`}, + {title: "postfix: spacing between unary minus and open/close", term: atomMinus.Apply(NewAtom(`+/`).Apply(NewAtom("a"))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `- (a+/)`}, + {title: "infix: spacing between unary minus and open/close", term: atomMinus.Apply(atomAsterisk.Apply(NewAtom("a"), NewAtom("b"))), opts: WriteOptions{_ops: ops, priority: 1201}, output: `- (a*b)`}, {title: "recursive", term: r, output: `f(...)`}, } diff --git a/engine/dcg_test.go b/engine/dcg_test.go index 87b0a94..da9e0d6 100644 --- a/engine/dcg_test.go +++ b/engine/dcg_test.go @@ -11,12 +11,15 @@ func TestVM_Phrase(t *testing.T) { t.Run("ok", func(t *testing.T) { var called bool vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("a"), arity: 2}: Predicate2(func(_ *VM, s0, s Term, k Cont, env *Env) *Promise { - called = true - return k(env) - }), - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("a"), arity: 2}, + Value: Predicate2(func(_ *VM, s0, s Term, k Cont, env *Env) *Promise { + called = true + return k(env) + }), + }, + ), } s0, s := NewVariable(), NewVariable() diff --git a/engine/parser.go b/engine/parser.go index bd51e93..2f469aa 100644 --- a/engine/parser.go +++ b/engine/parser.go @@ -3,6 +3,7 @@ package engine import ( "errors" "fmt" + orderedmap "github.com/wk8/go-ordered-map/v2" "io" "math/big" "reflect" @@ -21,7 +22,7 @@ var ( // Parser turns bytes into Term. type Parser struct { lexer Lexer - operators operators + _operators *operators doubleQuotes doubleQuotes Vars []ParsedVariable @@ -41,14 +42,11 @@ type ParsedVariable struct { // NewParser creates a new parser from the current VM and io.RuneReader. func NewParser(vm *VM, r io.RuneReader) *Parser { - if vm.operators == nil { - vm.operators = operators{} - } return &Parser{ lexer: Lexer{ input: newRuneRingBuffer(r), }, - operators: vm.operators, + _operators: vm.getOperators(), doubleQuotes: vm.doubleQuotes, } } @@ -258,48 +256,48 @@ func (s operatorSpecifier) arity() int { }[s] } -type operators map[Atom][_operatorClassLen]operator +type operators struct { + *orderedmap.OrderedMap[Atom, [_operatorClassLen]operator] +} + +func newOperators() *operators { + return &operators{OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator]()} +} func (ops *operators) defined(name Atom) bool { - ops.init() - _, ok := (*ops)[name] + _, ok := ops.Get(name) return ok } func (ops *operators) definedInClass(name Atom, class operatorClass) bool { - ops.init() - return (*ops)[name][class] != operator{} + if o, ok := ops.Get(name); ok { + return o[class] != operator{} + } + return false } func (ops *operators) define(p Integer, spec operatorSpecifier, op Atom) { if p == 0 { return } - ops.init() - os := (*ops)[op] + + os, _ := ops.Get(op) os[spec.class()] = operator{ priority: p, specifier: spec, name: op, } - (*ops)[op] = os -} - -func (ops *operators) init() { - if *ops != nil { - return - } - *ops = map[Atom][3]operator{} + ops.Set(op, os) } func (ops *operators) remove(name Atom, class operatorClass) { - os := (*ops)[name] + os, _ := ops.Get(name) os[class] = operator{} if os == ([_operatorClassLen]operator{}) { - delete(*ops, name) + ops.Delete(name) return } - (*ops)[name] = os + ops.Set(name, os) } type operator struct { @@ -342,6 +340,13 @@ func (d doubleQuotes) String() string { }[d] } +func (p *Parser) getOperators() *operators { + if p._operators == nil { + p._operators = newOperators() + } + return p._operators +} + // Loosely based on Pratt parser explained in this article: https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html func (p *Parser) term(maxPriority Integer) (Term, error) { var lhs Term @@ -417,7 +422,8 @@ func (p *Parser) prefix(maxPriority Integer) (operator, error) { p.backup() } - if op := p.operators[a][operatorClassPrefix]; op != (operator{}) && op.priority <= maxPriority { + o, _ := p.getOperators().Get(a) + if op := o[operatorClassPrefix]; op != (operator{}) && op.priority <= maxPriority { return op, nil } @@ -431,13 +437,14 @@ func (p *Parser) infix(maxPriority Integer) (operator, error) { return operator{}, errNoOp } - if op := p.operators[a][operatorClassInfix]; op != (operator{}) { + o, _ := p.getOperators().Get(a) + if op := o[operatorClassInfix]; op != (operator{}) { l, _ := op.bindingPriorities() if l <= maxPriority { return op, nil } } - if op := p.operators[a][operatorClassPostfix]; op != (operator{}) { + if op := o[operatorClassPostfix]; op != (operator{}) { l, _ := op.bindingPriorities() if l <= maxPriority { return op, nil @@ -559,7 +566,7 @@ func (p *Parser) term0Atom(maxPriority Integer) (Term, error) { } // 6.3.1.3 An atom which is an operator shall not be the immediate operand (3.120) of an operator. - if t, ok := t.(Atom); ok && maxPriority < 1201 && p.operators.defined(t) { + if t, ok := t.(Atom); ok && maxPriority < 1201 && p.getOperators().defined(t) { p.backup() return nil, errExpectation } @@ -752,7 +759,7 @@ func (p *Parser) functionalNotation(functor Atom) (Term, error) { func (p *Parser) arg() (Term, error) { if arg, err := p.atom(); err == nil { - if p.operators.defined(arg) { + if p.getOperators().defined(arg) { // Check if this atom is not followed by its own arguments. switch t, _ := p.next(); t.kind { case tokenComma, tokenClose, tokenBar, tokenCloseList: diff --git a/engine/parser_test.go b/engine/parser_test.go index a69fa37..20872df 100644 --- a/engine/parser_test.go +++ b/engine/parser_test.go @@ -22,7 +22,7 @@ func assertEqualFloatAware(t *testing.T, expected interface{}, actual interface{ } func TestParser_Term(t *testing.T) { - ops := operators{} + ops := newOperators() ops.define(1000, operatorSpecifierXFY, NewAtom(`,`)) ops.define(500, operatorSpecifierYFX, NewAtom(`+`)) ops.define(400, operatorSpecifierYFX, NewAtom(`*`)) @@ -175,7 +175,7 @@ func TestParser_Term(t *testing.T) { lexer: Lexer{ input: newRuneRingBuffer(strings.NewReader(tc.input)), }, - operators: ops, + _operators: ops, doubleQuotes: tc.doubleQuotes, } term, err := p.Term() diff --git a/engine/term.go b/engine/term.go index 8bbf9f5..f36b075 100644 --- a/engine/term.go +++ b/engine/term.go @@ -2,6 +2,7 @@ package engine import ( "fmt" + orderedmap "github.com/wk8/go-ordered-map/v2" "io" "strings" ) @@ -19,7 +20,7 @@ type WriteOptions struct { variableNames map[Variable]Atom numberVars bool - ops operators + _ops *operators priority Integer visited map[termID]struct{} prefixMinus bool @@ -57,14 +58,31 @@ func (o WriteOptions) withRight(op operator) *WriteOptions { return &o } +func (o WriteOptions) getOps() *operators { + if o._ops == nil { + o._ops = newOperators() + } + return o._ops +} + var defaultWriteOptions = WriteOptions{ - ops: operators{ - atomPlus: [_operatorClassLen]operator{ - operatorClassInfix: {priority: 500, specifier: operatorSpecifierYFX, name: atomPlus}, // for flag+value - }, - atomSlash: [_operatorClassLen]operator{ - operatorClassInfix: {priority: 400, specifier: operatorSpecifierYFX, name: atomSlash}, // for principal functors - }, + _ops: &operators{ + OrderedMap: orderedmap.New[Atom, [_operatorClassLen]operator]( + orderedmap.WithInitialData( + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: atomPlus, + Value: [_operatorClassLen]operator{ + operatorClassInfix: {priority: 500, specifier: operatorSpecifierYFX, name: atomPlus}, // for flag+value + }, + }, + orderedmap.Pair[Atom, [_operatorClassLen]operator]{ + Key: atomSlash, + Value: [_operatorClassLen]operator{ + operatorClassInfix: {priority: 400, specifier: operatorSpecifierYFX, name: atomSlash}, // for principal functors + }, + }, + ), + ), }, variableNames: map[Variable]Atom{}, priority: 1200, diff --git a/engine/text.go b/engine/text.go index 9e00b06..8af1f7f 100644 --- a/engine/text.go +++ b/engine/text.go @@ -3,6 +3,7 @@ package engine import ( "context" "fmt" + orderedmap "github.com/wk8/go-ordered-map/v2" "io/fs" "strings" ) @@ -27,16 +28,14 @@ func (vm *VM) Compile(ctx context.Context, s string, args ...interface{}) error return err } - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - for pi, u := range t.clauses { - if existing, ok := vm.procedures[pi].(*userDefined); ok && existing.multifile && u.multifile { - existing.clauses = append(existing.clauses, u.clauses...) + for c := t.clauses.Oldest(); c != nil; c = c.Next() { + p, _ := vm.getProcedure(c.Key) + if existing, ok := p.(*userDefined); ok && existing.multifile && c.Value.multifile { + existing.clauses = append(existing.clauses, c.Value.clauses...) continue } - vm.procedures[pi] = u + vm.setProcedure(c.Key, c.Value) } for _, g := range t.goals { @@ -79,7 +78,7 @@ func Consult(vm *VM, files Term, k Cont, env *Env) *Promise { func (vm *VM) compile(ctx context.Context, text *text, s string, args ...interface{}) error { if text.clauses == nil { - text.clauses = map[procedureIndicator]*userDefined{} + text.clauses = orderedmap.New[procedureIndicator, *userDefined]() } s = ignoreShebangLine(s) @@ -221,7 +220,7 @@ func (vm *VM) open(file Term, env *Env) (string, []byte, error) { type text struct { buf clauses - clauses map[procedureIndicator]*userDefined + clauses *orderedmap.OrderedMap[procedureIndicator, *userDefined] goals []Term } @@ -244,10 +243,10 @@ func (t *text) forEachUserDefined(pi Term, f func(u *userDefined)) error { return InstantiationError(nil) case Integer: pi := procedureIndicator{name: n, arity: a} - u, ok := t.clauses[pi] + u, ok := t.getClause(pi) if !ok { u = &userDefined{} - t.clauses[pi] = u + t.setClause(pi, u) } f(u) default: @@ -269,10 +268,10 @@ func (t *text) flush() error { } pi := t.buf[0].pi - u, ok := t.clauses[pi] + u, ok := t.getClause(pi) if !ok { u = &userDefined{} - t.clauses[pi] = u + t.setClause(pi, u) } if len(u.clauses) > 0 && !u.discontiguous { return &discontiguousError{pi: pi} @@ -282,6 +281,20 @@ func (t *text) flush() error { return nil } +func (t *text) getClause(key procedureIndicator) (*userDefined, bool) { + if t.clauses == nil { + return nil, false + } + return t.clauses.Get(key) +} + +func (t *text) setClause(key procedureIndicator, value *userDefined) (*userDefined, bool) { + if t.clauses == nil { + t.clauses = orderedmap.New[procedureIndicator, *userDefined]() + } + return t.clauses.Set(key, value) +} + func ignoreShebangLine(query string) string { if !strings.HasPrefix(query, "#!") { return query diff --git a/engine/text_test.go b/engine/text_test.go index e18fc2e..2eb3a31 100644 --- a/engine/text_test.go +++ b/engine/text_test.go @@ -4,6 +4,7 @@ import ( "context" "embed" "errors" + orderedmap "github.com/wk8/go-ordered-map/v2" "io" "io/fs" "testing" @@ -23,345 +24,395 @@ func mustOpen(fs fs.FS, name string) fs.File { } func TestVM_Compile(t *testing.T) { + varCounter = 1 + tests := []struct { title string text string args []interface{} err error - result map[procedureIndicator]procedure + result *orderedmap.OrderedMap[procedureIndicator, procedure] }{ {title: "shebang", text: `#!/foo/bar foo(a). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opExit}, + }, }, }, }, }, - }}, - {title: "shebang: no following lines", text: `#!/foo/bar`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - multifile: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("c")}, - {opcode: opExit}, + )}, + {title: "shebang: no following lines", text: `#!/foo/bar`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + multifile: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("c")}, + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "facts", text: ` foo(a). foo(b). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opExit}, + }, }, - }, - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("b")}, - {opcode: opExit}, + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("b")}, + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "rules", text: ` bar :- true. bar(X, "abc", [a, b], [a, b|Y], f(a)) :- X, !, foo(X, "abc", [a, b], [a, b|Y], f(a)). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - multifile: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("c")}, - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + multifile: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("c")}, + {opcode: opExit}, + }, }, }, }, }, - {name: NewAtom("bar"), arity: 0}: &userDefined{ - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("bar"), arity: 0}, - raw: atomIf.Apply(NewAtom("bar"), atomTrue), - bytecode: bytecode{ - {opcode: opEnter}, - {opcode: opCall, operand: procedureIndicator{name: atomTrue, arity: 0}}, - {opcode: opExit}, + procedurePair{ + Key: procedureIndicator{name: NewAtom("bar"), arity: 0}, + Value: &userDefined{ + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("bar"), arity: 0}, + raw: atomIf.Apply(NewAtom("bar"), atomTrue), + bytecode: bytecode{ + {opcode: opEnter}, + {opcode: opCall, operand: procedureIndicator{name: atomTrue, arity: 0}}, + {opcode: opExit}, + }, }, }, }, }, - {name: NewAtom("bar"), arity: 5}: &userDefined{ - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("bar"), arity: 5}, - raw: atomIf.Apply( - NewAtom("bar").Apply(lastVariable()+1, charList("abc"), List(NewAtom("a"), NewAtom("b")), PartialList(lastVariable()+2, NewAtom("a"), NewAtom("b")), NewAtom("f").Apply(NewAtom("a"))), - seq(atomComma, - lastVariable()+1, - atomCut, - NewAtom("foo").Apply(lastVariable()+1, charList("abc"), List(NewAtom("a"), NewAtom("b")), PartialList(lastVariable()+2, NewAtom("a"), NewAtom("b")), NewAtom("f").Apply(NewAtom("a"))), + procedurePair{ + Key: procedureIndicator{name: NewAtom("bar"), arity: 5}, + Value: &userDefined{ + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("bar"), arity: 5}, + raw: atomIf.Apply( + NewAtom("bar").Apply(lastVariable()+1, charList("abc"), List(NewAtom("a"), NewAtom("b")), PartialList(lastVariable()+2, NewAtom("a"), NewAtom("b")), NewAtom("f").Apply(NewAtom("a"))), + seq(atomComma, + lastVariable()+1, + atomCut, + NewAtom("foo").Apply(lastVariable()+1, charList("abc"), List(NewAtom("a"), NewAtom("b")), PartialList(lastVariable()+2, NewAtom("a"), NewAtom("b")), NewAtom("f").Apply(NewAtom("a"))), + ), ), - ), - vars: []Variable{lastVariable() + 1, lastVariable() + 2}, - bytecode: bytecode{ - {opcode: opGetVar, operand: Integer(0)}, - {opcode: opGetConst, operand: charList("abc")}, - {opcode: opGetList, operand: Integer(2)}, - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opGetConst, operand: NewAtom("b")}, - {opcode: opPop}, - {opcode: opGetPartial, operand: Integer(2)}, - {opcode: opGetVar, operand: Integer(1)}, - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opGetConst, operand: NewAtom("b")}, - {opcode: opPop}, - {opcode: opGetFunctor, operand: procedureIndicator{name: NewAtom("f"), arity: 1}}, - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opPop}, - {opcode: opEnter}, - {opcode: opPutVar, operand: Integer(0)}, - {opcode: opCall, operand: procedureIndicator{name: atomCall, arity: 1}}, - {opcode: opCut}, - {opcode: opPutVar, operand: Integer(0)}, - {opcode: opPutConst, operand: charList("abc")}, - {opcode: opPutList, operand: Integer(2)}, - {opcode: opPutConst, operand: NewAtom("a")}, - {opcode: opPutConst, operand: NewAtom("b")}, - {opcode: opPop}, - {opcode: opPutPartial, operand: Integer(2)}, - {opcode: opPutVar, operand: Integer(1)}, - {opcode: opPutConst, operand: NewAtom("a")}, - {opcode: opPutConst, operand: NewAtom("b")}, - {opcode: opPop}, - {opcode: opPutFunctor, operand: procedureIndicator{name: NewAtom("f"), arity: 1}}, - {opcode: opPutConst, operand: NewAtom("a")}, - {opcode: opPop}, - {opcode: opCall, operand: procedureIndicator{name: NewAtom("foo"), arity: 5}}, - {opcode: opExit}, + vars: []Variable{lastVariable() + 1, lastVariable() + 2}, + bytecode: bytecode{ + {opcode: opGetVar, operand: Integer(0)}, + {opcode: opGetConst, operand: charList("abc")}, + {opcode: opGetList, operand: Integer(2)}, + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opGetConst, operand: NewAtom("b")}, + {opcode: opPop}, + {opcode: opGetPartial, operand: Integer(2)}, + {opcode: opGetVar, operand: Integer(1)}, + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opGetConst, operand: NewAtom("b")}, + {opcode: opPop}, + {opcode: opGetFunctor, operand: procedureIndicator{name: NewAtom("f"), arity: 1}}, + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opPop}, + {opcode: opEnter}, + {opcode: opPutVar, operand: Integer(0)}, + {opcode: opCall, operand: procedureIndicator{name: atomCall, arity: 1}}, + {opcode: opCut}, + {opcode: opPutVar, operand: Integer(0)}, + {opcode: opPutConst, operand: charList("abc")}, + {opcode: opPutList, operand: Integer(2)}, + {opcode: opPutConst, operand: NewAtom("a")}, + {opcode: opPutConst, operand: NewAtom("b")}, + {opcode: opPop}, + {opcode: opPutPartial, operand: Integer(2)}, + {opcode: opPutVar, operand: Integer(1)}, + {opcode: opPutConst, operand: NewAtom("a")}, + {opcode: opPutConst, operand: NewAtom("b")}, + {opcode: opPop}, + {opcode: opPutFunctor, operand: procedureIndicator{name: NewAtom("f"), arity: 1}}, + {opcode: opPutConst, operand: NewAtom("a")}, + {opcode: opPop}, + {opcode: opCall, operand: procedureIndicator{name: NewAtom("foo"), arity: 5}}, + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "dynamic", text: ` :- dynamic(foo/1). foo(a). foo(b). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - public: true, - dynamic: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + public: true, + dynamic: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opExit}, + }, }, - }, - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("b")}, - {opcode: opExit}, + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("b")}, + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "multifile", text: ` :- multifile(foo/1). foo(a). foo(b). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - multifile: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("c")}, - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + multifile: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("c")}, + {opcode: opExit}, + }, }, - }, - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opExit}, + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opExit}, + }, }, - }, - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("b")}, - {opcode: opExit}, + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("b")}, + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "discontiguous", text: ` :- discontiguous(foo/1). foo(a). bar(a). foo(b). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - discontiguous: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + discontiguous: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opExit}, + }, }, - }, - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("b")}, - {opcode: opExit}, + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("b")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("b")}, + {opcode: opExit}, + }, }, }, }, }, - {name: NewAtom("bar"), arity: 1}: &userDefined{ - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("bar"), arity: 1}, - raw: &compound{functor: NewAtom("bar"), args: []Term{NewAtom("a")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("a")}, - {opcode: opExit}, + procedurePair{ + Key: procedureIndicator{name: NewAtom("bar"), arity: 1}, + Value: &userDefined{ + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("bar"), arity: 1}, + raw: &compound{functor: NewAtom("bar"), args: []Term{NewAtom("a")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("a")}, + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "include", text: ` :- include('testdata/foo'). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{ - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, - raw: NewAtom("foo"), - bytecode: bytecode{ - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + multifile: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("c")}, + {opcode: opExit}, + }, }, }, }, }, - {name: NewAtom("foo"), arity: 1}: &userDefined{ - multifile: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("c")}, - {opcode: opExit}, + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 0}, + Value: &userDefined{ + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, + raw: NewAtom("foo"), + bytecode: bytecode{ + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "ensure_loaded", text: ` :- ensure_loaded('testdata/foo'). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{ - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, - raw: NewAtom("foo"), - bytecode: bytecode{ - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + multifile: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("c")}, + {opcode: opExit}, + }, }, }, }, }, - {name: NewAtom("foo"), arity: 1}: &userDefined{ - multifile: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("c")}, - {opcode: opExit}, + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 0}, + Value: &userDefined{ + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, + raw: NewAtom("foo"), + bytecode: bytecode{ + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "initialization", text: ` :- initialization(foo(c)). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - multifile: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("c")}, - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + multifile: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("c")}, + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "predicate-backed directive", text: ` :- foo(c). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - multifile: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("c")}, - {opcode: opExit}, +`, result: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + multifile: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("c")}, + {opcode: opExit}, + }, }, }, }, }, - }}, + )}, {title: "error: invalid argument", text: ` foo(?). @@ -432,32 +483,37 @@ bar(b). for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { var vm VM - vm.operators.define(1200, operatorSpecifierXFX, atomIf) - vm.operators.define(1200, operatorSpecifierXFX, atomArrow) - vm.operators.define(1200, operatorSpecifierFX, atomIf) - vm.operators.define(1000, operatorSpecifierXFY, atomComma) - vm.operators.define(400, operatorSpecifierYFX, atomSlash) - vm.procedures = map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - multifile: true, - clauses: clauses{ - { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, - raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, - bytecode: bytecode{ - {opcode: opGetConst, operand: NewAtom("c")}, - {opcode: opExit}, + varCounter = 1 // Global var cause issues in testing environment that call in randomly order for checking equality between procedure clause args + + vm.getOperators().define(1200, operatorSpecifierXFX, atomIf) + vm.getOperators().define(1200, operatorSpecifierXFX, atomArrow) + vm.getOperators().define(1200, operatorSpecifierFX, atomIf) + vm.getOperators().define(1000, operatorSpecifierXFY, atomComma) + vm.getOperators().define(400, operatorSpecifierYFX, atomSlash) + vm.procedures = buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: &userDefined{ + multifile: true, + clauses: clauses{ + { + pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, + bytecode: bytecode{ + {opcode: opGetConst, operand: NewAtom("c")}, + {opcode: opExit}, + }, }, }, }, }, - } + ) vm.FS = testdata vm.Register1(NewAtom("throw"), Throw) assert.Equal(t, tt.err, vm.Compile(context.Background(), tt.text, tt.args...)) if tt.err == nil { - delete(vm.procedures, procedureIndicator{name: NewAtom("throw"), arity: 1}) - assert.Equal(t, tt.result, vm.procedures) + vm.procedures.Delete(procedureIndicator{name: NewAtom("throw"), arity: 1}) + assert.EqualValues(t, tt.result, vm.procedures) } }) } diff --git a/engine/vm.go b/engine/vm.go index 90ec0ff..c77ad61 100644 --- a/engine/vm.go +++ b/engine/vm.go @@ -3,6 +3,7 @@ package engine import ( "context" "fmt" + orderedmap "github.com/wk8/go-ordered-map/v2" "io" "io/fs" "strings" @@ -51,7 +52,7 @@ type VM struct { // Unknown is a callback that is triggered when the VM reaches to an unknown predicate while current_prolog_flag(unknown, warning). Unknown func(name Atom, args []Term, env *Env) - procedures map[procedureIndicator]procedure + procedures *orderedmap.OrderedMap[procedureIndicator, procedure] unknown unknownAction // FS is a file system that is referenced when the VM loads Prolog texts e.g. ensure_loaded/1. @@ -60,7 +61,7 @@ type VM struct { loaded map[string]struct{} // Internal/external expression - operators operators + _operators *operators charConversions map[rune]rune charConvEnabled bool doubleQuotes doubleQuotes @@ -75,74 +76,47 @@ type VM struct { // Register0 registers a predicate of arity 0. func (vm *VM) Register0(name Atom, p Predicate0) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 0}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 0}, p) } // Register1 registers a predicate of arity 1. func (vm *VM) Register1(name Atom, p Predicate1) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 1}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 1}, p) } // Register2 registers a predicate of arity 2. func (vm *VM) Register2(name Atom, p Predicate2) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 2}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 2}, p) } // Register3 registers a predicate of arity 3. func (vm *VM) Register3(name Atom, p Predicate3) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 3}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 3}, p) } // Register4 registers a predicate of arity 4. func (vm *VM) Register4(name Atom, p Predicate4) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 4}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 4}, p) } // Register5 registers a predicate of arity 5. func (vm *VM) Register5(name Atom, p Predicate5) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 5}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 5}, p) } // Register6 registers a predicate of arity 6. func (vm *VM) Register6(name Atom, p Predicate6) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 6}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 6}, p) } // Register7 registers a predicate of arity 7. func (vm *VM) Register7(name Atom, p Predicate7) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 7}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 7}, p) } // Register8 registers a predicate of arity 8. func (vm *VM) Register8(name Atom, p Predicate8) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} - } - vm.procedures[procedureIndicator{name: name, arity: 8}] = p + vm.setProcedure(procedureIndicator{name: name, arity: 8}, p) } type unknownAction int @@ -177,7 +151,7 @@ func (vm *VM) Arrive(name Atom, args []Term, k Cont, env *Env) (promise *Promise } pi := procedureIndicator{name: name, arity: Integer(len(args))} - p, ok := vm.procedures[pi] + p, ok := vm.getProcedure(pi) if !ok { switch vm.unknown { case unknownWarning: @@ -315,6 +289,27 @@ func (vm *VM) ResetEnv() { } } +func (vm *VM) getProcedure(p procedureIndicator) (procedure, bool) { + if vm.procedures == nil { + return nil, false + } + return vm.procedures.Get(p) +} + +func (vm *VM) setProcedure(key procedureIndicator, val procedure) (procedure, bool) { + if vm.procedures == nil { + vm.procedures = orderedmap.New[procedureIndicator, procedure]() + } + return vm.procedures.Set(key, val) +} + +func (vm *VM) getOperators() *operators { + if vm._operators == nil { + vm._operators = newOperators() + } + return vm._operators +} + // Predicate0 is a predicate of arity 0. type Predicate0 func(*VM, Cont, *Env) *Promise diff --git a/engine/vm_test.go b/engine/vm_test.go index 9e1381e..e8d3548 100644 --- a/engine/vm_test.go +++ b/engine/vm_test.go @@ -13,7 +13,7 @@ func TestVM_Register0(t *testing.T) { vm.Register0(NewAtom("foo"), func(_ *VM, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 0}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 0}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{}, Success, nil).Force(context.Background()) @@ -35,7 +35,7 @@ func TestVM_Register1(t *testing.T) { vm.Register1(NewAtom("foo"), func(_ *VM, a Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 1}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a")}, Success, nil).Force(context.Background()) @@ -55,7 +55,7 @@ func TestVM_Register2(t *testing.T) { vm.Register2(NewAtom("foo"), func(_ *VM, a, b Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 2}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 2}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b")}, Success, nil).Force(context.Background()) @@ -75,7 +75,7 @@ func TestVM_Register3(t *testing.T) { vm.Register3(NewAtom("foo"), func(_ *VM, a, b, c Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 3}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 3}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c")}, Success, nil).Force(context.Background()) @@ -95,7 +95,7 @@ func TestVM_Register4(t *testing.T) { vm.Register4(NewAtom("foo"), func(_ *VM, a, b, c, d Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 4}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 4}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d")}, Success, nil).Force(context.Background()) @@ -115,7 +115,7 @@ func TestVM_Register5(t *testing.T) { vm.Register5(NewAtom("foo"), func(_ *VM, a, b, c, d, e Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 5}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 5}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, Success, nil).Force(context.Background()) @@ -135,7 +135,7 @@ func TestVM_Register6(t *testing.T) { vm.Register6(NewAtom("foo"), func(_ *VM, a, b, c, d, e, f Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 6}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 6}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, Success, nil).Force(context.Background()) @@ -155,7 +155,7 @@ func TestVM_Register7(t *testing.T) { vm.Register7(NewAtom("foo"), func(_ *VM, a, b, c, d, e, f, g Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 7}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 7}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, Success, nil).Force(context.Background()) @@ -175,7 +175,7 @@ func TestVM_Register8(t *testing.T) { vm.Register8(NewAtom("foo"), func(_ *VM, a, b, c, d, e, f, g, h Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 8}] + p, _ := vm.procedures.Get(procedureIndicator{name: NewAtom("foo"), arity: 8}) t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, Success, nil).Force(context.Background()) @@ -193,11 +193,14 @@ func TestVM_Register8(t *testing.T) { func TestVM_Arrive(t *testing.T) { t.Run("success", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: Predicate1(func(_ *VM, t Term, k Cont, env *Env) *Promise { - return k(env) - }), - }, + procedures: buildOrderedMap( + procedurePair{ + Key: procedureIndicator{name: NewAtom("foo"), arity: 1}, + Value: Predicate1(func(_ *VM, t Term, k Cont, env *Env) *Promise { + return k(env) + }), + }, + ), } ok, err := vm.Arrive(NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) assert.NoError(t, err) diff --git a/go.mod b/go.mod index 0363c33..4bab642 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,21 @@ go 1.19 require ( github.com/cockroachdb/apd/v3 v3.2.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.8.1 + github.com/wk8/go-ordered-map/v2 v2.1.8 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 ) require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/lib/pq v1.10.9 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.1.1 // indirect + github.com/stretchr/objx v0.5.0 // indirect golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 745c3aa..1692e71 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,13 @@ +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -10,13 +15,20 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -29,5 +41,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=