diff --git a/bootstrap.pl b/bootstrap.pl deleted file mode 100644 index a089cea8..00000000 --- a/bootstrap.pl +++ /dev/null @@ -1,259 +0,0 @@ -/* - * bootstrap script - */ - -% Operators - -:-(op(1200, xfx, [:-, -->])). -:-(op(1200, fx, [:-, ?-])). -:-(op(1105, xfy, '|')). -:-(op(1100, xfy, ;)). -:-(op(1050, xfy, ->)). -:-(op(1000, xfy, ',')). -:-(op(900, fy, \+)). -:-(op(700, xfx, [=, \=])). -:-(op(700, xfx, [==, \==, @<, @=<, @>, @>=])). -:-(op(700, xfx, =..)). -:-(op(700, xfx, [is, =:=, =\=, <, =<, >, >=])). -:-(op(600, xfy, :)). -:-(op(500, yfx, [+, -, /\, \/])). -:-(op(400, yfx, [*, /, //, div, rem, mod, <<, >>])). -:-(op(200, xfx, **)). -:-(op(200, xfy, ^)). -:-(op(200, fy, [+, -, \])). - -% Control constructs - -true. - -fail :- \+true. - -! :- !. - -P, Q :- call((P, Q)). - -If -> Then; _ :- If, !, Then. -_ -> _; Else :- !, Else. - -P; Q :- call((P; Q)). - -If -> Then :- If, !, Then. - -% Term unification - -X \= Y :- \+(X = Y). - -% Type testing - -atomic(X) :- - nonvar(X), - \+compound(X). - -nonvar(X) :- \+var(X). - -number(X) :- float(X). -number(X) :- integer(X). - -callable(X) :- atom(X). -callable(X) :- compound(X). - -ground(X) :- term_variables(X, []). - -% Term comparison - -X @=< Y :- compare(=, X, Y). -X @=< Y :- compare(<, X, Y). - -X == Y :- compare(=, X, Y). - -X \== Y :- \+(X == Y). - -X @< Y :- compare(<, X, Y). - -X @> Y :- compare(>, X, Y). - -X @>= Y :- compare(>, X, Y). -X @>= Y :- compare(=, X, Y). - -% Clause creation and destruction - -retractall(Head) :- - retract((Head :- _)), - fail. -retractall(_). - -% Stream selection and control - -open(Filename, Mode, Stream) :- - open(Filename, Mode, Stream, []). - -close(Stream) :- close(Stream, []). - -flush_output :- - current_output(S), - flush_output(S). - -at_end_of_stream :- - current_input(S), - at_end_of_stream(S). - -at_end_of_stream(Stream) :- - stream_property(Stream, end_of_stream(E)), !, - (E = at; E = past). - -% Character input/output - -get_char(Char) :- - current_input(S), - get_char(S, Char). - -get_code(Code) :- - current_input(S), - get_code(S, Code). - -get_code(Stream, Code) :- - get_char(Stream, Char), - (Char = end_of_file -> Code = -1; char_code(Char, Code)). - -peek_char(Char) :- - current_input(S), - peek_char(S, Char). - -peek_code(Code) :- - current_input(S), - peek_code(S, Code). - -peek_code(Stream, Code) :- - peek_char(Stream, Char), - (Char = end_of_file -> Code = -1; char_code(Char, Code)). - -put_char(Char) :- - current_output(S), - put_char(S, Char). - -put_code(Code) :- - current_output(S), - put_code(S, Code). - -put_code(S, Code) :- - char_code(Char, Code), - put_char(S, Char). - -nl :- - current_output(S), - nl(S). - -nl(S) :- - put_char(S, '\n'). - -% Byte input/output - -get_byte(Byte) :- - current_input(S), - get_byte(S, Byte). - -peek_byte(Byte) :- - current_input(S), - peek_byte(S, Byte). - -put_byte(Byte) :- - current_output(S), - put_byte(S, Byte). - -% Term input/output - -read_term(Term, Options) :- - current_input(S), - read_term(S, Term, Options). - -read(Term) :- - current_input(S), - read(S, Term). - -read(Stream, Term) :- read_term(Stream, Term, []). - -write_term(Term, Options) :- - current_output(S), - write_term(S, Term, Options). - -write(Term) :- - current_output(S), - write(S, Term). - -write(Stream, Term) :- write_term(Stream, Term, [numbervars(true)]). - -writeq(Term) :- - current_output(S), - writeq(S, Term). - -writeq(Stream, Term) :- write_term(Stream, Term, [quoted(true), numbervars(true)]). - -write_canonical(Term) :- - current_output(S), - write_canonical(S, Term). - -write_canonical(Stream, Term) :- write_term(Stream, Term, [quoted(true), ignore_ops(true)]). - -% Logic and control - -once(P) :- P, !. - -false :- fail. - -% Atomic term processing - -% Implementation defined hooks - -halt :- halt(0). - -% Consult - -[H|T] :- consult([H|T]). - -% Definite clause grammar - -phrase(GRBody, S0) :- phrase(GRBody, S0, []). - -% Prolog prologue - -member(X, [X|_]). -member(X, [_|Xs]) :- member(X, Xs). - -select(E, [E|Xs], Xs). -select(E, [X|Xs], [X|Ys]) :- - select(E, Xs, Ys). - -maplist(_Cont_1, []). -maplist(Cont_1, [E1|E1s]) :- - call(Cont_1, E1), - maplist(Cont_1, E1s). - -maplist(_Cont_2, [], []). -maplist(Cont_2, [E1|E1s], [E2|E2s]) :- - call(Cont_2, E1, E2), - maplist(Cont_2, E1s, E2s). - -maplist(_Cont_3, [], [], []). -maplist(Cont_3, [E1|E1s], [E2|E2s], [E3|E3s]) :- - call(Cont_3, E1, E2, E3), - maplist(Cont_3, E1s, E2s, E3s). - -maplist(_Cont_4, [], [], [], []). -maplist(Cont_4, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s]) :- - call(Cont_4, E1, E2, E3, E4), - maplist(Cont_4, E1s, E2s, E3s, E4s). - -maplist(_Cont_5, [], [], [], [], []). -maplist(Cont_5, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s]) :- - call(Cont_5, E1, E2, E3, E4, E5), - maplist(Cont_5, E1s, E2s, E3s, E4s, E5s). - -maplist(_Cont_6, [], [], [], [], [], []). -maplist(Cont_6, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s]) :- - call(Cont_6, E1, E2, E3, E4, E5, E6), - maplist(Cont_6, E1s, E2s, E3s, E4s, E5s, E6s). - -maplist(_Cont_7, [], [], [], [], [], [], []). -maplist(Cont_7, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s]) :- - call(Cont_7, E1, E2, E3, E4, E5, E6, E7), - maplist(Cont_7, E1s, E2s, E3s, E4s, E5s, E6s, E7s). diff --git a/cmd/1pl/interpreter.go b/cmd/1pl/interpreter.go deleted file mode 100644 index a8e4fc29..00000000 --- a/cmd/1pl/interpreter.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "fmt" - "github.com/ichiban/prolog" - "github.com/ichiban/prolog/engine" - "io" -) - -// New creates a prolog.Interpreter with some helper predicates. -func New(r io.Reader, w io.Writer) *prolog.Interpreter { - i := prolog.New(r, w) - i.Register4(engine.NewAtom("skip_max_list"), engine.SkipMaxList) - i.Register2(engine.NewAtom("go_string"), func(vm *engine.VM, term, s engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { - return engine.Unify(vm, s, engine.NewAtom(fmt.Sprintf("%#v", term)), k, env) - }) - return i -} diff --git a/cmd/1pl/interpreter_test.go b/cmd/1pl/interpreter_test.go deleted file mode 100644 index 3ac81967..00000000 --- a/cmd/1pl/interpreter_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNew(t *testing.T) { - t.Run("go_string", func(t *testing.T) { - p := New(nil, nil) - assert.NoError(t, p.QuerySolution(`go_string("foo", '"foo"').`).Err()) - }) -} diff --git a/cmd/1pl/main.go b/cmd/1pl/main.go index 4dd32240..86657213 100644 --- a/cmd/1pl/main.go +++ b/cmd/1pl/main.go @@ -68,8 +68,30 @@ Type Ctrl-C or 'halt.' to exit. log.SetOutput(t) - i := New(&userInput{t: t}, t) - i.Register1(engine.NewAtom("halt"), halt) + i := prolog.New(&userInput{t: t}, t) + if err := i.QuerySolution(`use_module(library(prologue)).`).Err(); err != nil { + log.Panic(err) + } + i.SetPredicate1("cd", func(vm *engine.VM, path engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { + var p string + switch path := env.Resolve(path).(type) { + case engine.Variable: + return engine.Error(engine.InstantiationError(env)) + case engine.Atom: + p = path.String() + default: + return engine.Error(engine.TypeError(engine.NewAtom("atom"), path, env)) + } + if err := os.Chdir(p); err != nil { + return engine.Error(err) + } + return k(env) + }) + i.SetPredicate4("skip_max_list", engine.SkipMaxList) + i.SetPredicate2("go_string", func(vm *engine.VM, term, s engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { + return engine.Unify(vm, s, engine.NewAtom(fmt.Sprintf("%#v", term)), k, env) + }) + i.SetPredicate1("halt", halt) i.Unknown = func(name engine.Atom, args []engine.Term, env *engine.Env) { var sb strings.Builder s := engine.NewOutputTextStream(&sb) @@ -77,14 +99,23 @@ Type Ctrl-C or 'halt.' to exit. log.Printf("UNKNOWN %s", &sb) } - // Consult arguments. - if err := i.QuerySolution(`findall(F, (member(X, ?), atom_chars(F, X)), Fs), consult(Fs).`, flag.Args()).Err(); err != nil { - log.Panic(err) - } - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() + for _, arg := range flag.Args() { + f, err := os.Open(arg) + if err != nil { + log.Panicf("open file error: %v", err) + } + b, err := io.ReadAll(f) + if err != nil { + log.Panicf("read file error: %v", err) + } + if err := i.LoadText(ctx, string(b)); err != nil { + log.Panicf("load text error: %v", err) + } + } + var buf strings.Builder keys := bufio.NewReader(os.Stdin) for { diff --git a/engine/atom.go b/engine/atom.go index d856ffdf..295a8772 100644 --- a/engine/atom.go +++ b/engine/atom.go @@ -28,7 +28,7 @@ var ( atomEmpty = NewAtom("") atomSlash = NewAtom("/") atomSlashSlash = NewAtom("//") - atomIf = NewAtom(":-") + atomColonMinus = NewAtom(":-") atomEmptyList = NewAtom("[]") atomEmptyBlock = NewAtom("{}") atomPlus = NewAtom("+") @@ -42,6 +42,7 @@ var ( atomComma = NewAtom(",") atomBar = NewAtom("|") atomCut = NewAtom("!") + atomColon = NewAtom(":") atomSemiColon = NewAtom(";") atomNegation = NewAtom(`\+`) atomThen = NewAtom("->") @@ -53,11 +54,13 @@ var ( atomBitwiseAnd = NewAtom(`/\`) atomBitwiseOr = NewAtom(`\/`) atomElipsis = NewAtom(`...`) + atomQuestionMinus = NewAtom("?-") atomAbs = NewAtom("abs") atomAccess = NewAtom("access") atomAcos = NewAtom("acos") atomAlias = NewAtom("alias") + atomAll = NewAtom("all") atomAppend = NewAtom("append") atomAsin = NewAtom("asin") atomAt = NewAtom("at") @@ -72,6 +75,7 @@ var ( atomCall = NewAtom("call") atomCallable = NewAtom("callable") atomCeiling = NewAtom("ceiling") + atomChanged = NewAtom("changed") atomCharConversion = NewAtom("char_conversion") atomCharacter = NewAtom("character") atomCharacterCode = NewAtom("character_code") @@ -113,8 +117,9 @@ var ( atomFloatOverflow = NewAtom("float_overflow") atomFloor = NewAtom("floor") atomForce = NewAtom("force") - atomIOMode = NewAtom("io_mode") + atomIf = NewAtom("if") atomIgnoreOps = NewAtom("ignore_ops") + atomImports = NewAtom("imports") atomInByte = NewAtom("in_byte") atomInCharacter = NewAtom("in_character") atomInCharacterCode = NewAtom("in_character_code") @@ -125,6 +130,7 @@ var ( atomIntOverflow = NewAtom("int_overflow") atomInteger = NewAtom("integer") atomIntegerRoundingFunction = NewAtom("integer_rounding_function") + atomIOMode = NewAtom("io_mode") atomList = NewAtom("list") atomLog = NewAtom("log") atomMax = NewAtom("max") @@ -132,6 +138,7 @@ var ( atomMaxDepth = NewAtom("max_depth") atomMaxInteger = NewAtom("max_integer") atomMemory = NewAtom("memory") + atomMetaArgumentSpecifier = NewAtom("meta_argument_specifier") atomMin = NewAtom("min") atomMinInteger = NewAtom("min_integer") atomMod = NewAtom("mod") @@ -145,6 +152,7 @@ var ( atomNumberVars = NewAtom("numbervars") atomOff = NewAtom("off") atomOn = NewAtom("on") + atomOp = NewAtom("op") atomOpen = NewAtom("open") atomOperator = NewAtom("operator") atomOperatorPriority = NewAtom("operator_priority") @@ -161,6 +169,7 @@ var ( atomPredicateIndicator = NewAtom("predicate_indicator") atomPrivateProcedure = NewAtom("private_procedure") atomProcedure = NewAtom("procedure") + atomProlog = NewAtom("prolog") atomPrologFlag = NewAtom("prolog_flag") atomQuoted = NewAtom("quoted") atomRead = NewAtom("read") @@ -197,6 +206,7 @@ var ( atomUndefined = NewAtom("undefined") atomUnderflow = NewAtom("underflow") atomUnknown = NewAtom("unknown") + atomUser = NewAtom("user") atomUserInput = NewAtom("user_input") atomUserOutput = NewAtom("user_output") atomVar = NewAtom("$VAR") @@ -318,7 +328,7 @@ func (a Atom) Apply(args ...Term) Term { func needQuoted(a Atom) bool { p := Parser{ - lexer: Lexer{ + Lexer: Lexer{ input: newRuneRingBuffer(strings.NewReader(a.String())), }, } diff --git a/engine/builtin.go b/engine/builtin.go index e707963e..300bb25d 100644 --- a/engine/builtin.go +++ b/engine/builtin.go @@ -20,8 +20,8 @@ func Repeat(_ *VM, k Cont, env *Env) *Promise { }) } -// Negate calls goal and returns false if it succeeds. Otherwise, invokes the continuation. -func Negate(vm *VM, goal Term, k Cont, env *Env) *Promise { +// Not calls goal and returns false if it succeeds. Otherwise, invokes the continuation. +func Not(vm *VM, goal Term, k Cont, env *Env) *Promise { return Delay(func(ctx context.Context) *Promise { ok, err := Call(vm, goal, Success, env).Force(ctx) if err != nil { @@ -49,13 +49,14 @@ func Call(vm *VM, goal Term, k Cont, env *Env) (promise *Promise) { for i, fv := range fvs { args[i] = fv } - cs, err := compile(atomIf.Apply(tuple(args...), g), env) - if err != nil { - return Error(err) - } + return Delay(func(ctx context.Context) *Promise { + cs, err := vm.compileTerm(ctx, atomColonMinus.Apply(tuple(args...), g), env) + if err != nil { + return Error(err) + } - u := userDefined{clauses: cs} - return u.call(vm, args, k, env) + return cs.call(vm, args, k, env) + }) } } @@ -95,6 +96,7 @@ func Call7(vm *VM, closure, arg1, arg2, arg3, arg4, arg5, arg6, arg7 Term, k Con } func callN(vm *VM, closure Term, additional []Term, k Cont, env *Env) *Promise { + module, closure := qmut(vm.typeIn, closure, env) pi, arg, err := piArg(closure, env) if err != nil { return Error(err) @@ -108,7 +110,7 @@ func callN(vm *VM, closure Term, additional []Term, k Cont, env *Env) *Promise { args[i] = arg(i) } args = append(args, additional...) - return Call(vm, pi.name.Apply(args...), k, env) + return Call(vm, atomColon.Apply(module, pi.name.Apply(args...)), k, env) } // CallNth succeeds iff goal succeeds and nth unifies with the number of re-execution. @@ -553,33 +555,35 @@ func Op(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise { } } + m := vm.TypeInModule() + for _, name := range names { - if p := validateOp(vm, p, spec, name, env); p != nil { + if p := validateOp(m, p, spec, name, env); p != nil { return p } } for _, name := range names { - if class := spec.class(); vm.operators.definedInClass(name, spec.class()) { - vm.operators.remove(name, class) + if class := spec.class(); m.operators.definedInClass(name, spec.class()) { + m.operators.remove(name, class) } - vm.operators.define(p, spec, name) + m.operators.define(p, spec, name) } return k(env) } -func validateOp(vm *VM, p Integer, spec operatorSpecifier, name Atom, env *Env) *Promise { +func validateOp(m *module, p Integer, spec operatorSpecifier, name Atom, env *Env) *Promise { switch name { case atomComma: - if vm.operators.definedInClass(name, operatorClassInfix) { + if m.operators.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 m.operators.definedInClass(name, operatorClassInfix) { op = operationModify } return Error(permissionError(op, permissionTypeOperator, name, env)) @@ -591,11 +595,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 m.operators.definedInClass(name, operatorClassPostfix) { return Error(permissionError(operationCreate, permissionTypeOperator, name, env)) } case operatorClassPostfix: - if vm.operators.definedInClass(name, operatorClassInfix) { + if m.operators.definedInClass(name, operatorClassInfix) { return Error(permissionError(operationCreate, permissionTypeOperator, name, env)) } } @@ -651,9 +655,10 @@ func CurrentOp(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise return Error(typeError(validTypeAtom, op, env)) } + m := vm.TypeInModule() pattern := tuple(priority, specifier, op) - ks := make([]func(context.Context) *Promise, 0, len(vm.operators)*int(_operatorClassLen)) - for _, ops := range vm.operators { + ks := make([]func(context.Context) *Promise, 0, len(m.operators)*int(_operatorClassLen)) + for _, ops := range m.operators { for _, op := range ops { op := op if op == (operator{}) { @@ -669,7 +674,7 @@ func CurrentOp(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise // Assertz appends t to the database. func Assertz(vm *VM, t Term, k Cont, env *Env) *Promise { - if err := assertMerge(vm, t, func(existing, new []clause) []clause { + if err := assertMerge(vm, t, func(existing, new []clause) clauses { return append(existing, new...) }, env); err != nil { return Error(err) @@ -679,7 +684,7 @@ func Assertz(vm *VM, t Term, k Cont, env *Env) *Promise { // Asserta prepends t to the database. func Asserta(vm *VM, t Term, k Cont, env *Env) *Promise { - if err := assertMerge(vm, t, func(existing, new []clause) []clause { + if err := assertMerge(vm, t, func(existing, new []clause) clauses { return append(new, existing...) }, env); err != nil { return Error(err) @@ -687,39 +692,42 @@ func Asserta(vm *VM, t Term, k Cont, env *Env) *Promise { return k(env) } -func assertMerge(vm *VM, t Term, merge func([]clause, []clause) []clause, env *Env) error { +func assertMerge(vm *VM, t Term, merge func([]clause, []clause) clauses, env *Env) error { pi, arg, err := piArg(t, env) if err != nil { return err } - if pi == (procedureIndicator{name: atomIf, arity: 2}) { + if pi == (predicateIndicator{name: atomColonMinus, arity: 2}) { pi, _, err = piArg(arg(0), env) if err != nil { return err } } - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + m := vm.TypeInModule() + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} } - p, ok := vm.procedures[pi] + e, ok := m.procedures[pi] if !ok { - p = &userDefined{dynamic: true} - vm.procedures[pi] = p + e.dynamic = true + e.procedure = clauses{} + m.procedures[pi] = e } - added, err := compile(t, env) + added, err := vm.compileTerm(context.Background(), t, env) if err != nil { return err } - u, ok := p.(*userDefined) - if !ok || !u.dynamic { + cs, ok := e.procedure.(clauses) + if !ok || !e.dynamic { return permissionError(operationModify, permissionTypeStaticProcedure, pi.Term(), env) } - u.clauses = merge(u.clauses, added) + e.procedure = merge(cs, added) + m.procedures[pi] = e return nil } @@ -1066,10 +1074,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) { - case *userDefined: + m := vm.TypeInModule() + ks := make([]func(context.Context) *Promise, 0, len(m.procedures)) + for key, e := range m.procedures { + switch e.procedure.(type) { + case clauses: c := key.Term() ks = append(ks, func(context.Context) *Promise { return Unify(vm, pi, c, k, env) @@ -1083,6 +1092,7 @@ func CurrentPredicate(vm *VM, pi Term, k Cont, env *Env) *Promise { // Retract removes the first clause that matches with t. func Retract(vm *VM, t Term, k Cont, env *Env) *Promise { + m := vm.TypeInModule() t = rulify(t, env) h := t.(Compound).Arg(0) @@ -1091,25 +1101,27 @@ func Retract(vm *VM, t Term, k Cont, env *Env) *Promise { return Error(err) } - p, ok := vm.procedures[pi] + e, ok := m.procedures[pi] if !ok { return Bool(false) } - u, ok := p.(*userDefined) - if !ok || !u.dynamic { + cs, ok := e.procedure.(clauses) + if !ok || !e.dynamic { return Error(permissionError(operationModify, permissionTypeStaticProcedure, pi.Term(), env)) } deleted := 0 - ks := make([]func(context.Context) *Promise, len(u.clauses)) - for i, c := range u.clauses { + ks := make([]func(context.Context) *Promise, len(cs)) + for i, c := range cs { i := i raw := rulify(c.raw, env) ks[i] = func(_ context.Context) *Promise { return Unify(vm, t, raw, func(env *Env) *Promise { j := i - deleted - u.clauses, u.clauses[len(u.clauses)-1] = append(u.clauses[:j], u.clauses[j+1:]...), clause{} + cs, cs[len(cs)-1] = append(cs[:j], cs[j+1:]...), clause{} + e.procedure = cs + m.procedures[pi] = e deleted++ return k(env) }, env) @@ -1141,11 +1153,12 @@ func Abolish(vm *VM, pi Term, k Cont, env *Env) *Promise { if arity < 0 { return Error(domainError(validDomainNotLessThanZero, arity, env)) } - key := procedureIndicator{name: name, arity: arity} - if u, ok := vm.procedures[key].(*userDefined); !ok || !u.dynamic { + m := vm.TypeInModule() + key := predicateIndicator{name: name, arity: arity} + if e, ok := m.procedures[key]; !ok || !e.dynamic { return Error(permissionError(operationModify, permissionTypeStaticProcedure, key.Term(), env)) } - delete(vm.procedures, key) + delete(m.procedures, key) return k(env) default: return Error(typeError(validTypeInteger, arity, env)) @@ -1460,8 +1473,9 @@ func WriteTerm(vm *VM, streamOrAlias, t, options Term, k Cont, env *Env) *Promis return Error(err) } + m := vm.TypeInModule() opts := WriteOptions{ - ops: vm.operators, + ops: m.operators, priority: 1200, } iter := ListIterator{List: options, Env: env} @@ -1732,22 +1746,22 @@ func ReadTerm(vm *VM, streamOrAlias, out, options Term, k Cont, env *Env) *Promi return Error(err) } - p := NewParser(vm, s) + p := NewParser(vm.TypeInModule, s) defer func() { _ = s.UnreadRune() }() t, err := p.Term() - switch err { - case nil: + switch { + case err == nil: break - case io.EOF: + case errors.Is(err, io.EOF): return Unify(vm, out, atomEndOfFile, k, env) - case errWrongIOMode: + case errors.Is(err, errWrongIOMode): return Error(permissionError(operationInput, permissionTypeStream, streamOrAlias, env)) - case errWrongStreamType: + case errors.Is(err, errWrongStreamType): return Error(permissionError(operationInput, permissionTypeBinaryStream, streamOrAlias, env)) - case errPastEndOfStream: + case errors.Is(err, errPastEndOfStream): return Error(permissionError(operationInput, permissionTypePastEndOfStream, streamOrAlias, env)) default: return Error(syntaxError(err, env)) @@ -1982,25 +1996,26 @@ func Clause(vm *VM, head, body Term, k Cont, env *Env) *Promise { return Error(typeError(validTypeCallable, body, env)) } - p, ok := vm.procedures[pi] + m := vm.TypeInModule() + e, ok := m.procedures[pi] if !ok { return Bool(false) } - u, ok := p.(*userDefined) - if !ok || !u.public { + cs, ok := e.procedure.(clauses) + if !ok || !e.public { return Error(permissionError(operationAccess, permissionTypePrivateProcedure, pi.Term(), env)) } - ks := make([]func(context.Context) *Promise, len(u.clauses)) - for i, c := range u.clauses { + ks := make([]func(context.Context) *Promise, len(cs)) + for i, c := range cs { cp, err := renamedCopy(c.raw, nil, env) if err != nil { return Error(err) } r := rulify(cp, env) ks[i] = func(context.Context) *Promise { - return Unify(vm, atomIf.Apply(head, body), r, k, env) + return Unify(vm, atomColonMinus.Apply(head, body), r, k, env) } } return Delay(ks...) @@ -2008,10 +2023,10 @@ func Clause(vm *VM, head, body Term, k Cont, env *Env) *Promise { func rulify(t Term, env *Env) Term { t = env.Resolve(t) - if c, ok := t.(Compound); ok && c.Functor() == atomIf && c.Arity() == 2 { + if c, ok := t.(Compound); ok && c.Functor() == atomColonMinus && c.Arity() == 2 { return t } - return atomIf.Apply(t, atomTrue) + return atomColonMinus.Apply(t, atomTrue) } // AtomLength counts the runes in atom and unifies the result with length. @@ -2282,7 +2297,7 @@ func NumberChars(vm *VM, num, chars Term, k Cont, env *Env) *Promise { } p := Parser{ - lexer: Lexer{ + Lexer: Lexer{ input: newRuneRingBuffer(strings.NewReader(sb.String())), }, } @@ -2364,7 +2379,7 @@ func NumberCodes(vm *VM, num, codes Term, k Cont, env *Env) *Promise { } p := Parser{ - lexer: Lexer{ + Lexer: Lexer{ input: newRuneRingBuffer(strings.NewReader(sb.String())), }, } @@ -2536,14 +2551,15 @@ func CharConversion(vm *VM, inChar, outChar Term, k Cont, env *Env) *Promise { return Error(representationError(flagCharacter, env)) } - if vm.charConversions == nil { - vm.charConversions = map[rune]rune{} + m := vm.TypeInModule() + if m.charConversions == nil { + m.charConversions = map[rune]rune{} } if i[0] == o[0] { - delete(vm.charConversions, i[0]) + delete(m.charConversions, i[0]) return k(env) } - vm.charConversions[i[0]] = o[0] + m.charConversions[i[0]] = o[0] return k(env) default: return Error(representationError(flagCharacter, env)) @@ -2579,9 +2595,10 @@ func CurrentCharConversion(vm *VM, inChar, outChar Term, k Cont, env *Env) *Prom return Error(representationError(flagCharacter, env)) } + m := vm.TypeInModule() if c1, ok := env.Resolve(inChar).(Atom); ok { r := []rune(c1.String()) - if r, ok := vm.charConversions[r[0]]; ok { + if r, ok := m.charConversions[r[0]]; ok { return Unify(vm, outChar, Atom(r), k, env) } return Unify(vm, outChar, c1, k, env) @@ -2591,7 +2608,7 @@ func CurrentCharConversion(vm *VM, inChar, outChar Term, k Cont, env *Env) *Prom ks := make([]func(context.Context) *Promise, 256) for i := 0; i < 256; i++ { r := rune(i) - cr, ok := vm.charConversions[r] + cr, ok := m.charConversions[r] if !ok { cr = r } @@ -2609,7 +2626,8 @@ func SetPrologFlag(vm *VM, flag, value Term, k Cont, env *Env) *Promise { case Variable: return Error(InstantiationError(env)) case Atom: - var modify func(vm *VM, value Atom) error + m := vm.TypeInModule() + var modify func(m *module, value Atom) error switch f { case atomBounded, atomMaxInteger, atomMinInteger, atomIntegerRoundingFunction, atomMaxArity: return Error(permissionError(operationModify, permissionTypeFlag, f, env)) @@ -2629,7 +2647,7 @@ func SetPrologFlag(vm *VM, flag, value Term, k Cont, env *Env) *Promise { case Variable: return Error(InstantiationError(env)) case Atom: - if err := modify(vm, v); err != nil { + if err := modify(m, v); err != nil { return Error(err) } return k(env) @@ -2641,52 +2659,52 @@ func SetPrologFlag(vm *VM, flag, value Term, k Cont, env *Env) *Promise { } } -func modifyCharConversion(vm *VM, value Atom) error { +func modifyCharConversion(m *module, value Atom) error { switch value { case atomOn: - vm.charConvEnabled = true + m.charConvEnabled = true case atomOff: - vm.charConvEnabled = false + m.charConvEnabled = false default: return domainError(validDomainFlagValue, atomPlus.Apply(atomCharConversion, value), nil) } return nil } -func modifyDebug(vm *VM, value Atom) error { +func modifyDebug(m *module, value Atom) error { switch value { case atomOn: - vm.debug = true + m.debug = true case atomOff: - vm.debug = false + m.debug = false default: return domainError(validDomainFlagValue, atomPlus.Apply(atomDebug, value), nil) } return nil } -func modifyUnknown(vm *VM, value Atom) error { +func modifyUnknown(m *module, value Atom) error { switch value { case atomError: - vm.unknown = unknownError + m.unknown = unknownError case atomWarning: - vm.unknown = unknownWarning + m.unknown = unknownWarning case atomFail: - vm.unknown = unknownFail + m.unknown = unknownFail default: return domainError(validDomainFlagValue, atomPlus.Apply(atomUnknown, value), nil) } return nil } -func modifyDoubleQuotes(vm *VM, value Atom) error { +func modifyDoubleQuotes(m *module, value Atom) error { switch value { case atomCodes: - vm.doubleQuotes = doubleQuotesCodes + m.doubleQuotes = doubleQuotesCodes case atomChars: - vm.doubleQuotes = doubleQuotesChars + m.doubleQuotes = doubleQuotesChars case atomAtom: - vm.doubleQuotes = doubleQuotesAtom + m.doubleQuotes = doubleQuotesAtom default: return domainError(validDomainFlagValue, atomPlus.Apply(atomDoubleQuotes, value), nil) } @@ -2709,17 +2727,18 @@ func CurrentPrologFlag(vm *VM, flag, value Term, k Cont, env *Env) *Promise { return Error(typeError(validTypeAtom, f, env)) } + m := vm.TypeInModule() pattern := tuple(flag, value) flags := []Term{ tuple(atomBounded, atomTrue), tuple(atomMaxInteger, maxInt), tuple(atomMinInteger, minInt), tuple(atomIntegerRoundingFunction, atomTowardZero), - tuple(atomCharConversion, onOff(vm.charConvEnabled)), - tuple(atomDebug, onOff(vm.debug)), + tuple(atomCharConversion, onOff(m.charConvEnabled)), + tuple(atomDebug, onOff(m.debug)), tuple(atomMaxArity, atomUnbounded), - tuple(atomUnknown, NewAtom(vm.unknown.String())), - tuple(atomDoubleQuotes, NewAtom(vm.doubleQuotes.String())), + tuple(atomUnknown, NewAtom(m.unknown.String())), + tuple(atomDoubleQuotes, NewAtom(m.doubleQuotes.String())), } ks := make([]func(context.Context) *Promise, len(flags)) for i := range flags { @@ -2749,7 +2768,8 @@ 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 { + m := vm.TypeInModule() + if _, ok := m.procedures[predicateIndicator{name: atomTermExpansion, arity: 2}]; ok { var ret Term v := NewVariable() ok, err := Call(vm, atomTermExpansion.Apply(term, v), func(env *Env) *Promise { @@ -2817,9 +2837,9 @@ func nth(vm *VM, base Integer, n, list, elem Term, k Cont, env *Env) *Promise { // Succ succeeds if s is the successor of non-negative integer x. func Succ(vm *VM, x, s Term, k Cont, env *Env) *Promise { - switch x := x.(type) { + switch x := env.Resolve(x).(type) { case Variable: - switch s := s.(type) { + switch s := env.Resolve(s).(type) { case Variable: return Error(InstantiationError(env)) case Integer: @@ -2848,7 +2868,7 @@ func Succ(vm *VM, x, s Term, k Cont, env *Env) *Promise { return Error(err) } - switch s := s.(type) { + switch s := env.Resolve(s).(type) { case Variable: return Unify(vm, s, r, k, env) case Integer: @@ -3006,3 +3026,213 @@ func appendLists(vm *VM, xs, ys, zs Term, k Cont, env *Env) *Promise { }, env) }) } + +func Dynamic(vm *VM, pi Term, k Cont, env *Env) *Promise { + m := vm.TypeInModule() + iter := anyIterator{Any: pi, Env: env} + for iter.Next() { + pi, err := mustBePI(iter.Current(), env) + if err != nil { + return Error(err) + } + e, _ := m.procedures[pi] + e.dynamic = true + e.public = true + if e.procedure == nil { + e.procedure = clauses{} + } + m.procedures[pi] = e + } + if err := iter.Err(); err != nil { + return Error(err) + } + return k(env) +} + +func Multifile(vm *VM, pi Term, k Cont, env *Env) *Promise { + m := vm.TypeInModule() + iter := anyIterator{Any: pi, Env: env} + for iter.Next() { + pi, err := mustBePI(iter.Current(), env) + if err != nil { + return Error(err) + } + e, _ := m.procedures[pi] + e.multifile = true + m.procedures[pi] = e + } + if err := iter.Err(); err != nil { + return Error(err) + } + return k(env) +} + +func Discontiguous(vm *VM, pi Term, k Cont, env *Env) *Promise { + m := vm.TypeInModule() + iter := anyIterator{Any: pi, Env: env} + for iter.Next() { + pi, err := mustBePI(iter.Current(), env) + if err != nil { + return Error(err) + } + e, _ := m.procedures[pi] + e.discontiguous = true + m.procedures[pi] = e + } + if err := iter.Err(); err != nil { + return Error(err) + } + return k(env) +} + +func Initialization(vm *VM, goal Term, k Cont, env *Env) *Promise { + m := vm.TypeInModule() + m.initGoals = append(m.initGoals, goal) + return k(env) +} + +func Include(vm *VM, file Term, k Cont, env *Env) *Promise { + f, err := mustBeAtom(file, env) + if err != nil { + return Error(err) + } + return Delay(func(ctx context.Context) *Promise { + if err := vm.LoadFile(ctx, f.String()); err != nil { + return Error(err) + } + return k(env) + }) +} + +// LoadFile loads a Prolog text from a file. +func LoadFile(vm *VM, path, options Term, k Cont, env *Env) *Promise { + filename, err := mustBeAtom(path, env) + if err != nil { + return Error(err) + } + fn := filename.String() + + var ( + condition LoadFileCondition + importList []ImportSpec + ) + iter := ListIterator{List: options, Env: env} + for iter.Next() { + opt := iter.Current() + + if _, ok := env.Unify(opt, atomIf.Apply(atomTrue)); ok { + condition = LoadFileConditionTrue + break + } + + if _, ok := env.Unify(opt, atomIf.Apply(atomChanged)); ok { + condition = LoadFileConditionChanged + break + } + + if _, ok := env.Unify(opt, atomImports.Apply(atomAll)); ok { + importList = nil + break + } + + imports := NewVariable() + if env, ok := env.Unify(opt, atomImports.Apply(imports)); ok { + importList = []ImportSpec{} + iter := ListIterator{List: imports, Env: env} + for iter.Next() { + i := iter.Current() + + pi, err := mustBePI(i, env) + if err != nil { + return Error(err) + } + importList = append(importList, ImportSpec{ + Name: pi.name.String(), + Arity: int(pi.arity), + }) + } + if err := iter.Err(); err != nil { + return Error(err) + } + } + } + + switch err := vm.LoadFile(context.Background(), fn, + LoadFileOptionIf(condition), + LoadFileOptionImports(importList), + ); { + case err == nil: + break + case errors.Is(err, fs.ErrInvalid): + fallthrough + case errors.Is(err, fs.ErrNotExist): + return Error(existenceError(objectTypeSourceSink, path, env)) + default: + return Error(err) + } + + return k(env) +} + +func DefineModule(vm *VM, moduleName, exportList Term, k Cont, env *Env) *Promise { + var name Atom + switch n := env.Resolve(moduleName).(type) { + case Variable: + return Error(InstantiationError(env)) + case Atom: + name = n + default: + return Error(typeError(validTypeAtom, moduleName, env)) + } + + var pis []predicateIndicator + iter := ListIterator{List: exportList, Env: env} + for iter.Next() { + pi, err := mustBePI(iter.Current(), env) + if err != nil { + return Error(err) + } + pis = append(pis, pi) + } + if err := iter.Err(); err != nil { + return Error(err) + } + + m := vm.module(name) + m.reset() + + vm.importPredicates(name, vm.system, nil) + vm.SetModule(name) + + return k(env) +} + +func MetaPredicate(vm *VM, mi Term, k Cont, env *Env) *Promise { + m := vm.TypeInModule() + iter := anyIterator{Any: mi, Env: env} + for iter.Next() { + pi, arg, err := piArg(mi, env) + if err != nil { + return Error(err) + } + e, _ := m.procedures[pi] + e.metaPredicate = make([]metaArgumentSpecifier, pi.arity) + for i := 0; i < int(pi.arity); i++ { + switch t := env.Resolve(arg(i)).(type) { + case Variable: + return Error(InstantiationError(env)) + case Atom: + e.metaPredicate[i] = metaArgumentSpecifier{atom: t} + case Integer: + e.metaPredicate[i] = metaArgumentSpecifier{integer: t} + default: + return Error(domainError(validDomainMetaArgumentSpecifier, t, env)) + } + } + m.procedures[pi] = e + } + if err := iter.Err(); err != nil { + return Error(err) + } + return k(env) +} diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 28deaad3..478859e2 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -19,22 +19,24 @@ import ( func TestCall(t *testing.T) { var vm VM - vm.Register0(atomFail, func(_ *VM, f Cont, env *Env) *Promise { + m := vm.TypeInModule() + m.Register0("fail", func(_ *VM, f Cont, env *Env) *Promise { return Bool(false) }) - vm.Register0(NewAtom("do_not_call"), func(*VM, Cont, *Env) *Promise { + m.Register0("do_not_call", func(*VM, Cont, *Env) *Promise { panic("told you") }) - vm.Register0(NewAtom("lazy_do_not_call"), func(*VM, Cont, *Env) *Promise { + m.Register0("lazy_do_not_call", func(*VM, Cont, *Env) *Promise { return Delay(func(context.Context) *Promise { panic("told you") }) }) - assert.NoError(t, vm.Compile(context.Background(), ` + _, err := vm.Compile(context.Background(), ` foo. foo(_, _). f(g([a, [b, c|X]])). -`)) +`) + assert.NoError(t, err) tests := []struct { title string @@ -66,8 +68,8 @@ f(g([a, [b, c|X]])). defer setMemFree(tt.mem)() ok, err := Call(&vm, tt.goal, Success, nil).Force(context.Background()) - assert.Equal(t, tt.ok, ok) assert.Equal(t, tt.err, err) + assert.Equal(t, tt.ok, ok) }) } } @@ -91,11 +93,18 @@ 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 { - return k(env) - }), - }} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("p"), arity: 2}: {procedure: 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 +131,18 @@ 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 { - return k(env) - }), - }} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("p"), arity: 3}: {procedure: 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 +169,18 @@ 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 { - return k(env) - }), - }} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("p"), arity: 4}: {procedure: 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 +207,18 @@ 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 { - return k(env) - }), - }} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("p"), arity: 5}: {procedure: 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 +245,18 @@ 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 { - return k(env) - }), - }} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("p"), arity: 6}: {procedure: 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 +283,18 @@ 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 { - return k(env) - }), - }} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("p"), arity: 7}: {procedure: 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 +321,18 @@ 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 { - return k(env) - }), - }} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("p"), arity: 8}: {procedure: 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,16 +342,21 @@ 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")) - }) - }), + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 0}: {procedure: 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")) + }) + })}, + }, + }, }, } @@ -939,9 +995,18 @@ 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(`+`)) + var ops operators + ops.define(900, operatorSpecifierXFX, NewAtom(`+++`)) + ops.define(1100, operatorSpecifierXFX, NewAtom(`+`)) + + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } ok, err := Op(&vm, Integer(1000), atomXFX, NewAtom("++"), Success, nil).Force(context.Background()) assert.NoError(t, err) @@ -969,24 +1034,29 @@ func TestOp(t *testing.T) { name: atomPlus, }, }, - }, vm.operators) + }, vm.TypeInModule().operators) }) 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, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: operators{ + NewAtom(`+++`): { + operatorClassInfix: { + priority: 900, + specifier: operatorSpecifierXFX, + name: NewAtom("+++"), + }, + }, + NewAtom(`+`): { + operatorClassInfix: { + priority: 1100, + specifier: operatorSpecifierXFX, + name: atomPlus, + }, + }, }, }, }, @@ -1017,32 +1087,37 @@ func TestOp(t *testing.T) { name: atomPlus, }, }, - }, vm.operators) + }, vm.TypeInModule().operators) }) }) 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, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + 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, + }, + }, }, }, }, @@ -1066,7 +1141,7 @@ func TestOp(t *testing.T) { name: atomPlus, }, }, - }, vm.operators) + }, vm.TypeInModule().operators) }) t.Run("priority is a variable", func(t *testing.T) { @@ -1135,16 +1210,32 @@ func TestOp(t *testing.T) { }) t.Run("operator is ','", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(1000, operatorSpecifierXFY, NewAtom(`,`)) + var ops operators + ops.define(1000, operatorSpecifierXFY, NewAtom(`,`)) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } 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(`,`)) + var ops operators + ops.define(1000, operatorSpecifierXFY, NewAtom(`,`)) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } 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) @@ -1167,15 +1258,31 @@ func TestOp(t *testing.T) { t.Run("bar", func(t *testing.T) { t.Run("create", func(t *testing.T) { - var vm VM + var ops operators + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } ok, err := Op(&vm, Integer(1000), atomXFY, atomBar, Success, nil).Force(context.Background()) assert.Equal(t, permissionError(operationCreate, permissionTypeOperator, atomBar, nil), err) assert.False(t, ok) }) t.Run("modify", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(1001, operatorSpecifierXFY, NewAtom(`|`)) + var ops operators + ops.define(1001, operatorSpecifierXFY, NewAtom(`|`)) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } 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) @@ -1200,15 +1307,31 @@ func TestOp(t *testing.T) { t.Run("bar", func(t *testing.T) { t.Run("create", func(t *testing.T) { - var vm VM + var ops operators + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } ok, err := Op(&vm, Integer(1000), atomXFY, List(atomBar), Success, nil).Force(context.Background()) assert.Equal(t, permissionError(operationCreate, permissionTypeOperator, atomBar, nil), err) assert.False(t, ok) }) t.Run("modify", func(t *testing.T) { - vm := VM{operators: operators{}} - vm.operators.define(101, operatorSpecifierXFY, NewAtom(`|`)) + var ops operators + ops.define(101, operatorSpecifierXFY, NewAtom(`|`)) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } 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 +1341,32 @@ 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(`+`)) + var ops operators + ops.define(200, operatorSpecifierYF, NewAtom(`+`)) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } 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(`+`)) + var ops operators + ops.define(500, operatorSpecifierYFX, NewAtom(`+`)) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } 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 +1375,18 @@ 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(`+`)) + var ops operators + ops.define(900, operatorSpecifierXFX, NewAtom(`+++`)) + ops.define(1000, operatorSpecifierXFX, NewAtom(`++`)) + ops.define(1100, operatorSpecifierXFX, NewAtom(`+`)) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } t.Run("single solution", func(t *testing.T) { ok, err := CurrentOp(&vm, Integer(1100), atomXFX, atomPlus, Success, nil).Force(context.Background()) @@ -1510,29 +1657,29 @@ func TestBagOf(t *testing.T) { }, } - vm := VM{ + m := module{ unknown: unknownWarning, } - vm.Register2(atomEqual, Unify) - vm.Register2(atomComma, func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { + m.Register2("=", Unify) + m.Register2(",", func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { return Call(vm, g1, func(env *Env) *Promise { return Call(vm, g2, k, env) }, env) }) - vm.Register2(atomSemiColon, func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { + m.Register2(";", func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { return Delay(func(context.Context) *Promise { return Call(vm, g1, k, env) }, func(context.Context) *Promise { return Call(vm, g2, k, env) }) }) - vm.Register0(atomTrue, func(_ *VM, k Cont, env *Env) *Promise { + m.Register0("true", func(_ *VM, k Cont, env *Env) *Promise { return k(env) }) - vm.Register0(atomFail, func(*VM, Cont, *Env) *Promise { + m.Register0("fail", func(*VM, Cont, *Env) *Promise { return Bool(false) }) - vm.Register2(NewAtom("a"), func(vm *VM, x, y Term, k Cont, env *Env) *Promise { + m.Register2("a", func(vm *VM, x, y Term, k Cont, env *Env) *Promise { a, f := NewAtom("$a"), NewAtom("f") return Delay(func(context.Context) *Promise { return Unify(vm, a.Apply(x, y), a.Apply(Integer(1), f.Apply(NewVariable())), k, env) @@ -1540,7 +1687,7 @@ func TestBagOf(t *testing.T) { return Unify(vm, a.Apply(x, y), a.Apply(Integer(2), f.Apply(NewVariable())), k, env) }) }) - vm.Register2(NewAtom("b"), func(vm *VM, x, y Term, k Cont, env *Env) *Promise { + m.Register2("b", func(vm *VM, x, y Term, k Cont, env *Env) *Promise { b := NewAtom("$b") return Delay(func(context.Context) *Promise { return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(1)), k, env) @@ -1556,6 +1703,12 @@ func TestBagOf(t *testing.T) { return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) }) }) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: &m, + }, + } for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { @@ -1909,29 +2062,29 @@ func TestSetOf(t *testing.T) { }, } - vm := VM{ + m := module{ unknown: unknownWarning, } - vm.Register2(atomEqual, Unify) - vm.Register2(atomComma, func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { + m.Register2("=", Unify) + m.Register2(",", func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { return Call(vm, g1, func(env *Env) *Promise { return Call(vm, g2, k, env) }, env) }) - vm.Register2(atomSemiColon, func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { + m.Register2(";", func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { return Delay(func(context.Context) *Promise { return Call(vm, g1, k, env) }, func(context.Context) *Promise { return Call(vm, g2, k, env) }) }) - vm.Register0(atomTrue, func(_ *VM, k Cont, env *Env) *Promise { + m.Register0("true", func(_ *VM, k Cont, env *Env) *Promise { return k(env) }) - vm.Register0(atomFail, func(*VM, Cont, *Env) *Promise { + m.Register0("fail", func(*VM, Cont, *Env) *Promise { return Bool(false) }) - vm.Register2(NewAtom("a"), func(vm *VM, x, y Term, k Cont, env *Env) *Promise { + m.Register2("a", func(vm *VM, x, y Term, k Cont, env *Env) *Promise { a, f := NewAtom("$a"), NewAtom("f") return Delay(func(context.Context) *Promise { return Unify(vm, a.Apply(x, y), a.Apply(Integer(1), f.Apply(NewVariable())), k, env) @@ -1939,7 +2092,7 @@ func TestSetOf(t *testing.T) { return Unify(vm, a.Apply(x, y), a.Apply(Integer(2), f.Apply(NewVariable())), k, env) }) }) - vm.Register2(NewAtom("b"), func(vm *VM, x, y Term, k Cont, env *Env) *Promise { + m.Register2("b", func(vm *VM, x, y Term, k Cont, env *Env) *Promise { b := NewAtom("$b") return Delay(func(context.Context) *Promise { return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(1)), k, env) @@ -1955,7 +2108,7 @@ func TestSetOf(t *testing.T) { return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) }) }) - vm.Register2(NewAtom("d"), func(vm *VM, x, y Term, k Cont, env *Env) *Promise { + m.Register2("d", func(vm *VM, x, y Term, k Cont, env *Env) *Promise { d := NewAtom("$d") return Delay(func(context.Context) *Promise { return Unify(vm, d.Apply(x, y), d.Apply(Integer(1), Integer(1)), k, env) @@ -1971,7 +2124,7 @@ func TestSetOf(t *testing.T) { return Unify(vm, d.Apply(x, y), d.Apply(Integer(2), Integer(2)), k, env) }) }) - vm.Register2(NewAtom("member"), func(vm *VM, elem, list Term, k Cont, env *Env) *Promise { + m.Register2("member", func(vm *VM, elem, list Term, k Cont, env *Env) *Promise { var ks []func(context.Context) *Promise iter := ListIterator{List: list, Env: env, AllowPartial: true} for iter.Next() { @@ -1985,8 +2138,14 @@ func TestSetOf(t *testing.T) { } return Delay(ks...) }) - vm.Register3(NewAtom("setof"), SetOf) - vm.Register3(NewAtom("bagof"), BagOf) + m.Register3("setof", SetOf) + m.Register3("bagof", BagOf) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: &m, + }, + } for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { @@ -2053,18 +2212,24 @@ func TestFindAll(t *testing.T) { }, } - var vm VM - vm.Register2(atomEqual, Unify) - vm.Register2(atomSemiColon, func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { + var m module + m.Register2("=", Unify) + m.Register2(";", func(vm *VM, g1, g2 Term, k Cont, env *Env) *Promise { return Delay(func(context.Context) *Promise { return Call(vm, g1, k, env) }, func(context.Context) *Promise { return Call(vm, g2, k, env) }) }) - vm.Register0(atomFail, func(*VM, Cont, *Env) *Promise { + m.Register0("fail", func(*VM, Cont, *Env) *Promise { return Bool(false) }) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: &m, + }, + } for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { @@ -2386,15 +2551,21 @@ func TestThrow(t *testing.T) { } func TestCatch(t *testing.T) { - var vm VM - vm.Register2(atomEqual, Unify) - vm.Register1(NewAtom("throw"), Throw) - vm.Register0(atomTrue, func(_ *VM, k Cont, env *Env) *Promise { + var m module + m.Register2("=", Unify) + m.Register1("throw", Throw) + m.Register0("true", func(_ *VM, k Cont, env *Env) *Promise { return k(env) }) - vm.Register0(atomFail, func(*VM, Cont, *Env) *Promise { + m.Register0("fail", func(*VM, Cont, *Env) *Promise { return Bool(false) }) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: &m, + }, + } t.Run("match", func(t *testing.T) { v := NewVariable() @@ -2443,9 +2614,16 @@ 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{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 1}: {procedure: clauses{}}, + }, + }, + }, + } ok, err := CurrentPredicate(&vm, &compound{ functor: atomSlash, args: []Term{ @@ -2462,11 +2640,18 @@ 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{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 1}: {procedure: clauses{}}, + {name: NewAtom("bar"), arity: 1}: {procedure: clauses{}}, + {name: NewAtom("baz"), arity: 1}: {procedure: clauses{}}, + }, + }, + }, + } ok, err := CurrentPredicate(&vm, v, func(env *Env) *Promise { c, ok := env.Resolve(v).(*compound) assert.True(t, ok) @@ -2494,9 +2679,16 @@ func TestCurrentPredicate(t *testing.T) { }) t.Run("builtin predicate", func(t *testing.T) { - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: atomEqual, arity: 2}: Predicate2(Unify), - }} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: atomEqual, arity: 2}: {procedure: Predicate2(Unify)}, + }, + }, + }, + } ok, err := CurrentPredicate(&vm, &compound{ functor: atomSlash, args: []Term{ @@ -2577,9 +2769,9 @@ func TestAssertz(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, &userDefined{dynamic: true, clauses: []clause{ + assert.Equal(t, procedureEntry{dynamic: true, procedure: clauses{ { - pi: procedureIndicator{ + pi: predicateIndicator{ name: NewAtom("foo"), arity: 1, }, @@ -2593,7 +2785,7 @@ func TestAssertz(t *testing.T) { }, }, { - pi: procedureIndicator{ + pi: predicateIndicator{ name: NewAtom("foo"), arity: 1, }, @@ -2606,7 +2798,7 @@ func TestAssertz(t *testing.T) { {opcode: opExit}, }, }, - }}, vm.procedures[procedureIndicator{ + }}, vm.TypeInModule().procedures[predicateIndicator{ name: NewAtom("foo"), arity: 1, }]) @@ -2629,7 +2821,7 @@ func TestAssertz(t *testing.T) { t.Run("head is a variable", func(t *testing.T) { var vm VM ok, err := Assertz(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{NewVariable(), atomTrue}, }, Success, nil).Force(context.Background()) assert.Equal(t, InstantiationError(nil), err) @@ -2639,7 +2831,7 @@ func TestAssertz(t *testing.T) { t.Run("head is neither a variable, nor callable", func(t *testing.T) { var vm VM ok, err := Assertz(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{Integer(0), atomTrue}, }, Success, nil).Force(context.Background()) assert.Equal(t, typeError(validTypeCallable, Integer(0), nil), err) @@ -2649,7 +2841,7 @@ func TestAssertz(t *testing.T) { t.Run("body contains a term which is not callable", func(t *testing.T) { var vm VM ok, err := Assertz(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{ NewAtom("foo"), &compound{ @@ -2673,8 +2865,13 @@ 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}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 0}: {dynamic: false}, + }, + }, }, } @@ -2707,9 +2904,9 @@ func TestAsserta(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, &userDefined{dynamic: true, clauses: []clause{ + assert.Equal(t, procedureEntry{dynamic: true, procedure: clauses{ { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + pi: predicateIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{ functor: NewAtom("foo"), args: []Term{NewAtom("b")}, @@ -2720,7 +2917,7 @@ func TestAsserta(t *testing.T) { }, }, { - pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, + pi: predicateIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{ functor: NewAtom("foo"), args: []Term{NewAtom("a")}, @@ -2730,13 +2927,14 @@ func TestAsserta(t *testing.T) { {opcode: opExit}, }, }, - }}, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}]) + }}, vm.TypeInModule().procedures[predicateIndicator{name: NewAtom("foo"), arity: 1}]) }) t.Run("rule", func(t *testing.T) { var vm VM + vm.typeIn = atomUser ok, err := Asserta(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{ NewAtom("foo"), &compound{ @@ -2748,7 +2946,7 @@ func TestAsserta(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - ok, err = Asserta(&vm, atomIf.Apply( + ok, err = Asserta(&vm, atomColonMinus.Apply( NewAtom("foo"), atomComma.Apply( NewAtom("p").Apply(NewAtom("a")), @@ -2758,11 +2956,11 @@ func TestAsserta(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, &userDefined{dynamic: true, clauses: []clause{ + assert.Equal(t, procedureEntry{dynamic: true, procedure: clauses{ { - pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, + pi: predicateIndicator{name: NewAtom("foo"), arity: 0}, raw: &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{ NewAtom("foo"), &compound{ @@ -2780,15 +2978,18 @@ func TestAsserta(t *testing.T) { bytecode: bytecode{ {opcode: opEnter}, {opcode: opPutConst, operand: NewAtom("a")}, - {opcode: opCall, operand: procedureIndicator{name: NewAtom("p"), arity: 1}}, + {opcode: opCall, operand: qualifiedPredicateIndicator{ + module: atomUser, + predicateIndicator: predicateIndicator{name: NewAtom("p"), arity: 1}, + }}, {opcode: opCut}, {opcode: opExit}, }, }, { - pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, + pi: predicateIndicator{name: NewAtom("foo"), arity: 0}, raw: &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{ NewAtom("foo"), &compound{ @@ -2800,11 +3001,14 @@ func TestAsserta(t *testing.T) { bytecode: bytecode{ {opcode: opEnter}, {opcode: opPutConst, operand: NewAtom("b")}, - {opcode: opCall, operand: procedureIndicator{name: NewAtom("p"), arity: 1}}, + {opcode: opCall, operand: qualifiedPredicateIndicator{ + module: atomUser, + predicateIndicator: predicateIndicator{name: NewAtom("p"), arity: 1}, + }}, {opcode: opExit}, }, }, - }}, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 0}]) + }}, vm.TypeInModule().procedures[predicateIndicator{name: NewAtom("foo"), arity: 0}]) }) t.Run("clause is a variable", func(t *testing.T) { @@ -2824,7 +3028,7 @@ func TestAsserta(t *testing.T) { t.Run("head is a variable", func(t *testing.T) { var vm VM ok, err := Asserta(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{NewVariable(), atomTrue}, }, Success, nil).Force(context.Background()) assert.Equal(t, InstantiationError(nil), err) @@ -2834,7 +3038,7 @@ func TestAsserta(t *testing.T) { t.Run("head is neither a variable, nor callable", func(t *testing.T) { var vm VM ok, err := Asserta(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{Integer(0), atomTrue}, }, Success, nil).Force(context.Background()) assert.Equal(t, typeError(validTypeCallable, Integer(0), nil), err) @@ -2844,7 +3048,7 @@ func TestAsserta(t *testing.T) { t.Run("body is not callable", func(t *testing.T) { var vm VM ok, err := Asserta(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{NewAtom("foo"), Integer(0)}, }, Success, nil).Force(context.Background()) assert.Equal(t, typeError(validTypeCallable, Integer(0), nil), err) @@ -2854,7 +3058,7 @@ func TestAsserta(t *testing.T) { t.Run("body contains a term which is not callable", func(t *testing.T) { var vm VM ok, err := Asserta(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{ NewAtom("foo"), &compound{ @@ -2876,8 +3080,13 @@ 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}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 0}: {dynamic: false}, + }, + }, }, } @@ -2895,7 +3104,7 @@ func TestAsserta(t *testing.T) { t.Run("cut", func(t *testing.T) { var vm VM ok, err := Asserta(&vm, &compound{ - functor: atomIf, + functor: atomColonMinus, args: []Term{ NewAtom("foo"), atomCut, @@ -2909,12 +3118,17 @@ 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")}}}, - }}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 1}: {dynamic: true, procedure: clauses{ + {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")}}}, + }}, + }, + }, }, } @@ -2925,20 +3139,25 @@ func TestRetract(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, &userDefined{dynamic: true, clauses: []clause{ + assert.Equal(t, procedureEntry{dynamic: true, procedure: clauses{ {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.TypeInModule().procedures[predicateIndicator{name: NewAtom("foo"), arity: 1}]) }) 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")}}}, - }}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 1}: {dynamic: true, procedure: clauses{ + {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")}}}, + }}, + }, + }, }, } @@ -2949,20 +3168,25 @@ func TestRetract(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, &userDefined{dynamic: true, clauses: []clause{ + assert.Equal(t, procedureEntry{dynamic: true, procedure: clauses{ {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.TypeInModule().procedures[predicateIndicator{name: NewAtom("foo"), arity: 1}]) }) 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")}}}, - }}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 1}: {dynamic: true, procedure: clauses{ + {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")}}}, + }}, + }, + }, }, } @@ -2972,7 +3196,7 @@ 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) + assert.Empty(t, vm.TypeInModule().procedures[predicateIndicator{name: NewAtom("foo"), arity: 1}].procedure.(clauses)) }) t.Run("variable", func(t *testing.T) { @@ -3002,8 +3226,13 @@ 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}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 0}: {dynamic: false}, + }, + }, }, } @@ -3017,10 +3246,15 @@ 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")}}}, - }}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 1}: procedureEntry{dynamic: true, procedure: clauses{ + {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, + }}, + }, + }, }, } @@ -3034,19 +3268,24 @@ func TestRetract(t *testing.T) { assert.False(t, ok) // removed - assert.Empty(t, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}].(*userDefined).clauses) + assert.Empty(t, vm.TypeInModule().procedures[predicateIndicator{name: NewAtom("foo"), arity: 1}].procedure.(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")}}}, - }}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 1}: procedureEntry{dynamic: true, procedure: clauses{ + {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")}}}, + }}, + }, + }, }, } @@ -3057,7 +3296,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.TypeInModule().procedures[predicateIndicator{name: NewAtom("foo"), arity: 1}] assert.False(t, ok) }) @@ -3138,8 +3377,13 @@ 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}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 0}: procedureEntry{dynamic: false}, + }, + }, }, } ok, err := Abolish(&vm, &compound{ @@ -4057,10 +4301,18 @@ func TestWriteTerm(t *testing.T) { {title: `L = [a, b|L], write_term(S, L, [max_depth(9)]).`, sOrA: w, term: l, options: List(atomMaxDepth.Apply(Integer(9))), env: NewEnv().bind(l, PartialList(l, NewAtom("a"), NewAtom("b"))), ok: true, output: `[a,b,a,b,a,b,a,b,a|...]`}, // https://github.com/ichiban/prolog/issues/297#issuecomment-1646750461 } - var vm VM - vm.operators.define(500, operatorSpecifierYFX, atomPlus) - vm.operators.define(200, operatorSpecifierFY, atomPlus) - vm.operators.define(200, operatorSpecifierYF, atomMinus) + var ops operators + ops.define(500, operatorSpecifierYFX, atomPlus) + ops.define(200, operatorSpecifierFY, atomPlus) + ops.define(200, operatorSpecifierYF, atomMinus) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + operators: ops, + }, + }, + } for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { buf.Reset() @@ -4699,7 +4951,7 @@ func TestReadTerm(t *testing.T) { var vm VM ok, err := ReadTerm(&vm, s, NewVariable(), List(), Success, nil).Force(context.Background()) - assert.Equal(t, syntaxError(unexpectedTokenError{actual: Token{kind: tokenGraphic, val: "="}}, nil), err) + assert.Equal(t, syntaxError(unexpectedTokenError{actual: Token{kind: tokenGraphic, val: "***"}}, nil), err) assert.False(t, ok) }) } @@ -5400,16 +5652,21 @@ 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}}, - }, - }}, - {raw: &compound{functor: NewAtom("green"), args: []Term{NewAtom("kermit")}}}, - }}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("green"), arity: 1}: procedureEntry{public: true, procedure: clauses{ + {raw: &compound{ + functor: atomColonMinus, 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")}}}, + }}, + }, + }, }, } ok, err := Clause(&vm, &compound{ @@ -5460,10 +5717,15 @@ 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) - }), + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("green"), arity: 1}: {procedure: Predicate1(func(_ *VM, t Term, f Cont, env *Env) *Promise { + return Bool(true) + })}, + }, + }, }, } ok, err := Clause(&vm, &compound{ @@ -5488,10 +5750,15 @@ 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())}, - }}, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("green"), arity: 1}: {public: true, procedure: clauses{ + {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()) @@ -6263,20 +6530,25 @@ func TestCharConversion(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, 'b', vm.charConversions['a']) + assert.Equal(t, 'b', vm.TypeInModule().charConversions['a']) }) t.Run("remove", func(t *testing.T) { vm := VM{ - charConversions: map[rune]rune{ - 'a': 'b', + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + charConversions: map[rune]rune{ + 'a': 'b', + }, + }, }, } ok, err := CharConversion(&vm, NewAtom("a"), NewAtom("a"), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - _, ok = vm.charConversions['a'] + _, ok = vm.TypeInModule().charConversions['a'] assert.False(t, ok) }) @@ -6338,8 +6610,13 @@ func TestCurrentCharConversion(t *testing.T) { t.Run("converted", func(t *testing.T) { vm := VM{ - charConversions: map[rune]rune{ - 'a': 'b', + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + charConversions: map[rune]rune{ + 'a': 'b', + }, + }, }, } ok, err := CurrentCharConversion(&vm, NewAtom("a"), NewAtom("b"), Success, nil).Force(context.Background()) @@ -6444,19 +6721,33 @@ func TestSetPrologFlag(t *testing.T) { ok, err := SetPrologFlag(&vm, atomCharConversion, atomOn, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.True(t, vm.charConvEnabled) + assert.True(t, vm.TypeInModule().charConvEnabled) }) t.Run("off", func(t *testing.T) { - vm := VM{charConvEnabled: true} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + charConvEnabled: true, + }, + }, + } ok, err := SetPrologFlag(&vm, atomCharConversion, atomOff, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.False(t, vm.charConvEnabled) + assert.False(t, vm.TypeInModule().charConvEnabled) }) t.Run("unknown", func(t *testing.T) { - vm := VM{charConvEnabled: true} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + charConvEnabled: true, + }, + }, + } ok, err := SetPrologFlag(&vm, atomCharConversion, NewAtom("foo"), Success, nil).Force(context.Background()) assert.Error(t, err) assert.False(t, ok) @@ -6469,19 +6760,33 @@ func TestSetPrologFlag(t *testing.T) { ok, err := SetPrologFlag(&vm, atomDebug, atomOn, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.True(t, vm.debug) + assert.True(t, vm.TypeInModule().debug) }) t.Run("off", func(t *testing.T) { - vm := VM{debug: true} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + debug: true, + }, + }, + } ok, err := SetPrologFlag(&vm, atomDebug, atomOff, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.False(t, vm.debug) + assert.False(t, vm.TypeInModule().debug) }) t.Run("unknown", func(t *testing.T) { - vm := VM{debug: true} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + debug: true, + }, + }, + } ok, err := SetPrologFlag(&vm, atomDebug, NewAtom("foo"), Success, nil).Force(context.Background()) assert.Error(t, err) assert.False(t, ok) @@ -6497,11 +6802,18 @@ func TestSetPrologFlag(t *testing.T) { t.Run("unknown", func(t *testing.T) { t.Run("error", func(t *testing.T) { - vm := VM{unknown: unknownFail} + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + unknown: unknownFail, + }, + }, + } ok, err := SetPrologFlag(&vm, atomUnknown, atomError, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, unknownError, vm.unknown) + assert.Equal(t, unknownError, vm.TypeInModule().unknown) }) t.Run("warning", func(t *testing.T) { @@ -6509,7 +6821,7 @@ func TestSetPrologFlag(t *testing.T) { ok, err := SetPrologFlag(&vm, atomUnknown, atomWarning, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, unknownWarning, vm.unknown) + assert.Equal(t, unknownWarning, vm.TypeInModule().unknown) }) t.Run("fail", func(t *testing.T) { @@ -6517,7 +6829,7 @@ func TestSetPrologFlag(t *testing.T) { ok, err := SetPrologFlag(&vm, atomUnknown, atomFail, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, unknownFail, vm.unknown) + assert.Equal(t, unknownFail, vm.TypeInModule().unknown) }) t.Run("fail", func(t *testing.T) { @@ -6534,7 +6846,7 @@ func TestSetPrologFlag(t *testing.T) { ok, err := SetPrologFlag(&vm, atomDoubleQuotes, atomCodes, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, doubleQuotesCodes, vm.doubleQuotes) + assert.Equal(t, doubleQuotesCodes, vm.TypeInModule().doubleQuotes) }) t.Run("chars", func(t *testing.T) { @@ -6542,7 +6854,7 @@ func TestSetPrologFlag(t *testing.T) { ok, err := SetPrologFlag(&vm, atomDoubleQuotes, atomChars, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, doubleQuotesChars, vm.doubleQuotes) + assert.Equal(t, doubleQuotesChars, vm.TypeInModule().doubleQuotes) }) t.Run("atom", func(t *testing.T) { @@ -6550,7 +6862,7 @@ func TestSetPrologFlag(t *testing.T) { ok, err := SetPrologFlag(&vm, atomDoubleQuotes, atomAtom, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, doubleQuotesAtom, vm.doubleQuotes) + assert.Equal(t, doubleQuotesAtom, vm.TypeInModule().doubleQuotes) }) t.Run("unknown", func(t *testing.T) { @@ -6672,10 +6984,10 @@ func TestCurrentPrologFlag(t *testing.T) { assert.Equal(t, atomUnbounded, env.Resolve(value)) case 7: assert.Equal(t, atomUnknown, env.Resolve(flag)) - assert.Equal(t, NewAtom(vm.unknown.String()), env.Resolve(value)) + assert.Equal(t, NewAtom(vm.TypeInModule().unknown.String()), env.Resolve(value)) case 8: assert.Equal(t, atomDoubleQuotes, env.Resolve(flag)) - assert.Equal(t, NewAtom(vm.doubleQuotes.String()), env.Resolve(value)) + assert.Equal(t, NewAtom(vm.TypeInModule().doubleQuotes.String()), env.Resolve(value)) default: assert.Fail(t, "unreachable") } @@ -6710,9 +7022,10 @@ func TestExpandTerm(t *testing.T) { x := NewVariable() var vm VM - assert.NoError(t, vm.Compile(context.Background(), ` + _, err := vm.Compile(context.Background(), ` term_expansion(f(X), g(X)). -`)) +`) + assert.NoError(t, err) tests := []struct { title string @@ -6729,7 +7042,7 @@ term_expansion(f(X), g(X)). title: "terminal sequence: empty", in: atomArrow.Apply(s.Apply(a), List()), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(a, lastVariable()+1, lastVariable()+3), atomEqual.Apply(lastVariable()+1, lastVariable()+3), ) @@ -6740,7 +7053,7 @@ term_expansion(f(X), g(X)). title: "terminal sequence: ok", in: atomArrow.Apply(s.Apply(a), List(b)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(a, lastVariable()+1, lastVariable()+3), atomEqual.Apply(lastVariable()+1, PartialList(lastVariable()+3, b)), ) @@ -6757,7 +7070,7 @@ term_expansion(f(X), g(X)). title: "terminal sequence: variable in body", in: atomArrow.Apply(s.Apply(a), x), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(a, lastVariable()+1, lastVariable()+3), atomPhrase.Apply(x, lastVariable()+1, PartialList(lastVariable()+3, b)), ) @@ -6768,7 +7081,7 @@ term_expansion(f(X), g(X)). title: "concatenation: ok", in: atomArrow.Apply(s, seq(atomComma, a, b)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), seq(atomComma, a.Apply(lastVariable()+1, lastVariable()+4), @@ -6794,7 +7107,7 @@ term_expansion(f(X), g(X)). title: "alternative: ok", in: atomArrow.Apply(s, seq(atomSemiColon, a, b)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), seq(atomSemiColon, a.Apply(lastVariable()+1, lastVariable()+3), @@ -6808,7 +7121,7 @@ term_expansion(f(X), g(X)). title: "alternative: if-then-else", in: atomArrow.Apply(s, seq(atomSemiColon, atomThen.Apply(a, b), c)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), seq(atomSemiColon, atomThen.Apply( @@ -6837,7 +7150,7 @@ term_expansion(f(X), g(X)). title: "second form of alternative: ok", in: atomArrow.Apply(s, seq(atomBar, a, b)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), seq(atomSemiColon, a.Apply(lastVariable()+1, lastVariable()+3), @@ -6863,7 +7176,7 @@ term_expansion(f(X), g(X)). title: "grammar-body-goal", in: atomArrow.Apply(s, atomEmptyBlock.Apply(a)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), seq(atomComma, a, @@ -6877,7 +7190,7 @@ term_expansion(f(X), g(X)). title: "call", in: atomArrow.Apply(s, atomCall.Apply(a)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), atomCall.Apply(a, lastVariable()+1, lastVariable()+3), ) @@ -6888,7 +7201,7 @@ term_expansion(f(X), g(X)). title: "phrase", in: atomArrow.Apply(s, atomPhrase.Apply(a)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), atomPhrase.Apply(a, lastVariable()+1, lastVariable()+3), ) @@ -6899,7 +7212,7 @@ term_expansion(f(X), g(X)). title: "grammar-body-cut", in: atomArrow.Apply(s, atomCut), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), seq(atomComma, atomCut, @@ -6913,7 +7226,7 @@ term_expansion(f(X), g(X)). title: "grammar-body-not: ok", in: atomArrow.Apply(s, atomNegation.Apply(a)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), seq(atomComma, atomNegation.Apply(a.Apply(lastVariable()+1, lastVariable()+4)), @@ -6933,7 +7246,7 @@ term_expansion(f(X), g(X)). title: "if-then: ok", in: atomArrow.Apply(s, atomThen.Apply(a, b)), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( s.Apply(lastVariable()+1, lastVariable()+3), atomThen.Apply( a.Apply(lastVariable()+1, lastVariable()+4), @@ -6959,7 +7272,7 @@ term_expansion(f(X), g(X)). title: "with semicontexts: ok", in: atomArrow.Apply(atomComma.Apply(NewAtom("phrase1"), List(NewAtom("word"))), atomComma.Apply(NewAtom("phrase2"), NewAtom("phrase3"))), out: func() Term { - return atomIf.Apply( + return atomColonMinus.Apply( NewAtom("phrase1").Apply(lastVariable()+1, lastVariable()+3), atomComma.Apply( atomComma.Apply( @@ -7405,29 +7718,35 @@ func TestRepeat(t *testing.T) { assert.Equal(t, 1, c) } -func TestNegation(t *testing.T) { +func TestNot(t *testing.T) { e := errors.New("failed") - var vm VM - vm.Register0(atomTrue, func(_ *VM, k Cont, env *Env) *Promise { + var m module + m.Register0("true", func(_ *VM, k Cont, env *Env) *Promise { return k(env) }) - vm.Register0(atomFalse, func(*VM, Cont, *Env) *Promise { + m.Register0("false", func(*VM, Cont, *Env) *Promise { return Bool(false) }) - vm.Register0(atomError, func(*VM, Cont, *Env) *Promise { + m.Register0("error", func(*VM, Cont, *Env) *Promise { return Error(e) }) + vm := VM{ + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: &m, + }, + } - ok, err := Negate(&vm, atomTrue, Success, nil).Force(context.Background()) + ok, err := Not(&vm, atomTrue, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.False(t, ok) - ok, err = Negate(&vm, atomFalse, Success, nil).Force(context.Background()) + ok, err = Not(&vm, atomFalse, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) - _, err = Negate(&vm, atomError, Success, nil).Force(context.Background()) + _, err = Not(&vm, atomError, Success, nil).Force(context.Background()) assert.Equal(t, e, err) } diff --git a/engine/clause.go b/engine/compile.go similarity index 54% rename from engine/clause.go rename to engine/compile.go index 51e7e8f8..ff240a53 100644 --- a/engine/clause.go +++ b/engine/compile.go @@ -3,16 +3,110 @@ package engine import ( "context" "errors" + "fmt" + "strings" ) -type userDefined struct { - public bool - dynamic bool - multifile bool - discontiguous bool +func (vm *VM) Compile(ctx context.Context, text string) (Atom, error) { + { + m := vm.typeIn + defer vm.SetModule(m) + } + + // Skip a shebang line if exists. + if strings.HasPrefix(text, "#!") { + switch i := strings.IndexRune(text, '\n'); i { + case -1: + text = "" + default: + text = text[i+1:] + } + } + + r := strings.NewReader(text) + p := NewParser(vm.TypeInModule, r) + for p.More() { + p.Vars = p.Vars[:] + t, err := p.Term() + if err != nil { + return 0, err + } + + et, err := expand(vm, t, nil) + if err != nil { + return 0, err + } + + pi, arg, err := piArg(et, nil) + if err != nil { + return 0, err + } + switch pi { + case predicateIndicator{name: atomColonMinus, arity: 1}: // Directive + if err := vm.TypeInModule().flushClauseBuf(); err != nil { + return 0, err + } + + ok, err := Call(vm, arg(0), Success, nil).Force(ctx) + if err != nil { + return 0, err + } + if !ok { + return 0, p.unexpected() + } + + continue + case predicateIndicator{name: atomColonMinus, arity: 2}: // Rule + pi, arg, err = piArg(arg(0), nil) + if err != nil { + return 0, err + } - // 7.4.3 says "If no clauses are defined for a procedure indicated by a directive ... then the procedure shall exist but have no clauses." - clauses + fallthrough + default: + m := vm.TypeInModule() + if len(m.buf) > 0 && pi != m.buf[0].pi { + if err := m.flushClauseBuf(); err != nil { + return 0, err + } + } + + cs, err := vm.compileTerm(ctx, et, nil) + if err != nil { + return 0, err + } + + m.buf = append(m.buf, cs...) + } + } + + m := vm.TypeInModule() + if err := m.flushClauseBuf(); err != nil { + return 0, err + } + + for _, g := range m.initGoals { + ok, err := Call(vm, g, Success, nil).Force(ctx) + if err != nil { + return 0, err + } + if !ok { + var sb strings.Builder + s := NewOutputTextStream(&sb) + _, _ = WriteTerm(vm, s, g, List(atomQuoted.Apply(atomTrue)), Success, nil).Force(ctx) + return 0, fmt.Errorf("failed initialization goal: %s", sb.String()) + } + } + m.initGoals = m.initGoals[:0] + + return vm.typeIn, nil +} + +type clause struct { + pi predicateIndicator + raw Term + vars []Variable + bytecode bytecode } type clauses []clause @@ -34,14 +128,14 @@ func (cs clauses) call(vm *VM, args []Term, k Cont, env *Env) *Promise { return p } -func compile(t Term, env *Env) (clauses, error) { +func (vm *VM) compileTerm(ctx context.Context, t Term, env *Env) (clauses, error) { t = env.Resolve(t) - if t, ok := t.(Compound); ok && t.Functor() == atomIf && t.Arity() == 2 { + if t, ok := t.(Compound); ok && t.Functor() == atomColonMinus && t.Arity() == 2 { var cs clauses head, body := t.Arg(0), t.Arg(1) iter := altIterator{Alt: body, Env: env} for iter.Next() { - c, err := compileClause(head, iter.Current(), env) + c, err := vm.compileClause(ctx, head, iter.Current(), env) if err != nil { return nil, typeError(validTypeCallable, body, env) } @@ -51,23 +145,16 @@ func compile(t Term, env *Env) (clauses, error) { return cs, nil } - c, err := compileClause(t, nil, env) + c, err := vm.compileClause(ctx, t, nil, env) c.raw = env.simplify(t) return []clause{c}, err } -type clause struct { - pi procedureIndicator - raw Term - vars []Variable - bytecode bytecode -} - -func compileClause(head Term, body Term, env *Env) (clause, error) { +func (vm *VM) compileClause(ctx context.Context, head Term, body Term, env *Env) (clause, error) { var c clause - c.compileHead(head, env) + vm.compileHead(&c, head, env) if body != nil { - if err := c.compileBody(body, env); err != nil { + if err := vm.compileBody(ctx, &c, body, env); err != nil { return c, typeError(validTypeCallable, body, env) } } @@ -75,23 +162,23 @@ func compileClause(head Term, body Term, env *Env) (clause, error) { return c, nil } -func (c *clause) compileHead(head Term, env *Env) { +func (vm *VM) compileHead(c *clause, head Term, env *Env) { switch head := env.Resolve(head).(type) { case Atom: - c.pi = procedureIndicator{name: head, arity: 0} + c.pi = predicateIndicator{name: head, arity: 0} case Compound: - c.pi = procedureIndicator{name: head.Functor(), arity: Integer(head.Arity())} + c.pi = predicateIndicator{name: head.Functor(), arity: Integer(head.Arity())} for i := 0; i < head.Arity(); i++ { c.compileHeadArg(head.Arg(i), env) } } } -func (c *clause) compileBody(body Term, env *Env) error { +func (vm *VM) compileBody(ctx context.Context, c *clause, body Term, env *Env) error { c.bytecode = append(c.bytecode, instruction{opcode: opEnter}) iter := seqIterator{Seq: body, Env: env} for iter.Next() { - if err := c.compilePred(iter.Current(), env); err != nil { + if err := vm.compileGoal(ctx, c, iter.Current(), env); err != nil { return err } } @@ -100,23 +187,39 @@ func (c *clause) compileBody(body Term, env *Env) error { var errNotCallable = errors.New("not callable") -func (c *clause) compilePred(p Term, env *Env) error { - switch p := env.Resolve(p).(type) { +func (vm *VM) compileGoal(ctx context.Context, c *clause, goal Term, env *Env) error { + module, goal := qmut(vm.typeIn, goal, env) + switch g := env.Resolve(goal).(type) { case Variable: - return c.compilePred(atomCall.Apply(p), env) + return vm.compileGoal(ctx, c, atomCall.Apply(g), env) case Atom: - switch p { + switch g { case atomCut: c.bytecode = append(c.bytecode, instruction{opcode: opCut}) return nil } - c.bytecode = append(c.bytecode, instruction{opcode: opCall, operand: procedureIndicator{name: p, arity: 0}}) + c.bytecode = append(c.bytecode, instruction{opcode: opCall, operand: qualifiedPredicateIndicator{ + module: module, + predicateIndicator: predicateIndicator{name: g, arity: 0}, + }}) return nil case Compound: - for i := 0; i < p.Arity(); i++ { - c.compileBodyArg(p.Arg(i), env) + pi := predicateIndicator{name: g.Functor(), arity: Integer(g.Arity())} + var e procedureEntry + if m, ok := vm.modules[module]; ok { + e = m.procedures[pi] + } + for i := 0; i < g.Arity(); i++ { + arg := g.Arg(i) + if e.metaPredicate != nil && e.metaPredicate[i].needsModuleNameExpansion() { + arg = atomColon.Apply(vm.typeIn, arg) + } + c.compileBodyArg(arg, env) } - c.bytecode = append(c.bytecode, instruction{opcode: opCall, operand: procedureIndicator{name: p.Functor(), arity: Integer(p.Arity())}}) + c.bytecode = append(c.bytecode, instruction{opcode: opCall, operand: qualifiedPredicateIndicator{ + module: module, + predicateIndicator: pi, + }}) return nil default: return errNotCallable @@ -144,7 +247,7 @@ func (c *clause) compileHeadArg(a Term, env *Env) { } c.bytecode = append(c.bytecode, instruction{opcode: opPop}) case Compound: - c.bytecode = append(c.bytecode, instruction{opcode: opGetFunctor, operand: procedureIndicator{name: a.Functor(), arity: Integer(a.Arity())}}) + c.bytecode = append(c.bytecode, instruction{opcode: opGetFunctor, operand: predicateIndicator{name: a.Functor(), arity: Integer(a.Arity())}}) for i := 0; i < a.Arity(); i++ { c.compileHeadArg(a.Arg(i), env) } @@ -180,7 +283,7 @@ func (c *clause) compileBodyArg(a Term, env *Env) { } c.bytecode = append(c.bytecode, instruction{opcode: opPop}) case Compound: - c.bytecode = append(c.bytecode, instruction{opcode: opPutFunctor, operand: procedureIndicator{name: a.Functor(), arity: Integer(a.Arity())}}) + c.bytecode = append(c.bytecode, instruction{opcode: opPutFunctor, operand: predicateIndicator{name: a.Functor(), arity: Integer(a.Arity())}}) for i := 0; i < a.Arity(); i++ { c.compileBodyArg(a.Arg(i), env) } diff --git a/engine/compound_test.go b/engine/compound_test.go index a8c57ee5..7fea3d13 100644 --- a/engine/compound_test.go +++ b/engine/compound_test.go @@ -37,11 +37,11 @@ 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: "fx", term: atomColonMinus.Apply(atomColonMinus.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: "xfx", term: atomColonMinus.Apply(NewAtom("foo"), atomColonMinus.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`}, diff --git a/engine/dcg.go b/engine/dcg.go index 8f0cc339..259dbc30 100644 --- a/engine/dcg.go +++ b/engine/dcg.go @@ -41,7 +41,7 @@ func expandDCG(term Term, env *Env) (Term, error) { return nil, err } body := atomComma.Apply(goal1, goal2) - return atomIf.Apply(head, body), nil + return atomColonMinus.Apply(head, body), nil } head, err := dcgNonTerminal(rule.Arg(0), s0, s, env) @@ -52,7 +52,7 @@ func expandDCG(term Term, env *Env) (Term, error) { if err != nil { return nil, err } - return atomIf.Apply(head, body), nil + return atomColonMinus.Apply(head, body), nil } func dcgNonTerminal(nonTerminal, list, rest Term, env *Env) (Term, error) { @@ -80,10 +80,10 @@ func dcgTerminals(terminals, list, rest Term, env *Env) (Term, error) { return atomEqual.Apply(list, PartialList(rest, elems...)), nil } -var dcgConstr map[procedureIndicator]func(args []Term, list, rest Term, env *Env) (Term, error) +var dcgConstr map[predicateIndicator]func(args []Term, list, rest Term, env *Env) (Term, error) func init() { - dcgConstr = map[procedureIndicator]func(args []Term, list, rest Term, env *Env) (Term, error){ + dcgConstr = map[predicateIndicator]func(args []Term, list, rest Term, env *Env) (Term, error){ {name: atomEmptyList, arity: 0}: func(_ []Term, list, rest Term, _ *Env) (Term, error) { return atomEqual.Apply(list, rest), nil }, diff --git a/engine/dcg_test.go b/engine/dcg_test.go index 87b0a949..dbf20d16 100644 --- a/engine/dcg_test.go +++ b/engine/dcg_test.go @@ -11,11 +11,16 @@ 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) - }), + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("a"), arity: 2}: {procedure: Predicate2(func(_ *VM, s0, s Term, k Cont, env *Env) *Promise { + called = true + return k(env) + })}, + }, + }, }, } diff --git a/engine/exception.go b/engine/exception.go index e158a606..017c2071 100644 --- a/engine/exception.go +++ b/engine/exception.go @@ -111,27 +111,29 @@ const ( validDomainWriteOption validDomainOrder + validDomainMetaArgumentSpecifier ) var validDomainAtoms = [...]Atom{ - validDomainCharacterCodeList: atomCharacterCodeList, - validDomainCloseOption: atomCloseOption, - validDomainFlagValue: atomFlagValue, - validDomainIOMode: atomIOMode, - validDomainNonEmptyList: atomNonEmptyList, - validDomainNotLessThanZero: atomNotLessThanZero, - validDomainOperatorPriority: atomOperatorPriority, - validDomainOperatorSpecifier: atomOperatorSpecifier, - validDomainPrologFlag: atomPrologFlag, - validDomainReadOption: atomReadOption, - validDomainSourceSink: atomSourceSink, - validDomainStream: atomStream, - validDomainStreamOption: atomStreamOption, - validDomainStreamOrAlias: atomStreamOrAlias, - validDomainStreamPosition: atomStreamPosition, - validDomainStreamProperty: atomStreamProperty, - validDomainWriteOption: atomWriteOption, - validDomainOrder: atomOrder, + validDomainCharacterCodeList: atomCharacterCodeList, + validDomainCloseOption: atomCloseOption, + validDomainFlagValue: atomFlagValue, + validDomainIOMode: atomIOMode, + validDomainNonEmptyList: atomNonEmptyList, + validDomainNotLessThanZero: atomNotLessThanZero, + validDomainOperatorPriority: atomOperatorPriority, + validDomainOperatorSpecifier: atomOperatorSpecifier, + validDomainPrologFlag: atomPrologFlag, + validDomainReadOption: atomReadOption, + validDomainSourceSink: atomSourceSink, + validDomainStream: atomStream, + validDomainStreamOption: atomStreamOption, + validDomainStreamOrAlias: atomStreamOrAlias, + validDomainStreamPosition: atomStreamPosition, + validDomainStreamProperty: atomStreamProperty, + validDomainWriteOption: atomWriteOption, + validDomainOrder: atomOrder, + validDomainMetaArgumentSpecifier: atomMetaArgumentSpecifier, } // Term returns an Atom for the validDomain. @@ -333,3 +335,46 @@ func (ev exceptionalValue) Term() Term { func evaluationError(ev exceptionalValue, env *Env) Exception { return NewException(atomError.Apply(atomEvaluationError.Apply(ev.Term()), varContext), env) } + +func mustBeAtom(t Term, env *Env) (Atom, error) { + switch t := env.Resolve(t).(type) { + case Variable: + return 0, InstantiationError(env) + case Atom: + return t, nil + default: + return 0, typeError(validTypeAtom, t, env) + } +} + +func mustBePI(t Term, env *Env) (predicateIndicator, error) { + var pi predicateIndicator + switch t := t.(type) { + case Variable: + return pi, InstantiationError(env) + case Compound: + if t.Functor() != atomSlash || t.Arity() != 2 { + return pi, typeError(validTypePredicateIndicator, t, env) + } + switch n := t.Arg(0).(type) { + case Variable: + return pi, InstantiationError(env) + case Atom: + pi.name = n + default: + return pi, typeError(validTypePredicateIndicator, t, env) + } + + switch a := t.Arg(1).(type) { + case Variable: + return pi, InstantiationError(env) + case Integer: + pi.arity = a + default: + return pi, typeError(validTypePredicateIndicator, t, env) + } + default: + return pi, typeError(validTypePredicateIndicator, t, env) + } + return pi, nil +} diff --git a/engine/lexer.go b/engine/lexer.go index 3bdf705c..b4df9a91 100644 --- a/engine/lexer.go +++ b/engine/lexer.go @@ -12,8 +12,8 @@ import ( // Lexer turns runes into tokens. type Lexer struct { - input runeRingBuffer - charConversions map[rune]rune + module func() *module + input runeRingBuffer buf bytes.Buffer offset int @@ -21,6 +21,11 @@ type Lexer struct { // Token returns the next token. func (l *Lexer) Token() (Token, error) { + if l.module == nil { + l.module = func() *module { + return &module{} + } + } l.offset = l.buf.Len() return l.layoutTextSequence(false) } @@ -39,7 +44,11 @@ func (l *Lexer) rawNext() (rune, error) { } func (l *Lexer) conv(r rune) rune { - if r, ok := l.charConversions[r]; ok { + if l.module == nil { + return r + } + m := l.module() + if r, ok := m.charConversions[r]; ok { return r } return r diff --git a/engine/lexer_test.go b/engine/lexer_test.go index 814f06be..811e4960 100644 --- a/engine/lexer_test.go +++ b/engine/lexer_test.go @@ -194,7 +194,14 @@ a quoted ident"`}}, for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { - l := Lexer{input: newRuneRingBuffer(noMonkeyReader{strings.NewReader(tt.input)}), charConversions: tt.charConversions} + l := Lexer{ + module: func() *module { + return &module{ + charConversions: tt.charConversions, + } + }, + input: newRuneRingBuffer(noMonkeyReader{strings.NewReader(tt.input)}), + } token, err := l.Token() assert.Equal(t, tt.token, token) diff --git a/engine/module.go b/engine/module.go new file mode 100644 index 00000000..4a3c7472 --- /dev/null +++ b/engine/module.go @@ -0,0 +1,313 @@ +package engine + +import ( + "fmt" + "slices" +) + +type module struct { + procedures map[predicateIndicator]procedureEntry + unknown unknownAction + + // Internal/external expression + operators operators + charConversions map[rune]rune + charConvEnabled bool + doubleQuotes doubleQuotes + + // Misc + debug bool + + buf clauses + initGoals []Term +} + +func newModule() *module { + ops := operators{} + // op(1200, xfx, [:-, -->]). + ops.define(Integer(1200), operatorSpecifierXFX, NewAtom(`:-`)) + ops.define(Integer(1200), operatorSpecifierXFX, NewAtom(`-->`)) + // op(1200, fx, [:-, ?-]). + ops.define(Integer(1200), operatorSpecifierFX, NewAtom(`:-`)) + ops.define(Integer(1200), operatorSpecifierFX, NewAtom(`?-`)) + // op(1105, xfy, '|'). + ops.define(Integer(1105), operatorSpecifierXFY, NewAtom(`|`)) + // op(1100, xfy, ;). + ops.define(Integer(1100), operatorSpecifierXFY, NewAtom(`;`)) + // op(1050, xfy, ->). + ops.define(Integer(1050), operatorSpecifierXFY, NewAtom(`->`)) + // op(1000, xfy, ','). + ops.define(Integer(1000), operatorSpecifierXFY, NewAtom(`,`)) + // op(900, fy, \+). + ops.define(Integer(900), operatorSpecifierFY, NewAtom(`\+`)) + // op(700, xfx, [=, \=]). + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`=`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`\=`)) + // op(700, xfx, [==, \==, @<, @=<, @>, @>=]). + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`==`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`\==`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`@<`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`@=<`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`@>`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`@>=`)) + // op(700, xfx, =..). + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`=..`)) + // op(700, xfx, [is, =:=, =\=, <, =<, >, >=]). + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`is`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`=:=`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`=\=`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`<`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`=<`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`>`)) + ops.define(Integer(700), operatorSpecifierXFX, NewAtom(`>=`)) + // op(600, xfy, :). + ops.define(Integer(600), operatorSpecifierXFY, NewAtom(`:`)) + // op(500, yfx, [+, -, /\, \/]). + ops.define(Integer(500), operatorSpecifierYFX, NewAtom(`+`)) + ops.define(Integer(500), operatorSpecifierYFX, NewAtom(`-`)) + ops.define(Integer(500), operatorSpecifierYFX, NewAtom(`/\`)) + ops.define(Integer(500), operatorSpecifierYFX, NewAtom(`\/`)) + // op(400, yfx, [*, /, //, div, rem, mod, <<, >>]). + ops.define(Integer(400), operatorSpecifierYFX, NewAtom(`*`)) + ops.define(Integer(400), operatorSpecifierYFX, NewAtom(`/`)) + ops.define(Integer(400), operatorSpecifierYFX, NewAtom(`//`)) + ops.define(Integer(400), operatorSpecifierYFX, NewAtom(`div`)) + ops.define(Integer(400), operatorSpecifierYFX, NewAtom(`rem`)) + ops.define(Integer(400), operatorSpecifierYFX, NewAtom(`mod`)) + ops.define(Integer(400), operatorSpecifierYFX, NewAtom(`<<`)) + ops.define(Integer(400), operatorSpecifierYFX, NewAtom(`>>`)) + // op(200, xfx, **). + ops.define(Integer(200), operatorSpecifierXFX, NewAtom(`**`)) + // op(200, xfy, ^). + ops.define(Integer(200), operatorSpecifierXFY, NewAtom(`^`)) + // op(200, fy, [+, -, \]). + ops.define(Integer(200), operatorSpecifierFY, NewAtom(`+`)) + ops.define(Integer(200), operatorSpecifierFY, NewAtom(`-`)) + ops.define(Integer(200), operatorSpecifierFY, NewAtom(`\`)) + + return &module{ + operators: ops, + procedures: map[predicateIndicator]procedureEntry{}, + } +} + +func (m *module) reset() { + *m = *newModule() +} + +// Register0 registers a predicate of arity 0. +func (m *module) Register0(name string, p Predicate0) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 0} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +// Register1 registers a predicate of arity 1. +func (m *module) Register1(name string, p Predicate1) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 1} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +// Register2 registers a predicate of arity 2. +func (m *module) Register2(name string, p Predicate2) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 2} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +// Register3 registers a predicate of arity 3. +func (m *module) Register3(name string, p Predicate3) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 3} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +// Register4 registers a predicate of arity 4. +func (m *module) Register4(name string, p Predicate4) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 4} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +// Register5 registers a predicate of arity 5. +func (m *module) Register5(name string, p Predicate5) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 5} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +// Register6 registers a predicate of arity 6. +func (m *module) Register6(name string, p Predicate6) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 6} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +// Register7 registers a predicate of arity 7. +func (m *module) Register7(name string, p Predicate7) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 7} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +// Register8 registers a predicate of arity 8. +func (m *module) Register8(name string, p Predicate8) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + pi := predicateIndicator{name: NewAtom(name), arity: 8} + e := m.procedures[pi] + e.public = false + e.dynamic = false + e.procedure = p + m.procedures[pi] = e +} + +func (m *module) Import(from *module, importList []predicateIndicator) { + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + for pi, e := range from.procedures { + if !e.exported { + continue + } + + if importList != nil && !slices.Contains(importList, pi) { + continue + } + + } +} + +func (m *module) flushClauseBuf() error { + if len(m.buf) == 0 { + return nil + } + + pi := m.buf[0].pi + e, ok := m.procedures[pi] + if !ok { + e.procedure = clauses{} + if m.procedures == nil { + m.procedures = map[predicateIndicator]procedureEntry{} + } + m.procedures[pi] = e + } + + if e.procedure == nil { + e.procedure = clauses{} + } + + cs, ok := e.procedure.(clauses) + if !ok { + return permissionError(operationModify, permissionTypeStaticProcedure, pi, nil) + } + + if len(cs) > 0 && !e.discontiguous { + return &discontiguousError{pi: pi} + } + e.procedure = append(cs, m.buf...) + m.procedures[pi] = e + m.buf = m.buf[:0] + return nil +} + +type unknownAction int + +const ( + unknownError unknownAction = iota + unknownFail + unknownWarning +) + +func (u unknownAction) String() string { + return [...]string{ + unknownError: "error", + unknownFail: "fail", + unknownWarning: "warning", + }[u] +} + +type procedureEntry struct { + dynamic bool + public bool + builtIn bool + multifile bool + exported bool + metaPredicate []metaArgumentSpecifier + importedFrom Atom + definedIn Atom + discontiguous bool + procedure procedure +} + +type metaArgumentSpecifier struct { + atom Atom + integer Integer +} + +func (m *metaArgumentSpecifier) needsModuleNameExpansion() bool { + return m.atom == atomColon || m.integer > 0 +} + +type procedure interface { + call(*VM, []Term, Cont, *Env) *Promise +} + +// discontiguousError is an error that the user-defined predicate is defined by clauses which are not consecutive read-terms. +type discontiguousError struct { + pi predicateIndicator +} + +func (e *discontiguousError) Error() string { + return fmt.Sprintf("%s is discontiguous", e.pi) +} diff --git a/engine/module_test.go b/engine/module_test.go new file mode 100644 index 00000000..63407508 --- /dev/null +++ b/engine/module_test.go @@ -0,0 +1,189 @@ +package engine + +import ( + "context" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestModule_Register0(t *testing.T) { + var m module + m.Register0("foo", func(_ *VM, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 0}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + + assert.Equal(t, "wrong number of arguments: expected=0, actual=[a]", err.Error()) + }) +} + +func TestModule_Register1(t *testing.T) { + var m module + m.Register1("foo", func(_ *VM, a Term, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 1}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a")}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) +} + +func TestModule_Register2(t *testing.T) { + var m module + m.Register2("foo", func(_ *VM, a, b Term, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 2}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b")}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) +} + +func TestModule_Register3(t *testing.T) { + var m module + m.Register3("foo", func(_ *VM, a, b, c Term, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 3}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c")}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) +} + +func TestModule_Register4(t *testing.T) { + var m module + m.Register4("foo", func(_ *VM, a, b, c, d Term, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 4}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d")}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) +} + +func TestModule_Register5(t *testing.T) { + var m module + m.Register5("foo", func(_ *VM, a, b, c, d, e Term, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 5}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) +} + +func TestModule_Register6(t *testing.T) { + var m module + m.Register6("foo", func(_ *VM, a, b, c, d, e, f Term, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 6}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) +} + +func TestModule_Register7(t *testing.T) { + var m module + m.Register7("foo", func(_ *VM, a, b, c, d, e, f, g Term, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 7}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) +} + +func TestModule_Register8(t *testing.T) { + var m module + m.Register8("foo", func(_ *VM, a, b, c, d, e, f, g, h Term, k Cont, env *Env) *Promise { + return k(env) + }) + p := m.procedures[predicateIndicator{name: NewAtom("foo"), arity: 8}].procedure + + t.Run("ok", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("wrong number of arguments", func(t *testing.T) { + ok, err := p.call(nil, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h"), NewAtom("i")}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) +} diff --git a/engine/parser.go b/engine/parser.go index 9ca9ddf3..a15449de 100644 --- a/engine/parser.go +++ b/engine/parser.go @@ -12,17 +12,14 @@ import ( ) var ( - errExpectation = errors.New("expectation error") errNoOp = errors.New("no op") errNotANumber = errors.New("not a number") - errPlaceholder = errors.New("not enough arguments for placeholders") + errPlaceholder = errors.New("wrong number of arguments for placeholders") ) // Parser turns bytes into Term. type Parser struct { - lexer Lexer - operators operators - doubleQuotes doubleQuotes + Lexer Vars []ParsedVariable @@ -39,24 +36,20 @@ type ParsedVariable struct { Count int } -// 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{} - } +// NewParser creates a new parser from the moduler and io.RuneReader. +func NewParser(m func() *module, r io.RuneReader) *Parser { return &Parser{ - lexer: Lexer{ - input: newRuneRingBuffer(r), + Lexer: Lexer{ + module: m, + input: newRuneRingBuffer(r), }, - operators: vm.operators, - doubleQuotes: vm.doubleQuotes, } } // SetPlaceholder registers placeholder and its arguments. Every occurrence of placeholder will be replaced by arguments. // Mismatch of the number of occurrences of placeholder and the number of arguments raises an error. -func (p *Parser) SetPlaceholder(placeholder Atom, args ...interface{}) error { - p.placeholder = placeholder +func (p *Parser) SetPlaceholder(placeholder string, args ...interface{}) error { + p.placeholder = NewAtom(placeholder) p.args = make([]Term, len(args)) for i, a := range args { var err error @@ -75,7 +68,8 @@ func (p *Parser) termOf(o reflect.Value) (Term, error) { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return Integer(o.Int()), nil case reflect.String: - switch p.doubleQuotes { + m := p.module() + switch m.doubleQuotes { case doubleQuotesCodes: return CodeList(o.String()), nil case doubleQuotesAtom: @@ -101,7 +95,7 @@ func (p *Parser) termOf(o reflect.Value) (Term, error) { func (p *Parser) next() (Token, error) { if p.buf.empty() { - t, err := p.lexer.Token() + t, err := p.Token() if err != nil { return Token{}, err } @@ -118,19 +112,23 @@ func (p *Parser) current() Token { return p.buf.current() } +func (p *Parser) unexpected() error { + return unexpectedTokenError{actual: p.current()} +} + // Term parses a term followed by a full stop. func (p *Parser) Term() (Term, error) { t, err := p.term(1201) - switch err { - case nil: + switch { + case err == nil: break - case errExpectation: - return nil, unexpectedTokenError{actual: p.current()} + case errors.As(err, &unexpectedTokenError{}): + return nil, err default: return nil, err } - switch t, _ := p.next(); t.kind { + switch n, _ := p.next(); n.kind { case tokenEnd: break default: @@ -139,7 +137,7 @@ func (p *Parser) Term() (Term, error) { } if len(p.args) != 0 { - return nil, fmt.Errorf("too many arguments for placeholders: %s", p.args) + return nil, errPlaceholder } return t, nil @@ -194,8 +192,8 @@ func (p *Parser) number() (Number, error) { } // No more runes after a number. - switch _, err := p.lexer.rawNext(); err { - case io.EOF: + switch _, err := p.rawNext(); { + case errors.Is(err, io.EOF): return n, nil default: return nil, errNotANumber @@ -294,6 +292,12 @@ func (ops *operators) init() { *ops = map[Atom][3]operator{} } +func (ops *operators) reset() { + for name := range *ops { + (*ops)[name] = [3]operator{} + } +} + func (ops *operators) remove(name Atom, class operatorClass) { os := (*ops)[name] os[class] = operator{} @@ -419,7 +423,8 @@ func (p *Parser) prefix(maxPriority Integer) (operator, error) { p.backup() } - if op := p.operators[a][operatorClassPrefix]; op != (operator{}) && op.priority <= maxPriority { + m := p.module() + if op := m.operators[a][operatorClassPrefix]; op != (operator{}) && op.priority <= maxPriority { return op, nil } @@ -433,13 +438,14 @@ func (p *Parser) infix(maxPriority Integer) (operator, error) { return operator{}, errNoOp } - if op := p.operators[a][operatorClassInfix]; op != (operator{}) { + m := p.module() + if op := m.operators[a][operatorClassInfix]; op != (operator{}) { l, _ := op.bindingPriorities() if l <= maxPriority { return op, nil } } - if op := p.operators[a][operatorClassPostfix]; op != (operator{}) { + if op := m.operators[a][operatorClassPostfix]; op != (operator{}) { l, _ := op.bindingPriorities() if l <= maxPriority { return op, nil @@ -484,7 +490,7 @@ func (p *Parser) op(maxPriority Integer) (Atom, error) { } p.backup() - return 0, errExpectation + return 0, p.unexpected() } func (p *Parser) term0(maxPriority Integer) (Term, error) { @@ -518,7 +524,8 @@ func (p *Parser) term0(maxPriority Integer) (Term, error) { p.backup() return p.curlyBracketedTerm() case tokenDoubleQuotedList: - switch p.doubleQuotes { + m := p.module() + switch m.doubleQuotes { case doubleQuotesChars: return CharList(unDoubleQuote(t.val)), nil case doubleQuotesCodes: @@ -561,9 +568,10 @@ 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) { + m := p.module() + if t, ok := t.(Atom); ok && maxPriority < 1201 && m.operators.defined(t) { p.backup() - return nil, errExpectation + return nil, p.unexpected() } if p.placeholder != 0 && t == p.placeholder { @@ -599,7 +607,7 @@ func (p *Parser) openClose() (Term, error) { } if t, _ := p.next(); t.kind != tokenClose { p.backup() - return nil, errExpectation + return nil, p.unexpected() } return t, nil } @@ -625,7 +633,7 @@ func (p *Parser) atom() (Atom, error) { default: p.backup() p.backup() - return 0, errExpectation + return 0, p.unexpected() } case tokenOpenCurly: t, err := p.next() @@ -638,19 +646,20 @@ func (p *Parser) atom() (Atom, error) { default: p.backup() p.backup() - return 0, errExpectation + return 0, p.unexpected() } case tokenDoubleQuotedList: - switch p.doubleQuotes { + m := p.module() + switch m.doubleQuotes { case doubleQuotesAtom: return NewAtom(unDoubleQuote(t.val)), nil default: p.backup() - return 0, errExpectation + return 0, p.unexpected() } default: p.backup() - return 0, errExpectation + return 0, p.unexpected() } } @@ -666,7 +675,7 @@ func (p *Parser) name() (Atom, error) { return NewAtom(unquote(t.val)), nil default: p.backup() - return 0, errExpectation + return 0, p.unexpected() } } @@ -698,13 +707,13 @@ func (p *Parser) list() (Term, error) { return PartialList(rest, args...), nil default: p.backup() - return nil, errExpectation + return nil, p.unexpected() } case tokenCloseList: return List(args...), nil default: p.backup() - return nil, errExpectation + return nil, p.unexpected() } } } @@ -717,7 +726,7 @@ func (p *Parser) curlyBracketedTerm() (Term, error) { if t, _ := p.next(); t.kind != tokenCloseCurly { p.backup() - return nil, errExpectation + return nil, p.unexpected() } return atomEmptyBlock.Apply(t), nil @@ -743,7 +752,7 @@ func (p *Parser) functionalNotation(functor Atom) (Term, error) { return functor.Apply(args...), nil default: p.backup() - return nil, errExpectation + return nil, p.unexpected() } } default: @@ -754,7 +763,8 @@ 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) { + m := p.module() + if m.operators.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 6b9006cf..879a5be8 100644 --- a/engine/parser_test.go +++ b/engine/parser_test.go @@ -160,11 +160,15 @@ func TestParser_Term(t *testing.T) { for _, tc := range tests { t.Run(tc.input, func(t *testing.T) { p := Parser{ - lexer: Lexer{ + Lexer: Lexer{ + module: func() *module { + return &module{ + operators: ops, + doubleQuotes: tc.doubleQuotes, + } + }, input: newRuneRingBuffer(strings.NewReader(tc.input)), }, - operators: ops, - doubleQuotes: tc.doubleQuotes, } term, err := p.Term() assert.Equal(t, tc.err, err) @@ -222,25 +226,29 @@ func TestParser_Replace(t *testing.T) { title: "too few arguments", input: `[?, ?, ?, ?, ?].`, args: []interface{}{1.0, 2, "foo", []string{"a", "b", "c"}}, - termErr: errors.New("not enough arguments for placeholders"), + termErr: errPlaceholder, }, { title: "too many arguments", input: `[?, ?, ?, ?].`, args: []interface{}{1.0, 2, "foo", []string{"a", "b", "c"}, "extra"}, - termErr: errors.New("too many arguments for placeholders: [extra]"), + termErr: errPlaceholder, }, } for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { p := Parser{ - doubleQuotes: tt.doubleQuotes, - lexer: Lexer{ + Lexer: Lexer{ + module: func() *module { + return &module{ + doubleQuotes: tt.doubleQuotes, + } + }, input: newRuneRingBuffer(strings.NewReader(tt.input)), }, } - err := p.SetPlaceholder(NewAtom("?"), tt.args...) + err := p.SetPlaceholder("?", tt.args...) assert.Equal(t, tt.err, err) if err != nil { @@ -248,7 +256,7 @@ func TestParser_Replace(t *testing.T) { } term, err := p.Term() - assert.Equal(t, tt.termErr, err) + assert.ErrorIs(t, err, tt.termErr) assert.Equal(t, tt.term, term) }) } @@ -296,7 +304,7 @@ func TestParser_Number(t *testing.T) { for _, tc := range tests { t.Run(tc.input, func(t *testing.T) { p := Parser{ - lexer: Lexer{ + Lexer: Lexer{ input: newRuneRingBuffer(strings.NewReader(tc.input)), }, } @@ -309,7 +317,10 @@ func TestParser_Number(t *testing.T) { func TestParser_More(t *testing.T) { p := Parser{ - lexer: Lexer{ + Lexer: Lexer{ + module: func() *module { + return &module{} + }, input: newRuneRingBuffer(strings.NewReader(`foo. bar.`)), }, } diff --git a/engine/promise.go b/engine/promise.go index 0b043c0d..76944eb4 100644 --- a/engine/promise.go +++ b/engine/promise.go @@ -132,6 +132,20 @@ func panicError(r interface{}) error { return fmt.Errorf("panic: %v", r) } +type errorWithCaller struct { + file string + line int + err error +} + +func (e errorWithCaller) Error() string { + return fmt.Sprintf("%s (%s:%d)", e.err.Error(), e.file, e.line) +} + +func (e errorWithCaller) Unwrap() error { + return e.err +} + type promiseStack []*Promise func (s *promiseStack) pop() *Promise { diff --git a/engine/stream_test.go b/engine/stream_test.go index d8e698bf..e179fa82 100644 --- a/engine/stream_test.go +++ b/engine/stream_test.go @@ -2,6 +2,8 @@ package engine import ( "bytes" + "embed" + _ "embed" "errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -368,7 +370,7 @@ func TestStream_ReadRune(t *testing.T) { }, { title: "input text: 1 rune left, file", - s: &Stream{source: mustOpen(testdata, "testdata/a.txt"), streamType: streamTypeText, position: 0}, + s: &Stream{source: must(testdata.Open("testdata/a.txt")), streamType: streamTypeText, position: 0}, r: 'a', size: 1, pos: 1, @@ -429,6 +431,18 @@ func TestStream_ReadRune(t *testing.T) { } } +var ( + //go:embed testdata + testdata embed.FS +) + +func must(fs fs.File, err error) fs.File { + if err != nil { + panic(err) + } + return fs +} + type mockSeeker struct { mock.Mock } diff --git a/engine/term.go b/engine/term.go index 8bbf9f5e..31ee55df 100644 --- a/engine/term.go +++ b/engine/term.go @@ -104,3 +104,23 @@ func id(t Term) termID { return t // Assuming it's comparable. } } + +// Qualifying Module and Unqualified Term. +func qmut(m Atom, t Term, env *Env) (Atom, Term) { + c, ok := env.Resolve(t).(Compound) + if !ok { + return m, t + } + + if c.Functor() != atomColon || c.Arity() != 2 { + return m, t + } + + mm, ok := env.Resolve(c.Arg(0)).(Atom) + if !ok { + return m, t + } + tt := c.Arg(1) + + return qmut(mm, tt, env) +} diff --git a/engine/testdata/unexpected_op.txt b/engine/testdata/unexpected_op.txt index 27487c5e..442f4e57 100644 --- a/engine/testdata/unexpected_op.txt +++ b/engine/testdata/unexpected_op.txt @@ -1 +1 @@ -X = a. \ No newline at end of file +X *** a. \ No newline at end of file diff --git a/engine/text.go b/engine/text.go deleted file mode 100644 index 9e00b065..00000000 --- a/engine/text.go +++ /dev/null @@ -1,294 +0,0 @@ -package engine - -import ( - "context" - "fmt" - "io/fs" - "strings" -) - -// discontiguousError is an error that the user-defined predicate is defined by clauses which are not consecutive read-terms. -type discontiguousError struct { - pi procedureIndicator -} - -func (e *discontiguousError) Error() string { - return fmt.Sprintf("%s is discontiguous", e.pi) -} - -// Compile compiles the Prolog text and updates the DB accordingly. -func (vm *VM) Compile(ctx context.Context, s string, args ...interface{}) error { - var t text - if err := vm.compile(ctx, &t, s, args...); err != nil { - return err - } - - if err := t.flush(); err != nil { - 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...) - continue - } - - vm.procedures[pi] = u - } - - for _, g := range t.goals { - ok, err := Call(vm, g, Success, nil).Force(ctx) - if err != nil { - return err - } - if !ok { - var sb strings.Builder - s := NewOutputTextStream(&sb) - _, _ = WriteTerm(vm, s, g, List(atomQuoted.Apply(atomTrue)), Success, nil).Force(ctx) - return fmt.Errorf("failed initialization goal: %s", sb.String()) - } - } - - return nil -} - -// Consult executes Prolog texts in files. -func Consult(vm *VM, files Term, k Cont, env *Env) *Promise { - var filenames []Term - iter := ListIterator{List: files, Env: env} - for iter.Next() { - filenames = append(filenames, iter.Current()) - } - if err := iter.Err(); err != nil { - filenames = []Term{files} - } - - return Delay(func(ctx context.Context) *Promise { - for _, filename := range filenames { - if err := vm.ensureLoaded(ctx, filename, env); err != nil { - return Error(err) - } - } - - return k(env) - }) -} - -func (vm *VM) compile(ctx context.Context, text *text, s string, args ...interface{}) error { - if text.clauses == nil { - text.clauses = map[procedureIndicator]*userDefined{} - } - - s = ignoreShebangLine(s) - p := NewParser(vm, strings.NewReader(s)) - if err := p.SetPlaceholder(NewAtom("?"), args...); err != nil { - return err - } - - for p.More() { - p.Vars = p.Vars[:] - t, err := p.Term() - if err != nil { - return err - } - - et, err := expand(vm, t, nil) - if err != nil { - return err - } - - pi, arg, err := piArg(et, nil) - if err != nil { - return err - } - switch pi { - case procedureIndicator{name: atomIf, arity: 1}: // Directive - if err := vm.directive(ctx, text, arg(0)); err != nil { - return err - } - continue - case procedureIndicator{name: atomIf, arity: 2}: // Rule - pi, arg, err = piArg(arg(0), nil) - if err != nil { - return err - } - fallthrough - default: - if len(text.buf) > 0 && pi != text.buf[0].pi { - if err := text.flush(); err != nil { - return err - } - } - - cs, err := compile(et, nil) - if err != nil { - return err - } - - text.buf = append(text.buf, cs...) - } - } - return nil -} - -func (vm *VM) directive(ctx context.Context, text *text, d Term) error { - if err := text.flush(); err != nil { - return err - } - - switch pi, arg, _ := piArg(d, nil); pi { - case procedureIndicator{name: atomDynamic, arity: 1}: - return text.forEachUserDefined(arg(0), func(u *userDefined) { - u.dynamic = true - u.public = true - }) - case procedureIndicator{name: atomMultifile, arity: 1}: - return text.forEachUserDefined(arg(0), func(u *userDefined) { - u.multifile = true - }) - case procedureIndicator{name: atomDiscontiguous, arity: 1}: - return text.forEachUserDefined(arg(0), func(u *userDefined) { - u.discontiguous = true - }) - case procedureIndicator{name: atomInitialization, arity: 1}: - text.goals = append(text.goals, arg(0)) - return nil - case procedureIndicator{name: atomInclude, arity: 1}: - _, b, err := vm.open(arg(0), nil) - if err != nil { - return err - } - - return vm.compile(ctx, text, string(b)) - case procedureIndicator{name: atomEnsureLoaded, arity: 1}: - return vm.ensureLoaded(ctx, arg(0), nil) - default: - ok, err := Call(vm, d, Success, nil).Force(ctx) - if err != nil { - return err - } - if !ok { - var sb strings.Builder - s := NewOutputTextStream(&sb) - _, _ = WriteTerm(vm, s, d, List(atomQuoted.Apply(atomTrue)), Success, nil).Force(ctx) - return fmt.Errorf("failed directive: %s", sb.String()) - } - return nil - } -} - -func (vm *VM) ensureLoaded(ctx context.Context, file Term, env *Env) error { - f, b, err := vm.open(file, env) - if err != nil { - return err - } - - if vm.loaded == nil { - vm.loaded = map[string]struct{}{} - } - if _, ok := vm.loaded[f]; ok { - return nil - } - defer func() { - vm.loaded[f] = struct{}{} - }() - - return vm.Compile(ctx, string(b)) -} - -func (vm *VM) open(file Term, env *Env) (string, []byte, error) { - switch f := env.Resolve(file).(type) { - case Variable: - return "", nil, InstantiationError(env) - case Atom: - s := f.String() - for _, f := range []string{s, s + ".pl"} { - b, err := fs.ReadFile(vm.FS, f) - if err != nil { - continue - } - - return f, b, nil - } - return "", nil, existenceError(objectTypeSourceSink, file, env) - default: - return "", nil, typeError(validTypeAtom, file, env) - } -} - -type text struct { - buf clauses - clauses map[procedureIndicator]*userDefined - goals []Term -} - -func (t *text) forEachUserDefined(pi Term, f func(u *userDefined)) error { - iter := anyIterator{Any: pi} - for iter.Next() { - switch pi := iter.Current().(type) { - case Variable: - return InstantiationError(nil) - case Compound: - if pi.Functor() != atomSlash || pi.Arity() != 2 { - return typeError(validTypePredicateIndicator, pi, nil) - } - switch n := pi.Arg(0).(type) { - case Variable: - return InstantiationError(nil) - case Atom: - switch a := pi.Arg(1).(type) { - case Variable: - return InstantiationError(nil) - case Integer: - pi := procedureIndicator{name: n, arity: a} - u, ok := t.clauses[pi] - if !ok { - u = &userDefined{} - t.clauses[pi] = u - } - f(u) - default: - return typeError(validTypePredicateIndicator, pi, nil) - } - default: - return typeError(validTypePredicateIndicator, pi, nil) - } - default: - return typeError(validTypePredicateIndicator, pi, nil) - } - } - return iter.Err() -} - -func (t *text) flush() error { - if len(t.buf) == 0 { - return nil - } - - pi := t.buf[0].pi - u, ok := t.clauses[pi] - if !ok { - u = &userDefined{} - t.clauses[pi] = u - } - if len(u.clauses) > 0 && !u.discontiguous { - return &discontiguousError{pi: pi} - } - u.clauses = append(u.clauses, t.buf...) - t.buf = t.buf[:0] - return nil -} - -func ignoreShebangLine(query string) string { - if !strings.HasPrefix(query, "#!") { - return query - } - i := strings.Index(query, "\n") - if i < 0 { - i = len(query) - } - return query[i:] -} diff --git a/engine/text_test.go b/engine/text_test.go deleted file mode 100644 index e18fc2e3..00000000 --- a/engine/text_test.go +++ /dev/null @@ -1,514 +0,0 @@ -package engine - -import ( - "context" - "embed" - "errors" - "io" - "io/fs" - "testing" - - "github.com/stretchr/testify/assert" -) - -//go:embed testdata -var testdata embed.FS - -func mustOpen(fs fs.FS, name string) fs.File { - f, err := fs.Open(name) - if err != nil { - panic(err) - } - return f -} - -func TestVM_Compile(t *testing.T) { - tests := []struct { - title string - text string - args []interface{} - err error - result map[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}, - }, - }, - }, - }, - }}, - {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: "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}, - }, - }, - { - 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}, - }, - }, - }, - }, - {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}, - }, - }, - }, - }, - {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"))), - ), - ), - 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}, - }, - }, - { - 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}, - }, - }, - { - 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}, - }, - }, - }, - }, - }}, - {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}, - }, - }, - { - 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}, - }, - }, - }, - }, - }}, - {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}, - }, - }, - }, - }, - {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: "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}, - }, - }, - }, - }, - {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: "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}, - }, - }, - }, - }, - }}, - {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}, - }, - }, - }, - }, - }}, - - {title: "error: invalid argument", text: ` -foo(?). -`, args: []interface{}{nil}, err: errors.New("can't convert to term: ")}, - {title: "error: syntax error", text: ` -foo(). -`, err: unexpectedTokenError{actual: Token{kind: tokenClose, val: ")"}}}, - {title: "error: expansion error", text: ` -:- ensure_loaded('testdata/break_term_expansion'). -foo(a). -`, err: Exception{term: NewAtom("ball")}}, - {title: "error: variable fact", text: ` -X. -`, err: InstantiationError(nil)}, - {title: "error: variable rule", text: ` -X :- X. -`, err: InstantiationError(nil)}, - {title: "error: non-callable rule body", text: ` -foo :- 1. -`, err: typeError(validTypeCallable, Integer(1), nil)}, - {title: "error: non-PI argument, variable", text: `:- dynamic(PI).`, err: InstantiationError(nil)}, - {title: "error: non-PI argument, not compound", text: `:- dynamic(foo).`, err: typeError(validTypePredicateIndicator, NewAtom("foo"), nil)}, - {title: "error: non-PI argument, compound", text: `:- dynamic(foo(a, b)).`, err: typeError(validTypePredicateIndicator, NewAtom("foo").Apply(NewAtom("a"), NewAtom("b")), nil)}, - {title: "error: non-PI argument, name is variable", text: `:- dynamic(Name/2).`, err: InstantiationError(nil)}, - {title: "error: non-PI argument, arity is variable", text: `:- dynamic(foo/Arity).`, err: InstantiationError(nil)}, - {title: "error: non-PI argument, arity is not integer", text: `:- dynamic(foo/bar).`, err: typeError(validTypePredicateIndicator, atomSlash.Apply(NewAtom("foo"), NewAtom("bar")), nil)}, - {title: "error: non-PI argument, name is not atom", text: `:- dynamic(0/2).`, err: typeError(validTypePredicateIndicator, atomSlash.Apply(Integer(0), Integer(2)), nil)}, - {title: "error: included variable", text: ` -:- include(X). -`, err: InstantiationError(nil)}, - {title: "error: included file not found", text: ` -:- include('testdata/not_found'). -`, err: existenceError(objectTypeSourceSink, NewAtom("testdata/not_found"), nil)}, - {title: "error: included non-atom", text: ` -:- include(1). -`, err: typeError(validTypeAtom, Integer(1), nil)}, - {title: "error: initialization exception", text: ` -:- initialization(bar). -`, err: existenceError(objectTypeProcedure, atomSlash.Apply(NewAtom("bar"), Integer(0)), nil)}, - {title: "error: initialization failure", text: ` -:- initialization(foo(d)). -`, err: errors.New("failed initialization goal: foo(d)")}, - {title: "error: predicate-backed directive exception", text: ` -:- bar. -`, err: existenceError(objectTypeProcedure, atomSlash.Apply(NewAtom("bar"), Integer(0)), nil)}, - {title: "error: predicate-backed directive failure", text: ` -:- foo(d). -`, err: errors.New("failed directive: foo(d)")}, - {title: "error: discontiguous, end of text", text: ` -foo(a). -bar(a). -foo(b). -`, err: &discontiguousError{pi: procedureIndicator{name: NewAtom("foo"), arity: 1}}}, - {title: "error: discontiguous, before directive", text: ` -foo(a). -bar(a). -foo(b). -:- foo(c). -`, err: &discontiguousError{pi: procedureIndicator{name: NewAtom("foo"), arity: 1}}}, - {title: "error: discontiguous, before other facts", text: ` -foo(a). -bar(a). -foo(b). -bar(b). -`, err: &discontiguousError{pi: procedureIndicator{name: NewAtom("foo"), arity: 1}}}, - } - - 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}, - }, - }, - }, - }, - } - 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) - } - }) - } -} - -func TestVM_Consult(t *testing.T) { - x := NewVariable() - - tests := []struct { - title string - files Term - ok bool - err error - }{ - {title: `:- consult('testdata/empty.txt').`, files: NewAtom("testdata/empty.txt"), ok: true}, - {title: `:- consult([]).`, files: List(), ok: true}, - {title: `:- consult(['testdata/empty.txt']).`, files: List(NewAtom("testdata/empty.txt")), ok: true}, - {title: `:- consult(['testdata/empty.txt', 'testdata/empty.txt']).`, files: List(NewAtom("testdata/empty.txt"), NewAtom("testdata/empty.txt")), ok: true}, - - {title: `:- consult('testdata/abc.txt').`, files: NewAtom("testdata/abc.txt"), err: io.EOF}, - {title: `:- consult(['testdata/abc.txt']).`, files: List(NewAtom("testdata/abc.txt")), err: io.EOF}, - - {title: `:- consult(X).`, files: x, err: InstantiationError(nil)}, - {title: `:- consult(foo(bar)).`, files: NewAtom("foo").Apply(NewAtom("bar")), err: typeError(validTypeAtom, NewAtom("foo").Apply(NewAtom("bar")), nil)}, - {title: `:- consult(1).`, files: Integer(1), err: typeError(validTypeAtom, Integer(1), nil)}, - {title: `:- consult(['testdata/empty.txt'|_]).`, files: PartialList(NewVariable(), NewAtom("testdata/empty.txt")), err: typeError(validTypeAtom, PartialList(NewVariable(), NewAtom("testdata/empty.txt")), nil)}, - {title: `:- consult([X]).`, files: List(x), err: InstantiationError(nil)}, - {title: `:- consult([1]).`, files: List(Integer(1)), err: typeError(validTypeAtom, Integer(1), nil)}, - - {title: `:- consult('testdata/not_found.txt').`, files: NewAtom("testdata/not_found.txt"), err: existenceError(objectTypeSourceSink, NewAtom("testdata/not_found.txt"), nil)}, - {title: `:- consult(['testdata/not_found.txt']).`, files: List(NewAtom("testdata/not_found.txt")), err: existenceError(objectTypeSourceSink, NewAtom("testdata/not_found.txt"), nil)}, - } - - for _, tt := range tests { - t.Run(tt.title, func(t *testing.T) { - vm := VM{ - FS: testdata, - } - ok, err := Consult(&vm, tt.files, Success, nil).Force(context.Background()) - assert.Equal(t, tt.ok, ok) - if e, ok := tt.err.(Exception); ok { - _, ok := NewEnv().Unify(e.Term(), err.(Exception).Term()) - assert.True(t, ok) - } else { - assert.Equal(t, tt.err, err) - } - }) - } -} - -func TestDiscontiguousError_Error(t *testing.T) { - e := discontiguousError{pi: procedureIndicator{name: NewAtom("foo"), arity: 1}} - assert.Equal(t, "foo/1 is discontiguous", e.Error()) -} diff --git a/engine/vm.go b/engine/vm.go index f39fbd16..4b2a6c50 100644 --- a/engine/vm.go +++ b/engine/vm.go @@ -5,7 +5,9 @@ import ( "fmt" "io" "io/fs" + "path/filepath" "strings" + "time" ) type bytecode []instruction @@ -46,140 +48,87 @@ func Failure(*Env) *Promise { return Bool(false) } +type loadResult struct { + module Atom + loadedAt time.Time +} + // VM is the core of a Prolog interpreter. The zero value for VM is a valid VM without any builtin predicates. 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 - unknown unknownAction - // FS is a file system that is referenced when the VM loads Prolog texts e.g. ensure_loaded/1. // It has no effect on open/4 nor open/3 which always access the actual file system. FS fs.FS - loaded map[string]struct{} + loaded map[string]loadResult - // Internal/external expression - operators operators - charConversions map[rune]rune - charConvEnabled bool - doubleQuotes doubleQuotes + modules map[Atom]*module + system Atom + typeIn Atom // I/O streams streams input, output *Stream - - // Misc - debug bool } -// 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 +// Module returns the module. +func (vm *VM) Module(name string) *module { + return vm.module(NewAtom(name)) } -// 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 +// TypeInModule returns the type-in module. +func (vm *VM) TypeInModule() *module { + return vm.module(vm.typeIn) } -// Register2 registers a predicate of arity 2. -func (vm *VM) Register2(name Atom, p Predicate2) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} +func (vm *VM) module(name Atom) *module { + if vm.modules == nil { + vm.modules = map[Atom]*module{} } - vm.procedures[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{} + if m, ok := vm.modules[name]; ok { + return m } - vm.procedures[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{} + m := newModule() + if vm.modules == nil { + vm.modules = map[Atom]*module{} } - vm.procedures[procedureIndicator{name: name, arity: 4}] = p -} + vm.modules[name] = m -// Register5 registers a predicate of arity 5. -func (vm *VM) Register5(name Atom, p Predicate5) { - if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + if s := vm.system; s != 0 { + vm.importPredicates(name, vm.system, nil) } - vm.procedures[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 + return m } -// 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 +// SetModule sets the type-in module. +func (vm *VM) SetModule(name Atom) { + vm.typeIn = name } -// 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 -} - -type unknownAction int - -const ( - unknownError unknownAction = iota - unknownFail - unknownWarning -) - -func (u unknownAction) String() string { - return [...]string{ - unknownError: "error", - unknownFail: "fail", - unknownWarning: "warning", - }[u] -} - -type procedure interface { - call(*VM, []Term, Cont, *Env) *Promise +func (vm *VM) SetSystemModule(name Atom) { + vm.system = name } // Cont is a continuation. type Cont func(*Env) *Promise // Arrive is the entry point of the VM. -func (vm *VM) Arrive(name Atom, args []Term, k Cont, env *Env) (promise *Promise) { +func (vm *VM) Arrive(module, name Atom, args []Term, k Cont, env *Env) (promise *Promise) { defer ensurePromise(&promise) if vm.Unknown == nil { vm.Unknown = func(Atom, []Term, *Env) {} } - pi := procedureIndicator{name: name, arity: Integer(len(args))} - p, ok := vm.procedures[pi] - if !ok { - switch vm.unknown { + m := vm.module(module) + pi := predicateIndicator{name: name, arity: Integer(len(args))} + e := m.procedures[pi] + if e.procedure == nil { + switch m.unknown { case unknownWarning: vm.Unknown(name, args, env) fallthrough @@ -193,7 +142,7 @@ func (vm *VM) Arrive(name Atom, args []Term, k Cont, env *Env) (promise *Promise // bind the special variable to inform the predicate about the context. env = env.bind(varContext, pi.Term()) - return p.call(vm, args, k, env) + return e.procedure.call(vm, args, k, env) } func (vm *VM) exec(pc bytecode, vars []Variable, cont Cont, args []Term, astack [][]Term, env *Env, cutParent *Promise) *Promise { @@ -218,7 +167,7 @@ func (vm *VM) exec(pc bytecode, vars []Variable, cont Cont, args []Term, astack v := vars[operand.(Integer)] args = append(args, v) case opGetFunctor: - pi := operand.(procedureIndicator) + pi := operand.(predicateIndicator) arg, astack = env.Resolve(args[0]), append(astack, args[1:]) args = make([]Term, int(pi.arity)) for i := range args { @@ -226,7 +175,7 @@ func (vm *VM) exec(pc bytecode, vars []Variable, cont Cont, args []Term, astack } env, ok = env.Unify(arg, pi.name.Apply(args...)) case opPutFunctor: - pi := operand.(procedureIndicator) + pi := operand.(predicateIndicator) vs := make([]Term, int(pi.arity)) arg = pi.name.Apply(vs...) args = append(args, arg) @@ -237,8 +186,8 @@ func (vm *VM) exec(pc bytecode, vars []Variable, cont Cont, args []Term, astack case opEnter: break case opCall: - pi := operand.(procedureIndicator) - return vm.Arrive(pi.name, args, func(env *Env) *Promise { + pi := operand.(qualifiedPredicateIndicator) + return vm.Arrive(pi.module, pi.name, args, func(env *Env) *Promise { return vm.exec(pc, vars, cont, nil, nil, env, cutParent) }, env) case opExit: @@ -302,6 +251,154 @@ func (vm *VM) SetUserOutput(s *Stream) { vm.output = s } +type ImportSpec struct { + Name string + Arity int +} + +type LoadFileOptions struct { + condition LoadFileCondition + importList []ImportSpec +} + +// LoadFileCondition denotes a condition to load a file. +type LoadFileCondition int + +const ( + // LoadFileConditionTrue is a condition which always loads a file. + LoadFileConditionTrue LoadFileCondition = iota + // LoadFileConditionChanged is a condition which loads a file if it's not loaded yet or the timestamp is updated. + LoadFileConditionChanged +) + +// LoadFileOption specifies how the VM loads the file. +type LoadFileOption func(*LoadFileOptions) + +// LoadFileOptionIf specifies the condition of loading the file. +func LoadFileOptionIf(cond LoadFileCondition) func(*LoadFileOptions) { + return func(opts *LoadFileOptions) { + opts.condition = cond + } +} + +// LoadFileOptionImports specifies which predicates the type-in module imports from the loaded module. +func LoadFileOptionImports(importList []ImportSpec) func(options *LoadFileOptions) { + return func(opts *LoadFileOptions) { + opts.importList = importList + } +} + +// LoadFile loads a Prolog text from a file specified by filename. +func (vm *VM) LoadFile(ctx context.Context, filename string, options ...LoadFileOption) error { + var opts LoadFileOptions + for _, f := range options { + f(&opts) + } + + fs := extFS{ + fs: vm.FS, + extensions: []string{"", ".pl"}, + } + f, err := fs.Open(filename) + if err != nil { + return err + } + defer func() { + _ = f.Close() + }() + + fi, err := f.Stat() + if err != nil { + return err + } + + r, ok := vm.loaded[filename] + loading := !ok // If it's not loaded yet + loading = loading || opts.condition == LoadFileConditionTrue + loading = loading || opts.condition == LoadFileConditionChanged && fi.ModTime().After(r.loadedAt) + loading = loading || fi.ModTime().IsZero() // If the fs.FS doesn't report mod times + if loading { + b, err := io.ReadAll(f) + if err != nil { + return err + } + + module, err := vm.Compile(ctx, string(b)) + if err != nil { + return err + } + + r.module = module + r.loadedAt = time.Now() + } + + vm.importPredicates(vm.typeIn, r.module, opts.importList) + + return nil +} + +type LoadOptions struct { + importList []ImportSpec +} + +// LoadOption specifies how the VM loads the prolog text. +type LoadOption func(*LoadOptions) + +// LoadOptionImports specifies which predicates the type-in module imports from the loaded module. +func LoadOptionImports(importList []ImportSpec) func(options *LoadOptions) { + return func(opts *LoadOptions) { + opts.importList = importList + } +} + +// LoadText loads a Prolog text. +func (vm *VM) LoadText(ctx context.Context, text string, options ...LoadOption) error { + var opts LoadOptions + for _, f := range options { + f(&opts) + } + + module, err := vm.Compile(ctx, text) + if err != nil { + return err + } + + vm.importPredicates(vm.typeIn, module, opts.importList) + + return nil +} + +func (vm *VM) importPredicates(to, from Atom, importList []ImportSpec) { + dst, src := vm.module(to), vm.module(from) + + var pis []predicateIndicator + if importList == nil { + pis = make([]predicateIndicator, 0, len(src.procedures)) + for pi := range src.procedures { + pis = append(pis, pi) + } + } else { + pis = make([]predicateIndicator, len(importList)) + for i, spec := range importList { + pis[i] = predicateIndicator{ + name: NewAtom(spec.Name), + arity: Integer(spec.Arity), + } + } + } + + if dst.procedures == nil { + dst.procedures = map[predicateIndicator]procedureEntry{} + } + for _, pi := range pis { + orig := dst.procedures[pi] + e := src.procedures[pi] + e.importedFrom = from + e.exported = orig.exported + dst.procedures[pi] = e + } +} + // Predicate0 is a predicate of arity 0. type Predicate0 func(*VM, Cont, *Env) *Promise @@ -401,36 +498,41 @@ func (p Predicate8) call(vm *VM, args []Term, k Cont, env *Env) *Promise { return p(vm, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], k, env) } -// procedureIndicator identifies a procedure e.g. (=)/2. -type procedureIndicator struct { +type qualifiedPredicateIndicator struct { + module Atom + predicateIndicator +} + +// predicateIndicator identifies a predicate e.g. (=)/2. +type predicateIndicator struct { name Atom arity Integer } -func (p procedureIndicator) WriteTerm(w io.Writer, opts *WriteOptions, env *Env) error { +func (p predicateIndicator) WriteTerm(w io.Writer, opts *WriteOptions, env *Env) error { return WriteCompound(w, p, opts, env) } -func (p procedureIndicator) Compare(t Term, env *Env) int { +func (p predicateIndicator) Compare(t Term, env *Env) int { return CompareCompound(p, t, env) } -func (p procedureIndicator) Functor() Atom { +func (p predicateIndicator) Functor() Atom { return atomSlash } -func (p procedureIndicator) Arity() int { +func (p predicateIndicator) Arity() int { return 2 } -func (p procedureIndicator) Arg(n int) Term { +func (p predicateIndicator) Arg(n int) Term { if n == 0 { return p.name } return p.arity } -func (p procedureIndicator) String() string { +func (p predicateIndicator) String() string { var sb strings.Builder _ = p.name.WriteTerm(&sb, &WriteOptions{ quoted: true, @@ -440,28 +542,28 @@ func (p procedureIndicator) String() string { } // Term returns p as term. -func (p procedureIndicator) Term() Term { +func (p predicateIndicator) Term() Term { return atomSlash.Apply(p.name, p.arity) } // Apply applies p to args. -func (p procedureIndicator) Apply(args ...Term) (Term, error) { +func (p predicateIndicator) Apply(args ...Term) (Term, error) { if p.arity != Integer(len(args)) { return nil, &wrongNumberOfArgumentsError{expected: int(p.arity), actual: args} } return p.name.Apply(args...), nil } -func piArg(t Term, env *Env) (procedureIndicator, func(int) Term, error) { +func piArg(t Term, env *Env) (predicateIndicator, func(int) Term, error) { switch f := env.Resolve(t).(type) { case Variable: - return procedureIndicator{}, nil, InstantiationError(env) + return predicateIndicator{}, nil, InstantiationError(env) case Atom: - return procedureIndicator{name: f, arity: 0}, nil, nil + return predicateIndicator{name: f, arity: 0}, nil, nil case Compound: - return procedureIndicator{name: f.Functor(), arity: Integer(f.Arity())}, f.Arg, nil + return predicateIndicator{name: f.Functor(), arity: Integer(f.Arity())}, f.Arg, nil default: - return procedureIndicator{}, nil, typeError(validTypeCallable, f, env) + return predicateIndicator{}, nil, typeError(validTypeCallable, f, env) } } @@ -473,3 +575,39 @@ type wrongNumberOfArgumentsError struct { func (e *wrongNumberOfArgumentsError) Error() string { return fmt.Sprintf("wrong number of arguments: expected=%d, actual=%s", e.expected, e.actual) } + +// extFS is a file system that fills in file extensions if omitted. +type extFS struct { + fs fs.FS + extensions []string +} + +var ( + _ fs.FS = extFS{} + _ fs.StatFS = extFS{} +) + +func (e extFS) Open(name string) (fs.File, error) { + if ext := filepath.Ext(name); ext != "" { + return e.fs.Open(name) + } + for _, ext := range e.extensions { + f, err := e.fs.Open(name + ext) + if err != nil { + continue + } + return f, nil + } + return nil, fs.ErrNotExist +} + +func (e extFS) Stat(name string) (fs.FileInfo, error) { + for _, ext := range e.extensions { + fi, err := fs.Stat(e.fs, name+ext) + if err != nil { + continue + } + return fi, nil + } + return nil, fs.ErrNotExist +} diff --git a/engine/vm_test.go b/engine/vm_test.go index 9e1381ee..a7de14b8 100644 --- a/engine/vm_test.go +++ b/engine/vm_test.go @@ -8,198 +8,21 @@ import ( "github.com/stretchr/testify/assert" ) -func TestVM_Register0(t *testing.T) { - var vm VM - vm.Register0(NewAtom("foo"), func(_ *VM, k Cont, env *Env) *Promise { - return k(env) - }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 0}] - - t.Run("ok", func(t *testing.T) { - ok, err := p.call(&vm, []Term{}, Success, nil).Force(context.Background()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", func(t *testing.T) { - ok, err := p.call(&vm, []Term{NewAtom("a")}, Success, nil).Force(context.Background()) - assert.Error(t, err) - assert.False(t, ok) - - assert.Equal(t, "wrong number of arguments: expected=0, actual=[a]", err.Error()) - }) -} - -func TestVM_Register1(t *testing.T) { - var vm VM - 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}] - - t.Run("ok", func(t *testing.T) { - ok, err := p.call(&vm, []Term{NewAtom("a")}, Success, nil).Force(context.Background()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", func(t *testing.T) { - ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b")}, Success, nil).Force(context.Background()) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestVM_Register2(t *testing.T) { - var vm VM - 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}] - - t.Run("ok", func(t *testing.T) { - ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b")}, Success, nil).Force(context.Background()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", func(t *testing.T) { - ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c")}, Success, nil).Force(context.Background()) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestVM_Register3(t *testing.T) { - var vm VM - 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}] - - t.Run("ok", func(t *testing.T) { - ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c")}, Success, nil).Force(context.Background()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", func(t *testing.T) { - ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d")}, Success, nil).Force(context.Background()) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestVM_Register4(t *testing.T) { - var vm VM - 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}] - - 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()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", 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()) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestVM_Register5(t *testing.T) { - var vm VM - 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}] - - 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()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", 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()) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestVM_Register6(t *testing.T) { - var vm VM - 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}] - - 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()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", 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()) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestVM_Register7(t *testing.T) { - var vm VM - 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}] - - 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()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", 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()) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestVM_Register8(t *testing.T) { - var vm VM - 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}] - - 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()) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("wrong number of arguments", 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"), NewAtom("i")}, Success, nil).Force(context.Background()) - assert.Error(t, err) - assert.False(t, ok) - }) -} - 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) - }), + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + procedures: map[predicateIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 1}: {procedure: 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()) + ok, err := vm.Arrive(atomUser, NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) }) @@ -207,9 +30,14 @@ func TestVM_Arrive(t *testing.T) { t.Run("unknown procedure", func(t *testing.T) { t.Run("error", func(t *testing.T) { vm := VM{ - unknown: unknownError, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + unknown: unknownError, + }, + }, } - ok, err := vm.Arrive(NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) + ok, err := vm.Arrive(atomUser, NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) assert.Equal(t, existenceError(objectTypeProcedure, &compound{ functor: atomSlash, args: []Term{NewAtom("foo"), Integer(1)}, @@ -220,7 +48,12 @@ func TestVM_Arrive(t *testing.T) { t.Run("warning", func(t *testing.T) { var warned bool vm := VM{ - unknown: unknownWarning, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + unknown: unknownWarning, + }, + }, Unknown: func(name Atom, args []Term, env *Env) { assert.Equal(t, NewAtom("foo"), name) assert.Equal(t, []Term{NewAtom("a")}, args) @@ -228,7 +61,7 @@ func TestVM_Arrive(t *testing.T) { warned = true }, } - ok, err := vm.Arrive(NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) + ok, err := vm.Arrive(atomUser, NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.False(t, ok) assert.True(t, warned) @@ -236,9 +69,14 @@ func TestVM_Arrive(t *testing.T) { t.Run("fail", func(t *testing.T) { vm := VM{ - unknown: unknownFail, + typeIn: atomUser, + modules: map[Atom]*module{ + atomUser: { + unknown: unknownFail, + }, + }, } - ok, err := vm.Arrive(NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) + ok, err := vm.Arrive(atomUser, NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) assert.NoError(t, err) assert.False(t, ok) }) @@ -269,7 +107,7 @@ func TestVM_SetUserOutput(t *testing.T) { func TestProcedureIndicator_Apply(t *testing.T) { t.Run("ok", func(t *testing.T) { - c, err := procedureIndicator{name: NewAtom("foo"), arity: 2}.Apply(NewAtom("a"), NewAtom("b")) + c, err := predicateIndicator{name: NewAtom("foo"), arity: 2}.Apply(NewAtom("a"), NewAtom("b")) assert.NoError(t, err) assert.Equal(t, &compound{ functor: NewAtom("foo"), @@ -278,7 +116,7 @@ func TestProcedureIndicator_Apply(t *testing.T) { }) t.Run("wrong number of arguments", func(t *testing.T) { - c, err := procedureIndicator{name: NewAtom("foo"), arity: 2}.Apply(NewAtom("a"), NewAtom("b"), NewAtom("c")) + c, err := predicateIndicator{name: NewAtom("foo"), arity: 2}.Apply(NewAtom("a"), NewAtom("b"), NewAtom("c")) assert.Error(t, err) assert.Nil(t, c) }) diff --git a/examples/call_go_from_prolog/main.go b/examples/call_go_from_prolog/main.go index 44309cdb..95d6c1ef 100644 --- a/examples/call_go_from_prolog/main.go +++ b/examples/call_go_from_prolog/main.go @@ -12,7 +12,8 @@ func main() { p := prolog.New(nil, nil) // Define a custom predicate of arity 2. - p.Register2(engine.NewAtom("get_status"), func(_ *engine.VM, url, status engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { + m := p.TypeInModule() + m.Register2("get_status", func(_ *engine.VM, url, status engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { // Check if the input arguments are of the types you expected. u, ok := env.Resolve(url).(engine.Atom) if !ok { @@ -36,7 +37,7 @@ func main() { }) // Treat a string argument as an atom. - if err := p.Exec(`:- set_prolog_flag(double_quotes, atom).`); err != nil { + if err := p.QuerySolution(`set_prolog_flag(double_quotes, atom).`).Err(); err != nil { panic(err) } diff --git a/examples/dcg/main.go b/examples/dcg/main.go index d9840fa3..b34e0c1c 100644 --- a/examples/dcg/main.go +++ b/examples/dcg/main.go @@ -1,9 +1,10 @@ package main import ( + "context" + "embed" "flag" "fmt" - "github.com/ichiban/prolog" ) @@ -16,6 +17,9 @@ import ( // $ go run examples/dcg/main.go the cat chases the mouse // $ go run examples/dcg/main.go -prefix the cat +//go:embed prolog +var prologTexts embed.FS + func main() { var prefix bool flag.BoolVar(&prefix, "prefix", false, "prefix search mode") @@ -23,23 +27,13 @@ func main() { // First, create a Prolog interpreter. i := prolog.New(nil, nil) + i.FS = prolog.OverlayFS{ + prologTexts, + i.FS, + } // Then, define DCG rules with -->/2. - if err := i.Exec(` -:- set_prolog_flag(double_quotes, atom). - -sentence --> noun_phrase, verb_phrase. -verb_phrase --> verb. -noun_phrase --> article, noun. -noun_phrase --> article, adjective, noun. -article --> [the]. -adjective --> [nice]. -noun --> [dog]. -noun --> [cat]. -verb --> [runs]. -verb --> [barks]. -verb --> [bites]. -`); err != nil { + if err := i.LoadFile(context.Background(), "prolog/sentence.pl"); err != nil { panic(err) } @@ -65,6 +59,11 @@ verb --> [bites]. fmt.Printf("%s\n", s.Sentence) } + + if err := sols.Err(); err != nil { + panic(err) + } + return } diff --git a/examples/dcg/prolog/sentence.pl b/examples/dcg/prolog/sentence.pl new file mode 100644 index 00000000..30d8c466 --- /dev/null +++ b/examples/dcg/prolog/sentence.pl @@ -0,0 +1,15 @@ +:- use_module(library(prologue), [append/3]). +:- use_module(library(dcg)). +:- set_prolog_flag(double_quotes, atom). + +sentence --> noun_phrase, verb_phrase. +verb_phrase --> verb. +noun_phrase --> article, noun. +noun_phrase --> article, adjective, noun. +article --> [the]. +adjective --> [nice]. +noun --> [dog]. +noun --> [cat]. +verb --> [runs]. +verb --> [barks]. +verb --> [bites]. diff --git a/examples/embed_prolog_into_go/main.go b/examples/embed_prolog_into_go/main.go index 8c1480ed..46513a06 100644 --- a/examples/embed_prolog_into_go/main.go +++ b/examples/embed_prolog_into_go/main.go @@ -1,25 +1,21 @@ package main import ( + "context" + "embed" "fmt" "github.com/ichiban/prolog" ) +//go:embed prolog +var prologTexts embed.FS + // http://www.cse.unsw.edu.au/~billw/dictionaries/prolog/cut.html func main() { p := prolog.New(nil, nil) - if err := p.Exec(` -teaches(dr_fred, history). -teaches(dr_fred, english). -teaches(dr_fred, drama). -teaches(dr_fiona, physics). - -studies(alice, english). -studies(angus, english). -studies(amelia, drama). -studies(alex, physics). -`); err != nil { + p.FS = prologTexts + if err := p.LoadFile(context.Background(), "prolog/main.pl"); err != nil { panic(err) } diff --git a/examples/embed_prolog_into_go/prolog/main.pl b/examples/embed_prolog_into_go/prolog/main.pl new file mode 100644 index 00000000..83c34759 --- /dev/null +++ b/examples/embed_prolog_into_go/prolog/main.pl @@ -0,0 +1,9 @@ +teaches(dr_fred, history). +teaches(dr_fred, english). +teaches(dr_fred, drama). +teaches(dr_fiona, physics). + +studies(alice, english). +studies(angus, english). +studies(amelia, drama). +studies(alex, physics). diff --git a/examples/hanoi/main.go b/examples/hanoi/main.go index ff2c3178..c9683b65 100644 --- a/examples/hanoi/main.go +++ b/examples/hanoi/main.go @@ -1,6 +1,8 @@ package main import ( + "context" + "embed" "flag" "fmt" @@ -8,26 +10,22 @@ import ( "github.com/ichiban/prolog/engine" ) +//go:embed prolog +var prologTexts embed.FS + func main() { var n int flag.IntVar(&n, "n", 3, "the number of disks") flag.Parse() i := prolog.New(nil, nil) - if err := i.Exec(` -hanoi(N) :- move(N, left, right, center). - -move(0, _, _, _) :- !. -move(N, X, Y, Z) :- - M is N - 1, - move(M, X, Z, Y), - actuate(X, Y), - move(M, Z, Y, X). -`); err != nil { + i.FS = prologTexts + if err := i.LoadFile(context.Background(), "prolog/hanoi.pl"); err != nil { panic(err) } - i.Register2(engine.NewAtom("actuate"), func(_ *engine.VM, x engine.Term, y engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { + m := i.TypeInModule() + m.Register2("actuate", func(_ *engine.VM, x engine.Term, y engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { fmt.Printf("move a disk from %s to %s.\n", env.Resolve(x), env.Resolve(y)) return k(env) }) diff --git a/examples/hanoi/prolog/hanoi.pl b/examples/hanoi/prolog/hanoi.pl new file mode 100644 index 00000000..064e3a0c --- /dev/null +++ b/examples/hanoi/prolog/hanoi.pl @@ -0,0 +1,8 @@ +hanoi(N) :- move(N, left, right, center). + +move(0, _, _, _) :- !. +move(N, X, Y, Z) :- + M is N - 1, + move(M, X, Z, Y), + actuate(X, Y), + move(M, Z, Y, X). diff --git a/examples/initialization/main.go b/examples/initialization/main.go index 8614beb6..529fae77 100644 --- a/examples/initialization/main.go +++ b/examples/initialization/main.go @@ -1,18 +1,21 @@ package main import ( + "context" + "embed" _ "embed" "os" "github.com/ichiban/prolog" ) -//go:embed hello.pl -var hello string +//go:embed prolog +var prologTexts embed.FS func main() { p := prolog.New(nil, os.Stdout) - if err := p.Exec(hello); err != nil { + p.FS = prologTexts + if err := p.LoadFile(context.Background(), "prolog/hello.pl"); err != nil { panic(err) } } diff --git a/examples/initialization/hello.pl b/examples/initialization/prolog/hello.pl similarity index 100% rename from examples/initialization/hello.pl rename to examples/initialization/prolog/hello.pl diff --git a/examples/sandboxing/main.go b/examples/sandboxing/main.go index 28fb290d..e3029beb 100644 --- a/examples/sandboxing/main.go +++ b/examples/sandboxing/main.go @@ -1,24 +1,31 @@ package main import ( + "context" + "embed" "fmt" "github.com/ichiban/prolog/engine" "github.com/ichiban/prolog" ) +//go:embed prolog +var prologTexts embed.FS + func main() { // Instantiates a new Prolog interpreter without any builtin predicates nor operators. p := new(prolog.Interpreter) + p.FS = prologTexts // In this vanilla interpreter, even the infix operator `:-` is not defined. // Instead of writing `:-(mortal(X), human(X)).`, you may want to define the infix operator first. // To define operators, register op/3. - p.Register3(engine.NewAtom("op"), engine.Op) + m := p.TypeInModule() + m.Register3("op", engine.Op) // Then, define the infix operator with priority 1200 and specifier XFX. - if err := p.Exec(`:-(op(1200, xfx, :-)).`); err != nil { + if err := p.QuerySolution(`op(1200, xfx, :-).`).Err(); err != nil { panic(err) } @@ -26,10 +33,7 @@ func main() { // You can use p.Register0~5 to register any builtin/custom predicates of respective arity. // Now you can load a Prolog program with infix `:-`. - if err := p.Exec(` - human(socrates). - mortal(X) :- human(X). - `); err != nil { + if err := p.LoadFile(context.Background(), "prolog/main.pl"); err != nil { panic(err) } diff --git a/examples/sandboxing/prolog/main.pl b/examples/sandboxing/prolog/main.pl new file mode 100644 index 00000000..cfb6913f --- /dev/null +++ b/examples/sandboxing/prolog/main.pl @@ -0,0 +1,2 @@ +human(socrates). +mortal(X) :- human(X). diff --git a/fs.go b/fs.go new file mode 100644 index 00000000..733bf7ef --- /dev/null +++ b/fs.go @@ -0,0 +1,40 @@ +package prolog + +import ( + "errors" + "io/fs" +) + +// OverlayFS is a sequence of fs.FS. +// If the requested file doesn't exist in the first fs.FS, it falls back to the next and so on. +type OverlayFS []fs.FS + +var ( + _ fs.FS = OverlayFS{} + _ fs.StatFS = OverlayFS{} +) + +func (o OverlayFS) Open(name string) (fs.File, error) { + for _, e := range o { + switch f, err := e.Open(name); { + case err == nil: + return f, nil + case errors.Is(err, fs.ErrNotExist): + continue + default: + return nil, err + } + } + return nil, fs.ErrNotExist +} + +func (o OverlayFS) Stat(name string) (fs.FileInfo, error) { + for _, e := range o { + fi, err := fs.Stat(e, name) + if err != nil { + continue + } + return fi, nil + } + return nil, fs.ErrNotExist +} diff --git a/interpreter.go b/interpreter.go index f0f88248..fd56d691 100644 --- a/interpreter.go +++ b/interpreter.go @@ -2,173 +2,178 @@ package prolog import ( "context" + "embed" _ "embed" // for go:embed "errors" "github.com/ichiban/prolog/engine" "io" - "io/fs" "os" "strings" ) -//go:embed bootstrap.pl -var bootstrap string +//go:embed library +var library embed.FS // Interpreter is a Prolog interpreter. The zero value is a valid interpreter without any predicates/operators defined. type Interpreter struct { engine.VM - loaded map[string]struct{} } -// New creates a new Prolog interpreter with predefined predicates/operators. +// New creates a new Prolog interpreter with predefined fs.FS/predicates. func New(in io.Reader, out io.Writer) *Interpreter { var i Interpreter - i.FS = defaultFS{} + i.FS = OverlayFS{ + library, + os.DirFS("."), + } i.SetUserInput(engine.NewInputTextStream(in)) i.SetUserOutput(engine.NewOutputTextStream(out)) + // Directives + i.SetPredicate1("dynamic", engine.Dynamic) + i.SetPredicate1("multifile", engine.Multifile) + i.SetPredicate1("discontiguous", engine.Discontiguous) + i.SetPredicate1("initialization", engine.Initialization) + i.SetPredicate1("include", engine.Include) + // Control constructs - i.Register1(engine.NewAtom("call"), engine.Call) - i.Register3(engine.NewAtom("catch"), engine.Catch) - i.Register1(engine.NewAtom("throw"), engine.Throw) + i.SetPredicate1("call", engine.Call) + i.SetPredicate3("catch", engine.Catch) + i.SetPredicate1("throw", engine.Throw) // Term unification - i.Register2(engine.NewAtom("="), engine.Unify) - i.Register2(engine.NewAtom("unify_with_occurs_check"), engine.UnifyWithOccursCheck) - i.Register2(engine.NewAtom("subsumes_term"), engine.SubsumesTerm) + i.SetPredicate2("=", engine.Unify) + i.SetPredicate2("unify_with_occurs_check", engine.UnifyWithOccursCheck) + i.SetPredicate2("subsumes_term", engine.SubsumesTerm) // Type testing - i.Register1(engine.NewAtom("var"), engine.TypeVar) - i.Register1(engine.NewAtom("atom"), engine.TypeAtom) - i.Register1(engine.NewAtom("integer"), engine.TypeInteger) - i.Register1(engine.NewAtom("float"), engine.TypeFloat) - i.Register1(engine.NewAtom("compound"), engine.TypeCompound) - i.Register1(engine.NewAtom("acyclic_term"), engine.AcyclicTerm) + i.SetPredicate1("var", engine.TypeVar) + i.SetPredicate1("atom", engine.TypeAtom) + i.SetPredicate1("integer", engine.TypeInteger) + i.SetPredicate1("float", engine.TypeFloat) + i.SetPredicate1("compound", engine.TypeCompound) + i.SetPredicate1("acyclic_term", engine.AcyclicTerm) // Term comparison - i.Register3(engine.NewAtom("compare"), engine.Compare) - i.Register2(engine.NewAtom("sort"), engine.Sort) - i.Register2(engine.NewAtom("keysort"), engine.KeySort) + i.SetPredicate3("compare", engine.Compare) + i.SetPredicate2("sort", engine.Sort) + i.SetPredicate2("keysort", engine.KeySort) // Term creation and decomposition - i.Register3(engine.NewAtom("functor"), engine.Functor) - i.Register3(engine.NewAtom("arg"), engine.Arg) - i.Register2(engine.NewAtom("=.."), engine.Univ) - i.Register2(engine.NewAtom("copy_term"), engine.CopyTerm) - i.Register2(engine.NewAtom("term_variables"), engine.TermVariables) + i.SetPredicate3("functor", engine.Functor) + i.SetPredicate3("arg", engine.Arg) + i.SetPredicate2("univ", engine.Univ) + i.SetPredicate2("copy_term", engine.CopyTerm) + i.SetPredicate2("term_variables", engine.TermVariables) // Arithmetic evaluation - i.Register2(engine.NewAtom("is"), engine.Is) + i.SetPredicate2("is", engine.Is) // Arithmetic comparison - i.Register2(engine.NewAtom("=:="), engine.Equal) - i.Register2(engine.NewAtom("=\\="), engine.NotEqual) - i.Register2(engine.NewAtom("<"), engine.LessThan) - i.Register2(engine.NewAtom("=<"), engine.LessThanOrEqual) - i.Register2(engine.NewAtom(">"), engine.GreaterThan) - i.Register2(engine.NewAtom(">="), engine.GreaterThanOrEqual) + i.SetPredicate2("equal", engine.Equal) + i.SetPredicate2("not_equal", engine.NotEqual) + i.SetPredicate2("less_than", engine.LessThan) + i.SetPredicate2("less_than_or_equal", engine.LessThanOrEqual) + i.SetPredicate2("greater_than", engine.GreaterThan) + i.SetPredicate2("greater_than_or_equal", engine.GreaterThanOrEqual) // Clause retrieval and information - i.Register2(engine.NewAtom("clause"), engine.Clause) - i.Register1(engine.NewAtom("current_predicate"), engine.CurrentPredicate) + i.SetPredicate2("clause", engine.Clause) + i.SetPredicate1("current_predicate", engine.CurrentPredicate) // Clause creation and destruction - i.Register1(engine.NewAtom("asserta"), engine.Asserta) - i.Register1(engine.NewAtom("assertz"), engine.Assertz) - i.Register1(engine.NewAtom("retract"), engine.Retract) - i.Register1(engine.NewAtom("abolish"), engine.Abolish) + i.SetPredicate1("asserta", engine.Asserta) + i.SetPredicate1("assertz", engine.Assertz) + i.SetPredicate1("retract", engine.Retract) + i.SetPredicate1("abolish", engine.Abolish) // All solutions - i.Register3(engine.NewAtom("findall"), engine.FindAll) - i.Register3(engine.NewAtom("bagof"), engine.BagOf) - i.Register3(engine.NewAtom("setof"), engine.SetOf) + i.SetPredicate3("findall", engine.FindAll) + i.SetPredicate3("bagof", engine.BagOf) + i.SetPredicate3("setof", engine.SetOf) // Stream selection and control - i.Register1(engine.NewAtom("current_input"), engine.CurrentInput) - i.Register1(engine.NewAtom("current_output"), engine.CurrentOutput) - i.Register1(engine.NewAtom("set_input"), engine.SetInput) - i.Register1(engine.NewAtom("set_output"), engine.SetOutput) - i.Register4(engine.NewAtom("open"), engine.Open) - i.Register2(engine.NewAtom("close"), engine.Close) - i.Register1(engine.NewAtom("flush_output"), engine.FlushOutput) - i.Register2(engine.NewAtom("stream_property"), engine.StreamProperty) - i.Register2(engine.NewAtom("set_stream_position"), engine.SetStreamPosition) + i.SetPredicate1("current_input", engine.CurrentInput) + i.SetPredicate1("current_output", engine.CurrentOutput) + i.SetPredicate1("set_input", engine.SetInput) + i.SetPredicate1("set_output", engine.SetOutput) + i.SetPredicate4("open", engine.Open) + i.SetPredicate2("close", engine.Close) + i.SetPredicate1("flush_output", engine.FlushOutput) + i.SetPredicate2("stream_property", engine.StreamProperty) + i.SetPredicate2("set_stream_position", engine.SetStreamPosition) // Character input/output - i.Register2(engine.NewAtom("get_char"), engine.GetChar) - i.Register2(engine.NewAtom("peek_char"), engine.PeekChar) - i.Register2(engine.NewAtom("put_char"), engine.PutChar) + i.SetPredicate2("get_char", engine.GetChar) + i.SetPredicate2("peek_char", engine.PeekChar) + i.SetPredicate2("put_char", engine.PutChar) // Byte input/output - i.Register2(engine.NewAtom("get_byte"), engine.GetByte) - i.Register2(engine.NewAtom("peek_byte"), engine.PeekByte) - i.Register2(engine.NewAtom("put_byte"), engine.PutByte) + i.SetPredicate2("get_byte", engine.GetByte) + i.SetPredicate2("peek_byte", engine.PeekByte) + i.SetPredicate2("put_byte", engine.PutByte) // Term input/output - i.Register3(engine.NewAtom("read_term"), engine.ReadTerm) - i.Register3(engine.NewAtom("write_term"), engine.WriteTerm) - i.Register3(engine.NewAtom("op"), engine.Op) - i.Register3(engine.NewAtom("current_op"), engine.CurrentOp) - i.Register2(engine.NewAtom("char_conversion"), engine.CharConversion) - i.Register2(engine.NewAtom("current_char_conversion"), engine.CurrentCharConversion) + i.SetPredicate3("read_term", engine.ReadTerm) + i.SetPredicate3("write_term", engine.WriteTerm) + i.SetPredicate3("op", engine.Op) + i.SetPredicate3("current_op", engine.CurrentOp) + i.SetPredicate2("char_conversion", engine.CharConversion) + i.SetPredicate2("current_char_conversion", engine.CurrentCharConversion) // Logic and control - i.Register1(engine.NewAtom(`\+`), engine.Negate) - i.Register0(engine.NewAtom("repeat"), engine.Repeat) - i.Register2(engine.NewAtom("call"), engine.Call1) - i.Register3(engine.NewAtom("call"), engine.Call2) - i.Register4(engine.NewAtom("call"), engine.Call3) - i.Register5(engine.NewAtom("call"), engine.Call4) - i.Register6(engine.NewAtom("call"), engine.Call5) - i.Register7(engine.NewAtom("call"), engine.Call6) - i.Register8(engine.NewAtom("call"), engine.Call7) + i.SetPredicate1(`not`, engine.Not) + i.SetPredicate0("repeat", engine.Repeat) + i.SetPredicate2("call", engine.Call1) + i.SetPredicate3("call", engine.Call2) + i.SetPredicate4("call", engine.Call3) + i.SetPredicate5("call", engine.Call4) + i.SetPredicate6("call", engine.Call5) + i.SetPredicate7("call", engine.Call6) + i.SetPredicate8("call", engine.Call7) // Atomic term processing - i.Register2(engine.NewAtom("atom_length"), engine.AtomLength) - i.Register3(engine.NewAtom("atom_concat"), engine.AtomConcat) - i.Register5(engine.NewAtom("sub_atom"), engine.SubAtom) - i.Register2(engine.NewAtom("atom_chars"), engine.AtomChars) - i.Register2(engine.NewAtom("atom_codes"), engine.AtomCodes) - i.Register2(engine.NewAtom("char_code"), engine.CharCode) - i.Register2(engine.NewAtom("number_chars"), engine.NumberChars) - i.Register2(engine.NewAtom("number_codes"), engine.NumberCodes) + i.SetPredicate2("atom_length", engine.AtomLength) + i.SetPredicate3("atom_concat", engine.AtomConcat) + i.SetPredicate5("sub_atom", engine.SubAtom) + i.SetPredicate2("atom_chars", engine.AtomChars) + i.SetPredicate2("atom_codes", engine.AtomCodes) + i.SetPredicate2("char_code", engine.CharCode) + i.SetPredicate2("number_chars", engine.NumberChars) + i.SetPredicate2("number_codes", engine.NumberCodes) // Implementation defined hooks - i.Register2(engine.NewAtom("set_prolog_flag"), engine.SetPrologFlag) - i.Register2(engine.NewAtom("current_prolog_flag"), engine.CurrentPrologFlag) - i.Register1(engine.NewAtom("halt"), engine.Halt) - - // Consult - i.Register1(engine.NewAtom("consult"), engine.Consult) + i.SetPredicate2("set_prolog_flag", engine.SetPrologFlag) + i.SetPredicate2("current_prolog_flag", engine.CurrentPrologFlag) + i.SetPredicate1("halt", engine.Halt) // Definite clause grammar - i.Register3(engine.NewAtom("phrase"), engine.Phrase) - i.Register2(engine.NewAtom("expand_term"), engine.ExpandTerm) - - // Prolog prologue - i.Register3(engine.NewAtom("append"), engine.Append) - i.Register2(engine.NewAtom("length"), engine.Length) - i.Register3(engine.NewAtom("between"), engine.Between) - i.Register2(engine.NewAtom("succ"), engine.Succ) - i.Register3(engine.NewAtom("nth0"), engine.Nth0) - i.Register3(engine.NewAtom("nth1"), engine.Nth1) - i.Register2(engine.NewAtom("call_nth"), engine.CallNth) + i.SetPredicate3("phrase", engine.Phrase) + + // A Prologue for Prolog + // https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue + i.SetPredicate2("length", engine.Length) + i.SetPredicate3("between", engine.Between) + i.SetPredicate2("succ", engine.Succ) + i.SetPredicate3("nth0", engine.Nth0) + i.SetPredicate3("nth1", engine.Nth1) + i.SetPredicate2("call_nth", engine.CallNth) + + // SICStus Prolog compatibility + i.SetPredicate2("module", engine.DefineModule) + i.SetPredicate1("meta_predicate", engine.MetaPredicate) + i.SetPredicate2("load_file", engine.LoadFile) + + if err := i.LoadFile(context.Background(), "library/prolog.pl"); err != nil { + panic(err) + } - _ = i.Exec(bootstrap) + i.SetModule("user") + i.SetSystemModule("prolog") return &i } -// Exec executes a prolog program. -func (i *Interpreter) Exec(query string, args ...interface{}) error { - return i.ExecContext(context.Background(), query, args...) -} - -// ExecContext executes a prolog program with context. -func (i *Interpreter) ExecContext(ctx context.Context, query string, args ...interface{}) error { - return i.Compile(ctx, query, args...) -} - // Query executes a prolog query and returns *Solutions. func (i *Interpreter) Query(query string, args ...interface{}) (*Solutions, error) { return i.QueryContext(context.Background(), query, args...) @@ -176,8 +181,8 @@ func (i *Interpreter) Query(query string, args ...interface{}) (*Solutions, erro // QueryContext executes a prolog query and returns *Solutions with context. func (i *Interpreter) QueryContext(ctx context.Context, query string, args ...interface{}) (*Solutions, error) { - p := engine.NewParser(&i.VM, strings.NewReader(query)) - if err := p.SetPlaceholder(engine.NewAtom("?"), args...); err != nil { + p := engine.NewParser(i.VM.TypeInModule, strings.NewReader(query)) + if err := p.SetPlaceholder("?", args...); err != nil { return nil, err } @@ -238,8 +243,70 @@ func (i *Interpreter) QuerySolutionContext(ctx context.Context, query string, ar return &Solution{sols: sols, err: sols.Close()} } -type defaultFS struct{} +// SetModule sets the type-in module. +func (i *Interpreter) SetModule(name string) { + n := engine.NewAtom(name) + i.VM.SetModule(n) +} + +// SetSystemModule sets the system module. +func (i *Interpreter) SetSystemModule(name string) { + n := engine.NewAtom(name) + i.VM.SetSystemModule(n) +} + +const moduleNameNative = "native" + +// SetPredicate0 registers a native predicate of arity 0. +func (i *Interpreter) SetPredicate0(name string, p engine.Predicate0) { + m := i.Module(moduleNameNative) + m.Register0(name, p) +} + +// SetPredicate1 registers a native predicate of arity 1. +func (i *Interpreter) SetPredicate1(name string, p engine.Predicate1) { + m := i.Module(moduleNameNative) + m.Register1(name, p) +} + +// SetPredicate2 registers a native predicate of arity 2. +func (i *Interpreter) SetPredicate2(name string, p engine.Predicate2) { + m := i.Module(moduleNameNative) + m.Register2(name, p) +} + +// SetPredicate3 registers a native predicate of arity 3. +func (i *Interpreter) SetPredicate3(name string, p engine.Predicate3) { + m := i.Module(moduleNameNative) + m.Register3(name, p) +} + +// SetPredicate4 registers a native predicate of arity 4. +func (i *Interpreter) SetPredicate4(name string, p engine.Predicate4) { + m := i.Module(moduleNameNative) + m.Register4(name, p) +} + +// SetPredicate5 registers a native predicate of arity 5. +func (i *Interpreter) SetPredicate5(name string, p engine.Predicate5) { + m := i.Module(moduleNameNative) + m.Register5(name, p) +} + +// SetPredicate6 registers a native predicate of arity 6. +func (i *Interpreter) SetPredicate6(name string, p engine.Predicate6) { + m := i.Module(moduleNameNative) + m.Register6(name, p) +} + +// SetPredicate7 registers a native predicate of arity 7. +func (i *Interpreter) SetPredicate7(name string, p engine.Predicate7) { + m := i.Module(moduleNameNative) + m.Register7(name, p) +} -func (d defaultFS) Open(name string) (fs.File, error) { - return os.Open(name) +// SetPredicate8 registers a native predicate of arity 8. +func (i *Interpreter) SetPredicate8(name string, p engine.Predicate8) { + m := i.Module(moduleNameNative) + m.Register8(name, p) } diff --git a/interpreter_test.go b/interpreter_test.go index 0784ba79..22713ad2 100644 --- a/interpreter_test.go +++ b/interpreter_test.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/ichiban/prolog/engine" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "io" "os" "regexp" @@ -83,7 +84,8 @@ func TestNew(t *testing.T) { t.Run("length", func(t *testing.T) { // http://www.complang.tuwien.ac.at/ulrich/iso-prolog/length_quad.pl - p := New(nil, nil) + p := New(nil, os.Stdout) + require.NoError(t, p.QuerySolution(`use_module(library(prologue)).`).Err()) var s struct { L []interface{} @@ -165,6 +167,7 @@ func TestNew(t *testing.T) { } p := New(nil, nil) + require.NoError(t, p.QuerySolution(`use_module(library(prologue)).`).Err()) assert.NoError(t, p.QuerySolution(`call_nth(true, Nth), Nth = 1.`).Err()) @@ -217,6 +220,7 @@ func TestNew_variableNames(t *testing.T) { var out bytes.Buffer p := New(nil, &out) + require.NoError(t, p.QuerySolution(`use_module(library(prologue)).`).Err()) defer func() { _ = os.Remove("f") // Some test cases open a file 'f'. @@ -710,10 +714,9 @@ a.`, output: `syntax err.`}, } } -func TestInterpreter_Exec(t *testing.T) { +func TestInterpreter_Compile(t *testing.T) { tests := []struct { query string - args []interface{} err bool premise string }{ @@ -721,9 +724,6 @@ func TestInterpreter_Exec(t *testing.T) { {query: `0.`, err: true}, {query: `append(cons(X, L1), L2, cons(X, L3)) :- append(L1, L2, L3).`}, - {query: `foo(?, ?, ?, ?).`, args: []interface{}{"a", 1, 2.0, []string{"abc", "def"}}}, - {query: `foo(?).`, args: []interface{}{nil}, err: true}, - {query: `#!/usr/bin/env 1pl append(nil, L, L).`}, {query: `#!/usr/bin/env 1pl`}, @@ -742,21 +742,27 @@ append(nil, L, L).`}, for _, tt := range tests { t.Run(tt.query, func(t *testing.T) { var i Interpreter - i.Register0(engine.NewAtom("true"), func(_ *engine.VM, k engine.Cont, env *engine.Env) *engine.Promise { + m := i.TypeInModule() + m.Register0("true", func(_ *engine.VM, k engine.Cont, env *engine.Env) *engine.Promise { return k(env) }) - i.Register0(engine.NewAtom("fail"), func(*engine.VM, engine.Cont, *engine.Env) *engine.Promise { + m.Register0("fail", func(*engine.VM, engine.Cont, *engine.Env) *engine.Promise { return engine.Bool(false) }) - i.Register1(engine.NewAtom("consult"), engine.Consult) - i.Register3(engine.NewAtom("op"), engine.Op) - assert.NoError(t, i.Exec(`:-(op(1200, xfx, :-)).`)) - assert.NoError(t, i.Exec(`:-(op(1200, fx, :-)).`)) - assert.NoError(t, i.Exec(tt.premise)) + m.Register3("op", engine.Op) + m.Register1("initialization", engine.Initialization) + _, err := i.Compile(context.Background(), `:-(op(1200, xfx, :-)).`) + assert.NoError(t, err) + _, err = i.Compile(context.Background(), `:-(op(1200, fx, :-)).`) + assert.NoError(t, err) + _, err = i.Compile(context.Background(), tt.premise) + assert.NoError(t, err) if tt.err { - assert.Error(t, i.Exec(tt.query, tt.args...)) + _, err = i.Compile(context.Background(), tt.query) + assert.Error(t, err) } else { - assert.NoError(t, i.Exec(tt.query, tt.args...)) + _, err = i.Compile(context.Background(), tt.query) + assert.NoError(t, err) } }) } @@ -798,9 +804,10 @@ func TestInterpreter_Query(t *testing.T) { for _, tt := range tests { t.Run(tt.query, func(t *testing.T) { var i Interpreter - i.Register3(engine.NewAtom("op"), engine.Op) - i.Register2(engine.NewAtom("set_prolog_flag"), engine.SetPrologFlag) - assert.NoError(t, i.Exec(` + m := i.TypeInModule() + m.Register3("op", engine.Op) + m.Register2("set_prolog_flag", engine.SetPrologFlag) + _, err := i.Compile(context.Background(), ` :-(op(1200, xfx, :-)). :-(set_prolog_flag(double_quotes, atom)). @@ -808,7 +815,8 @@ append([], L, L). append([X|L1], L2, [X|L3]) :- append(L1, L2, L3). foo(a, 1, 2.0, [abc, def]). -`)) +`) + assert.NoError(t, err) sols, err := i.Query(tt.query, tt.args...) if tt.queryErr { @@ -832,7 +840,8 @@ foo(a, 1, 2.0, [abc, def]). func TestInterpreter_Query_close(t *testing.T) { var i Interpreter - i.Register0(engine.NewAtom("do_not_call"), func(_ *engine.VM, k engine.Cont, env *engine.Env) *engine.Promise { + m := i.TypeInModule() + m.Register0("do_not_call", func(_ *engine.VM, k engine.Cont, env *engine.Env) *engine.Promise { assert.Fail(t, "unreachable") return k(env) }) @@ -854,11 +863,12 @@ func TestMisc(t *testing.T) { // https://www.cs.uleth.ca/~gaur/post/prolog-cut-negation/ t.Run("p", func(t *testing.T) { i := New(nil, nil) - assert.NoError(t, i.Exec(` + _, err := i.Compile(context.Background(), ` p(a). p(b):-!. p(c). -`)) +`) + assert.NoError(t, err) t.Run("single", func(t *testing.T) { sols, err := i.Query(`p(X).`) @@ -921,7 +931,7 @@ p(c). // http://www.cse.unsw.edu.au/~billw/dictionaries/prolog/cut.html t.Run("teaches", func(t *testing.T) { i := New(nil, nil) - assert.NoError(t, i.Exec(` + _, err := i.Compile(context.Background(), ` teaches(dr_fred, history). teaches(dr_fred, english). teaches(dr_fred, drama). @@ -930,7 +940,8 @@ studies(alice, english). studies(angus, english). studies(amelia, drama). studies(alex, physics). -`)) +`) + assert.NoError(t, err) t.Run("without cut", func(t *testing.T) { sols, err := i.Query(`teaches(dr_fred, Course), studies(Student, Course).`) @@ -1116,10 +1127,11 @@ studies(alex, physics). t.Run("call cut", func(t *testing.T) { i := New(nil, nil) - assert.NoError(t, i.Exec(` + _, err := i.Compile(context.Background(), ` foo :- call(true), !. foo :- throw(unreachable). -`)) +`) + assert.NoError(t, err) sols, err := i.Query("foo.") assert.NoError(t, err) assert.True(t, sols.Next()) @@ -1129,10 +1141,11 @@ foo :- throw(unreachable). t.Run("catch cut", func(t *testing.T) { i := New(nil, nil) - assert.NoError(t, i.Exec(` + _, err := i.Compile(context.Background(), ` foo :- catch(true, _, true), !. foo :- throw(unreachable). -`)) +`) + assert.NoError(t, err) sols, err := i.Query("foo.") assert.NoError(t, err) assert.True(t, sols.Next()) @@ -1142,12 +1155,13 @@ foo :- throw(unreachable). t.Run("counter", func(t *testing.T) { i := New(nil, nil) - assert.NoError(t, i.Exec(` + _, err := i.Compile(context.Background(), ` :- dynamic(count/1). count(0). next(N) :- retract(count(X)), N is X + 1, asserta(count(N)). -`)) +`) + assert.NoError(t, err) var s struct { X int @@ -1184,11 +1198,12 @@ next(N) :- retract(count(X)), N is X + 1, asserta(count(N)). func TestInterpreter_QuerySolution(t *testing.T) { var i Interpreter - assert.NoError(t, i.Exec(` + _, err := i.Compile(context.Background(), ` foo(a, b). foo(b, c). foo(c, d). -`)) +`) + assert.NoError(t, err) t.Run("ok", func(t *testing.T) { t.Run("struct", func(t *testing.T) { @@ -1229,7 +1244,8 @@ foo(c, d). t.Run("runtime error", func(t *testing.T) { err := errors.New("something went wrong") - i.Register0(engine.NewAtom("error"), func(_ *engine.VM, k engine.Cont, env *engine.Env) *engine.Promise { + m := i.TypeInModule() + m.Register0("error", func(_ *engine.VM, k engine.Cont, env *engine.Env) *engine.Promise { return engine.Error(err) }) sol := i.QuerySolution(`error.`) @@ -1240,50 +1256,9 @@ foo(c, d). }) } -func ExampleInterpreter_Exec_placeholders() { - p := New(nil, os.Stdout) - - _ = p.Exec(`my_string(?).`, "foo") - sols, _ := p.Query(`my_string(A), maplist(atom, A), write(A), nl.`) - sols.Next() - _ = sols.Close() - - _ = p.Exec(`my_int(?, ?, ?, ?, ?).`, int8(1), int16(1), int32(1), int64(1), 1) - sols, _ = p.Query(`my_int(I, I, I, I, I), integer(I), write(I), nl.`) - sols.Next() - _ = sols.Close() - - _ = p.Exec(`my_float(?, ?).`, float32(1), float64(1)) - sols, _ = p.Query(`my_float(F, F), float(F), write(F), nl.`) - sols.Next() - _ = sols.Close() - - _ = p.Exec(`my_atom_list(?).`, []string{"foo", "bar", "baz"}) - sols, _ = p.Query(`my_atom_list(As), maplist(maplist(atom), As), write(As), nl.`) - sols.Next() - _ = sols.Close() - - _ = p.Exec(`my_int_list(?).`, []int{1, 2, 3}) - sols, _ = p.Query(`my_int_list(Is), maplist(integer, Is), write(Is), nl.`) - sols.Next() - _ = sols.Close() - - _ = p.Exec(`my_float_list(?).`, []float64{1, 2, 3}) - sols, _ = p.Query(`my_float_list(Fs), maplist(float, Fs), write(Fs), nl.`) - sols.Next() - _ = sols.Close() - - // Output: - // [f,o,o] - // 1 - // 1.0 - // [[f,o,o],[b,a,r],[b,a,z]] - // [1,2,3] - // [1.0,2.0,3.0] -} - func ExampleInterpreter_Query_placeholders() { p := New(nil, os.Stdout) + _ = p.QuerySolution(`use_module(library(prologue)).`) sols, _ := p.Query(`A = ?, maplist(atom, A), write(A), nl.`, "foo") sols.Next() _ = sols.Close() @@ -1314,7 +1289,8 @@ func ExampleInterpreter_Query_placeholders() { func ExampleNew_phrase() { p := New(nil, nil) - _ = p.Exec(` + _ = p.QuerySolution(`use_module(library(dcg)).`) + _, _ = p.Compile(context.Background(), ` determiner --> [the]. determiner --> [a]. @@ -1599,13 +1575,6 @@ func ExampleNew_arg() { // error(type_error(compound,3),arg/3) } -func TestDefaultFS_Open(t *testing.T) { - var fs defaultFS - f, err := fs.Open("interpreter.go") - assert.NoError(t, err) - assert.NotNil(t, f) -} - type readFn func(p []byte) (n int, err error) func (f readFn) Read(p []byte) (n int, err error) { diff --git a/library/dcg.pl b/library/dcg.pl new file mode 100644 index 00000000..4b4a39e9 --- /dev/null +++ b/library/dcg.pl @@ -0,0 +1,10 @@ +:- module(dcg, [ + phrase/2, + phrase/3 +]). + +phrase(GRBody, S0, S) :- + native:phrase(GRBody, S0, S). + +phrase(GRBody, S0) :- + phrase(GRBody, S0, []). diff --git a/library/prolog.pl b/library/prolog.pl new file mode 100644 index 00000000..694e975f --- /dev/null +++ b/library/prolog.pl @@ -0,0 +1,436 @@ +:- native:module(prolog, [ + % 7.4.2 Directives + dynamic/1, + multifile/1, + discontiguous/1, + % op/3, + % char_conversion/2, + initialization/1, + include/1, + ensure_loaded/1, + % set_prolog_flag/2, + + % 7.8 Control constructs + true/0, + fail/0, + call/1, + !/0, + (',')/2, + (;)/2, + (->)/2, + catch/3, + throw/1, + + % 8.2 Term unification + (=)/2, + unify_with_occurs_check/2, + (\=)/2, + subsumes_term/2, + + % 8.3 Type testing + var/1, + atom/1, + integer/1, + float/1, + atomic/1, + compound/1, + nonvar/1, + number/1, + callable/1, + ground/1, + acyclic_term/1, + + % 8.4 Term comparison + (@=<)/2, + (==)/2, + (\==)/2, + (@<)/2, + (@>)/2, + (@>=)/2, + compare/3, + sort/2, + keysort/2, + + % 8.5 Term creation and decomposition + functor/3, + arg/3, + (=..)/2, + copy_term/2, + term_variables/2, + + % 8.6 Arithmetic evaluation + (is)/2, + + % 8.7 Arithmetic comparison + (=:=)/2, + (=\=)/2, + (<)/2, + (=<)/2, + (>)/2, + (>=)/2, + + % 8.8 Clause retrieval and information + clause/2, + current_predicate/1, + + % 8.9 Clause creation and destruction + asserta/1, + assertz/1, + retract/1, + abolish/1, + retractall/1, + + % 8.10 All solutions + findall/3, + bagof/3, + setof/3, + + % 8.11 Stream selection and control + current_input/1, + current_output/1, + set_input/1, + set_output/1, + open/4, + open/3, + close/2, + close/1, + flush_output/1, + flush_output/0, + stream_property/2, + at_end_of_stream/0, + at_end_of_stream/1, + set_stream_position/2, + + % 8.12 Character input/output + get_char/2, + get_char/1, + get_code/1, + get_code/2, + peek_char/2, + peek_char/1, + peek_code/1, + peek_code/2, + put_char/2, + put_char/1, + put_code/1, + put_code/2, + nl/0, + nl/1, + + % 8.13 Byte input/output + get_byte/2, + get_byte/1, + peek_byte/2, + peek_byte/1, + put_byte/2, + put_byte/1, + + % 8.14 Term input/output + read_term/3, + read_term/2, + read/1, + read/2, + write_term/3, + write_term/2, + write/1, + write/2, + writeq/1, + writeq/2, + write_canonical/1, + write_canonical/2, + op/3, + current_op/3, + char_conversion/2, + current_char_conversion/2, + + % 8.15 Logic and control + (\+)/1, + once/1, + repeat/0, + call/2, + call/3, + call/4, + call/5, + call/6, + call/7, + call/8, + false/0, + + % 8.16 Atomic term processing + atom_length/2, + atom_concat/3, + sub_atom/5, + atom_chars/2, + atom_codes/2, + char_code/2, + number_chars/2, + number_codes/2, + + % 8.17 Implementation defined hooks + set_prolog_flag/2, + current_prolog_flag/2, + halt/0, + halt/1, + + % Part 2: Modules + + % 7.2 Module predicates + current_module/1, + predicate_property/2, + + % SICStus Prolog compatibility + + module/2, + module/3, + use_module/1, + use_module/2, + meta_predicate/1, + consult/1 +]). + +% 7.4.2 Directives +dynamic(PI) :- native:dynamic(PI). +multifile(PI) :- native:multifile(PI). +discontiguous(PI) :- native:discontiguous(PI). +initialization(T) :- native:initialization(T). +include(F) :- native:include(F). +ensure_loaded(P_text) :- + load_files(P_text, [if(changed)]). + +load_files([], _). +load_files([File|Files], Options) :- + load_files(File, Options), + load_files(Files, Options). +load_files(File, Options) :- + load_file(File, Options). + +load_file(FileSpec, Options) :- + file_spec_path(FileSpec, Path), + native:load_file(Path, Options). + +file_spec_path(FileSpec, FileSpec) :- atom(FileSpec), !. +file_spec_path(FileSpec, Path) :- % Converts foo(bar) to 'foo/bar' for compatibility. + FileSpec =.. [PathAlias, DirSpec], + atom(PathAlias), + catch(file_spec_path(DirSpec, Path1), _, false), !, + atom_concat(PathAlias, /, X), + atom_concat(X, Path1, Path). +file_spec_path(FileSpec1/FileSpec2, Path) :- % Converts foo/bar to 'foo/bar' + catch(file_spec_path(FileSpec1, Path1), _, false), + atom(FileSpec2), !, + atom_concat(Path1, /, X), + atom_concat(X, FileSpec2, Path). +file_spec_path(FileSpec, _) :- + throw(error(domain_error(file_spec, FileSpec), file_spec_path/2)). + +% 7.8 Control constructs +true. +fail :- \+true. +call(G) :- native:call(G). +! :- !. +P, Q :- call((P, Q)). +If -> Then; _ :- If, !, Then. +_ -> _; Else :- !, Else. +P; Q :- call((P; Q)). +If -> Then :- If, !, Then. +catch(Goal, Catcher, Recovery) :- native:catch(Goal, Catcher, Recovery). +throw(B) :- native:throw(B). + +% 8.2 Term unification +X = X. +unify_with_occurs_check(X, Y) :- native:unify_with_occurs_check(X, Y). +X \= Y :- \+(X = Y). +subsumes_term(General, Specific) :- native:subsumes_term(General, Specific). + +% 8.3 Type testing +var(X) :- native:var(X). +atom(X) :- native:atom(X). +integer(X) :- native:integer(X). +float(X) :- native:float(X). +atomic(X) :- nonvar(X), \+compound(X). +compound(X) :- native:compound(X). +nonvar(X) :- \+var(X). +number(X) :- float(X). +number(X) :- integer(X). +callable(X) :- atom(X). +callable(X) :- compound(X). +ground(X) :- term_variables(X, []). +acyclic_term(X) :- native:acyclic_term(X). + +% 8.4 Term comparison +X @=< Y :- compare(=, X, Y). +X @=< Y :- compare(<, X, Y). +X == Y :- compare(=, X, Y). +X \== Y :- \+(X == Y). +X @< Y :- compare(<, X, Y). +X @> Y :- compare(>, X, Y). +X @>= Y :- compare(>, X, Y). +X @>= Y :- compare(=, X, Y). +compare(Order, X, Y) :- native:compare(Order, X, Y). +sort(List, Sorted) :- native:sort(List, Sorted). +keysort(Pairs, Sorted) :- native:keysort(Pairs, Sorted). + +% 8.5 Term creation and decomposition +functor(Term, Name, Arity) :- native:functor(Term, Name, Arity). +arg(N, Term, Arg) :- native:arg(N, Term, Arg). +Term =.. List :- native:univ(Term, List). +copy_term(Term_1, Term_2) :- native:copy_term(Term_1, Term_2). +term_variables(Term, Vars) :- native:term_variables(Term, Vars). + +% 8.6 Arithmetic evaluation +Result is Expression :- native:is(Result, Expression). + +% 8.7 Arithmetic comparison +E1 =:= E2 :- native:equal(E1, E2). +E1 =\= E2 :- native:not_equal(E1, E2). +E1 < E2 :- native:less_than(E1, E2). +E1 =< E2 :- native:less_than_or_equal(E1, E2). +E1 > E2 :- native:greater_than(E1, E2). +E1 >= E2 :- native:greater_than_or_equal(E1, E2). + +% 8.8 Clause retrieval and information +:- native:meta_predicate(clause(0, ?)). +clause(Head, Body) :- native:clause(Head, Body). +current_predicate(PI) :- native:current_predicate(PI). + +% 8.9 Clause creation and destruction +:- native:meta_predicate(asserta(0)). +asserta(Clause) :- native:asserta(Clause). +:- native:meta_predicate(assertz(0)). +assertz(Clause) :- native:assertz(Clause). +:- native:meta_predicate(retract(0)). +retract(Clause) :- native:retract(Clause). +:- native:meta_predicate(abolish(0)). +abolish(Pred) :- native:abolish(Pred). +retractall(Head) :- + retract((Head :- _)), + fail. +retractall(_). + +% 8.10 All solutions +:- native:meta_predicate(findall(+, 0, -)). +findall(Template, Goal, Instances) :- native:findall(Template, Goal, Instances). +:- native:meta_predicate(bagof(+, 0, -)). +bagof(Template, Goal, Instances) :- native:bagof(Template, Goal, Instances). +:- native:meta_predicate(setof(+, 0, -)). +setof(Template, Goal, Instances) :- native:setof(Template, Goal, Instances). + +% 8.11 Stream selection and control +current_input(Stream) :- native:current_input(Stream). +current_output(Stream) :- native:current_output(Stream). +set_input(S_or_a) :- native:set_input(S_or_a). +set_output(S_or_a) :- native:set_output(S_or_a). +open(Source_sink, Mode, Stream, Options) :- native:open(Source_sink, Mode, Stream, Options). +open(Source_sink, Mode, Stream) :- open(Source_sink, Mode, Stream, []). +close(S_or_a, Options) :- native:close(S_or_a, Options). +close(S_or_a) :- close(S_or_a, []). +flush_output(S_or_a) :- native:flush_output(S_or_a). +flush_output :- current_output(S), flush_output(S). +stream_property(Stream, Property) :- native:stream_property(Stream, Property). +at_end_of_stream :- current_input(S), at_end_of_stream(S). +at_end_of_stream(S_or_a) :- + (atom(S_or_a) -> stream_property(S, alias(S_or_a)); S = S_or_a), + stream_property(S, end_of_stream(E)), !, + (E = at; E = past). +set_stream_position(S_or_a, Position) :- native:set_stream_position(S_or_a, Position). + +% 8.12 Character input/output +get_char(S_or_a, Char) :- native:get_char(S_or_a, Char). +get_char(Char) :- current_input(S), get_char(S, Char). +get_code(Code) :- current_input(S), get_code(S, Code). +get_code(S_or_a, Code) :- native:get_code(S_or_a, Code). +peek_char(S_or_a, Char) :- native:peek_char(S_or_a, Char). +peek_char(Char) :- current_input(S), peek_char(S, Char). +peek_code(Code) :- current_input(S), peek_code(S, Code). +peek_code(S_or_a, Code) :- peek_char(S_or_a, Char), (Char = end_of_file -> Code = -1; char_code(Char, Code)). +put_char(S_or_a, Char) :- native:put_char(S_or_a, Char). +put_char(Char) :- current_output(S), put_char(S, Char). +put_code(Code) :- current_output(S), put_code(S, Code). +put_code(S_or_a, Code) :- char_code(Char, Code), put_char(S, Char). +nl :- current_output(S), nl(S). +nl(S_or_a) :- put_char(S_or_a, '\n'). + +% 8.13 Byte input/output +get_byte(S_or_a, Byte) :- native:get_byte(S_or_a, Byte). +get_byte(Byte) :- current_input(S), peek_byte(S, Byte). +peek_byte(S_or_a, Byte) :- native:peek_byte(S_or_a, Byte). +peek_byte(Byte) :- current_input(S), peek_byte(S, Byte). +put_byte(S_or_a, Byte) :- native:put_byte(S_or_a, Byte). +put_byte(Byte) :- current_output(S), put_byte(S, Byte). + +% 8.14 Term input/output +read_term(S_or_a, Term, Options) :- native:read_term(S_or_a, Term, Options). +read_term(Term, Options) :- current_input(S), read_term(S, Term, Options). +read(Term) :- current_input(S), read(S, Term). +read(S_or_a, Term) :- read_term(S_or_a, Term, []). +write_term(S_or_a, Term, Options) :- native:write_term(S_or_a, Term, Options). +write_term(Term, Options) :- current_output(S), write_term(S, Term, Options). +write(Term) :- current_output(S), write(S, Term). +write(S_or_a, Term) :- write_term(S_or_a, Term, [numbervars(true)]). +writeq(Term) :- current_output(S), writeq(S, Term). +writeq(S_or_a, Term) :- write_term(S_or_a, Term, [quoted(true), numbervars(true)]). +write_canonical(Term) :- current_output(S), write_canonical(S, Term). +write_canonical(S_or_a, Term) :- write_term(S_or_a, Term, [quoted(true), ignore_ops(true)]). +op(Priority, Op_specifier, Operator) :- native:op(Priority, Op_specifier, Operator). +current_op(Priority, Op_specifier, Operator) :- native:current_op(Priority, Op_specifier, Operator). +char_conversion(In_char, Out_char) :- native:char_conversion(In_char, Out_char). +current_char_conversion(In_char, Out_char) :- native:current_char_conversion(In_char, Out_char). + +% 8.15 Logic and control +:- native:meta_predicate(\+ 0). +\+Term :- native:not(Term). +:- native:meta_predicate(once(0)). +once(Term) :- Term, !. +repeat :- native:repeat. +:- native:meta_predicate(call(1, ?)). +call(Closure, Arg1) :- native:call(Closure, Arg1). +:- native:meta_predicate(call(2, ?, ?)). +call(Closure, Arg1, Arg2) :- native:call(Closure, Arg1, Arg2). +:- native:meta_predicate(call(3, ?, ?, ?)). +call(Closure, Arg1, Arg2, Arg3) :- native:call(Closure, Arg1, Arg2, Arg3). +:- native:meta_predicate(call(4, ?, ?, ?, ?)). +call(Closure, Arg1, Arg2, Arg3, Arg4) :- native:call(Closure, Arg1, Arg2, Arg3, Arg4). +:- native:meta_predicate(call(5, ?, ?, ?, ?, ?)). +call(Closure, Arg1, Arg2, Arg3, Arg4, Arg5) :- native:call(Closure, Arg1, Arg2, Arg3, Arg4, Arg5). +:- native:meta_predicate(call(6, ?, ?, ?, ?, ?, ?)). +call(Closure, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) :- native:call(Closure, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6). +:- native:meta_predicate(call(7, ?, ?, ?, ?, ?, ?, ?)). +call(Closure, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) :- native:call(Closure, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7). +false :- fail. + +% 8.16 Atomic term processing +atom_length(Atom, Length) :- native:atom_length(Atom, Length). +atom_concat(Atom_1, Atom_2, Atom_3) :- native:atom_concat(Atom_1, Atom_2, Atom_3). +sub_atom(Atom, Before, Length, After, Sub_atom) :- native:sub_atom(Atom, Before, Length, After, Sub_atom). +atom_chars(Atom, List) :- native:atom_chars(Atom, List). +atom_codes(Atom, List) :- native:atom_codes(Atom, List). +char_code(Char, Code) :- native:char_code(Char, Code). +number_chars(Number, List) :- native:number_chars(Number, List). +number_codes(Number, List) :- native:number_codes(Number, List). + +% 8.17 Implementation defined hooks +set_prolog_flag(Flag, Value) :- native:set_prolog_flag(Flag, Value). +current_prolog_flag(Flag, Value) :- native:current_prolog_flag(Flag, Value). +halt :- halt(0). +halt(X) :- native:halt(X). + +% SICStus Prolog compatibility + +module(ModuleName, ExportList) :- + native:module(ModuleName, ExportList). + +current_module(Module) :- native:current_module(Module). +:- native:meta_predicate(predicate_property(0, ?)). +predicate_property(Prototype, Property) :- native:predicate_property(Prototype, Property). +meta_predicate(MI) :- native:meta_predicate(MI). + +use_module(File) :- + load_files([File], [if(changed)]). +use_module(File, ImportList) :- + load_files([File], [if(changed), imports(ImportList)]). + +consult(Files) :- + load_files(Files, []). + +[]. +[F|Fs] :- consult([F|Fs]). diff --git a/library/prologue.pl b/library/prologue.pl new file mode 100644 index 00000000..d31f48a4 --- /dev/null +++ b/library/prologue.pl @@ -0,0 +1,89 @@ +:- module(prologue, [ + member/2, + append/3, + length/2, + select/3, + succ/2, + maplist/2, + maplist/3, + maplist/4, + maplist/5, + maplist/6, + maplist/7, + maplist/8, + nth0/3, + nth1/3, + call_nth/2 +]). + +member(X, [X|_]). +member(X, [_|Xs]) :- + member(X, Xs). + +append([], Zs, Zs). +append([X|Xs], Ys, [X|Zs]) :- + append(Xs, Ys, Zs). + +length(List, Length) :- + native:length(List, Length). + +between(Lower, Upper, X) :- + native:between(Lower, Upper, X). + +select(E, [E|Xs], Xs). +select(E, [X|Xs], [X|Ys]) :- + select(E, Xs, Ys). + +succ(X, S) :- + native:succ(X, S). + +:- meta_predicate(maplist(1, ?)). +maplist(_Cont_1, []). +maplist(Cont_1, [E1|E1s]) :- + call(Cont_1, E1), + maplist(Cont_1, E1s). + +:- meta_predicate(maplist(2, ?, ?)). +maplist(_Cont_2, [], []). +maplist(Cont_2, [E1|E1s], [E2|E2s]) :- + call(Cont_2, E1, E2), + maplist(Cont_2, E1s, E2s). + +:- meta_predicate(maplist(3, ?, ?, ?)). +maplist(_Cont_3, [], [], []). +maplist(Cont_3, [E1|E1s], [E2|E2s], [E3|E3s]) :- + call(Cont_3, E1, E2, E3), + maplist(Cont_3, E1s, E2s, E3s). + +:- meta_predicate(maplist(4, ?, ?, ?, ?)). +maplist(_Cont_4, [], [], [], []). +maplist(Cont_4, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s]) :- + call(Cont_4, E1, E2, E3, E4), + maplist(Cont_4, E1s, E2s, E3s, E4s). + +:- meta_predicate(maplist(5, ?, ?, ?, ?, ?)). +maplist(_Cont_5, [], [], [], [], []). +maplist(Cont_5, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s]) :- + call(Cont_5, E1, E2, E3, E4, E5), + maplist(Cont_5, E1s, E2s, E3s, E4s, E5s). + +:- meta_predicate(maplist(6, ?, ?, ?, ?, ?, ?)). +maplist(_Cont_6, [], [], [], [], [], []). +maplist(Cont_6, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s]) :- + call(Cont_6, E1, E2, E3, E4, E5, E6), + maplist(Cont_6, E1s, E2s, E3s, E4s, E5s, E6s). + +:- meta_predicate(maplist(7, ?, ?, ?, ?, ?, ?, ?)). +maplist(_Cont_7, [], [], [], [], [], [], []). +maplist(Cont_7, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s]) :- + call(Cont_7, E1, E2, E3, E4, E5, E6, E7), + maplist(Cont_7, E1s, E2s, E3s, E4s, E5s, E6s, E7s). + +nth0(N, List, Elem) :- + native:nth0(N, List, Elem). + +nth1(N, List, Elem) :- + native:nth1(N, List, Elem). + +call_nth(Goal, Nth) :- + native:call_nth(Goal, Nth).