diff --git a/bootstrap.pl b/bootstrap.pl index a089cea8..52d61d17 100644 --- a/bootstrap.pl +++ b/bootstrap.pl @@ -2,6 +2,187 @@ * bootstrap script */ +:-(module(prolog, [ + /(true, 0), + /(fail, 0), + /(call, 1), + /(!, 0), + /((','), 2), + /((;), 2), + /((->), 2), + /(catch, 3), + /(throw, 1), + + /((=), 2), + /(unify_with_occurs_check, 2), + /((\=), 2), + /(subsumes_term, 2), + + /(var, 1), + /(atom, 1), + /(integer, 1), + /(float, 1), + /(atomic, 1), + /(compound, 1), + /(nonvar, 1), + /(number, 1), + /(callable, 1), + /(ground, 1), + /(acyclic_term, 1), + + /((@=<), 2), + /((==), 2), + /((\==), 2), + /((@<), 2), + /((@>), 2), + /((@>=), 2), + /(compare, 3), + /(sort, 2), + /(keysort, 2), + + /(functor, 3), + /(arg, 3), + /((=..), 2), + /(copy_term, 2), + /(term_variables, 2), + + /((is), 2), + + /((=:=), 2), + /((=\=), 2), + /((<), 2), + /((=<), 2), + /((>), 2), + /((>=), 2), + + /(clause, 2), + /(current_predicate, 1), + + /(asserta, 1), + /(assertz, 1), + /(retract, 1), + /(abolish, 1), + /(retractall, 1), + + /(findall, 3), + /(bagof, 3), + /(setof, 3), + + /(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), + + /(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), + + /(get_byte, 2), + /(get_byte, 1), + /(peek_byte, 2), + /(peek_byte, 1), + /(put_byte, 2), + /(put_byte, 1), + + /(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), + + /((\+), 1), + /(once, 1), + /(repeat, 0), + /(call, 2), + /(call, 3), + /(call, 4), + /(call, 5), + /(call, 6), + /(call, 7), + /(call, 8), + /(false, 0), + + /(atom_length, 2), + /(atom_concat, 3), + /(sub_atom, 5), + /(atom_chars, 2), + /(atom_codes, 2), + /(char_code, 2), + /(number_chars, 2), + /(number_codes, 2), + + /(set_prolog_flag, 2), + /(current_prolog_flag, 2), + /(halt, 0), + /(halt, 1), + + /(consult, 1), + /((.), 2), + + /(phrase, 2), + /(phrase, 3), + /(expand_term, 2), + + /(member, 2), + /(append, 3), + /(length, 2), + /(between, 3), + /(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), + + /(current_module, 1), + /(predicate_property, 2), + /(current_predicate, 1), + /(use_module, 2), + /(use_module, 1) +])). + +% Dependencies + +:-(use_module('/prolog/system', all)). + % Operators :-(op(1200, xfx, [:-, -->])). @@ -22,6 +203,22 @@ :-(op(200, xfy, ^)). :-(op(200, fy, [+, -, \])). +% Meta predicates + +:- meta_predicate([ + clause(0, ?), + asserta(0), + assertz(0), + retract(0), + abolish(0), + predicate_property(0, ?), + once(0), + \+ 0, + setof(+, 0, -), + bagof(+, 0, -), + findall(+, 0, -) +]). + % Control constructs true. @@ -163,36 +360,48 @@ % Term input/output read_term(Term, Options) :- + calling_context(M), current_input(S), - read_term(S, Term, Options). + M:read_term(S, Term, Options). read(Term) :- + calling_context(M), current_input(S), - read(S, Term). + M:read(S, Term). -read(Stream, Term) :- read_term(Stream, Term, []). +read(Stream, Term) :- + calling_context(M), + M:read_term(Stream, Term, []). write_term(Term, Options) :- + calling_context(M), current_output(S), - write_term(S, Term, Options). + M:write_term(S, Term, Options). write(Term) :- + calling_context(M), current_output(S), - write(S, Term). + M:write(S, Term). -write(Stream, Term) :- write_term(Stream, Term, [numbervars(true)]). +write(Stream, Term) :- + calling_context(M), + M:write_term(Stream, Term, [numbervars(true)]). writeq(Term) :- + calling_context(M), current_output(S), - writeq(S, Term). + M:writeq(S, Term). -writeq(Stream, Term) :- write_term(Stream, Term, [quoted(true), numbervars(true)]). +writeq(Stream, Term) :- + calling_context(M), + M: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)]). +write_canonical(Stream, Term) :- + write_term(Stream, Term, [quoted(true), ignore_ops(true)]). % Logic and control @@ -212,7 +421,9 @@ % Definite clause grammar -phrase(GRBody, S0) :- phrase(GRBody, S0, []). +phrase(GRBody, S0) :- + calling_context(M), + M:phrase(GRBody, S0, []). % Prolog prologue @@ -257,3 +468,11 @@ 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). + +use_module(File, Imports) :- + calling_context(M), + M:use_module(_, File, Imports). + +use_module(File) :- + calling_context(M), + M:use_module(File, all). diff --git a/engine/atom.go b/engine/atom.go index d856ffdf..08ab91a3 100644 --- a/engine/atom.go +++ b/engine/atom.go @@ -42,6 +42,7 @@ var ( atomComma = NewAtom(",") atomBar = NewAtom("|") atomCut = NewAtom("!") + atomColon = NewAtom(":") atomSemiColon = NewAtom(";") atomNegation = NewAtom(`\+`) atomThen = NewAtom("->") @@ -58,6 +59,7 @@ var ( atomAccess = NewAtom("access") atomAcos = NewAtom("acos") atomAlias = NewAtom("alias") + atomAll = NewAtom("all") atomAppend = NewAtom("append") atomAsin = NewAtom("asin") atomAt = NewAtom("at") @@ -68,6 +70,7 @@ var ( atomBinary = NewAtom("binary") atomBinaryStream = NewAtom("binary_stream") atomBounded = NewAtom("bounded") + atomBuiltIn = NewAtom("built_in") atomByte = NewAtom("byte") atomCall = NewAtom("call") atomCallable = NewAtom("callable") @@ -83,6 +86,7 @@ var ( atomCos = NewAtom("cos") atomCreate = NewAtom("create") atomDebug = NewAtom("debug") + atomDefinedIn = NewAtom("defined_in") atomDiscontiguous = NewAtom("discontiguous") atomDiv = NewAtom("div") atomDomainError = NewAtom("domain_error") @@ -99,6 +103,7 @@ var ( atomEvaluationError = NewAtom("evaluation_error") atomExistenceError = NewAtom("existence_error") atomExp = NewAtom("exp") + atomExported = NewAtom("exported") atomFX = NewAtom("fx") atomFY = NewAtom("fy") atomFail = NewAtom("fail") @@ -113,6 +118,7 @@ var ( atomFloatOverflow = NewAtom("float_overflow") atomFloor = NewAtom("floor") atomForce = NewAtom("force") + atomImportedFrom = NewAtom("imported_from") atomIOMode = NewAtom("io_mode") atomIgnoreOps = NewAtom("ignore_ops") atomInByte = NewAtom("in_byte") @@ -132,11 +138,13 @@ var ( atomMaxDepth = NewAtom("max_depth") atomMaxInteger = NewAtom("max_integer") atomMemory = NewAtom("memory") + atomMetaPredicate = NewAtom("meta_predicate") atomMin = NewAtom("min") atomMinInteger = NewAtom("min_integer") atomMod = NewAtom("mod") atomMode = NewAtom("mode") atomModify = NewAtom("modify") + atomModule = NewAtom("module") atomMultifile = NewAtom("multifile") atomNonEmptyList = NewAtom("non_empty_list") atomNot = NewAtom("not") @@ -145,6 +153,7 @@ var ( atomNumberVars = NewAtom("numbervars") atomOff = NewAtom("off") atomOn = NewAtom("on") + atomOp = NewAtom("op") atomOpen = NewAtom("open") atomOperator = NewAtom("operator") atomOperatorPriority = NewAtom("operator_priority") @@ -159,9 +168,12 @@ var ( atomPi = NewAtom("pi") atomPosition = NewAtom("position") atomPredicateIndicator = NewAtom("predicate_indicator") + atomPrivate = NewAtom("private") atomPrivateProcedure = NewAtom("private_procedure") atomProcedure = NewAtom("procedure") + atomProlog = NewAtom("prolog") atomPrologFlag = NewAtom("prolog_flag") + atomPublic = NewAtom("public") atomQuoted = NewAtom("quoted") atomRead = NewAtom("read") atomReadOption = NewAtom("read_option") @@ -177,6 +189,7 @@ var ( atomSmallE = NewAtom("e") atomSourceSink = NewAtom("source_sink") atomSqrt = NewAtom("sqrt") + atomStatic = NewAtom("static") atomStaticProcedure = NewAtom("static_procedure") atomStream = NewAtom("stream") atomStreamOption = NewAtom("stream_option") @@ -197,6 +210,8 @@ var ( atomUndefined = NewAtom("undefined") atomUnderflow = NewAtom("underflow") atomUnknown = NewAtom("unknown") + atomUseModule = NewAtom("use_module") + atomUser = NewAtom("user") atomUserInput = NewAtom("user_input") atomUserOutput = NewAtom("user_output") atomVar = NewAtom("$VAR") diff --git a/engine/builtin.go b/engine/builtin.go index e707963e..742cacea 100644 --- a/engine/builtin.go +++ b/engine/builtin.go @@ -13,6 +13,11 @@ import ( "unicode/utf8" ) +func callingModule(env *Env) Atom { + pi, _ := env.Resolve(varContext).(procedureIndicator) + return pi.module +} + // Repeat repeats the continuation until it succeeds. func Repeat(_ *VM, k Cont, env *Env) *Promise { return repeat(func(ctx context.Context) *Promise { @@ -37,26 +42,26 @@ func Negate(vm *VM, goal Term, k Cont, env *Env) *Promise { // Call executes goal. it succeeds if goal followed by k succeeds. A cut inside goal doesn't affect outside of Call. func Call(vm *VM, goal Term, k Cont, env *Env) (promise *Promise) { defer ensurePromise(&promise) - switch g := env.Resolve(goal).(type) { - case Variable: - return Error(InstantiationError(env)) - default: - fvs := env.freeVariables(g) - args, err := makeSlice(len(fvs)) - if err != nil { - return Error(resourceError(resourceMemory, env)) - } - for i, fv := range fvs { - args[i] = fv - } - cs, err := compile(atomIf.Apply(tuple(args...), g), env) - if err != nil { - return Error(err) - } - u := userDefined{clauses: cs} - return u.call(vm, args, k, env) + module, goal, err := moduleTerm(callingModule(env), goal, env) + if err != nil { + return Error(err) } + + fvs := env.freeVariables(goal) + args, err := makeSlice(len(fvs)) + if err != nil { + return Error(resourceError(resourceMemory, env)) + } + for i, fv := range fvs { + args[i] = fv + } + cs, err := compile(module, atomIf.Apply(tuple(args...), goal), env) + if err != nil { + return Error(err) + } + + return cs.call(vm, args, k, env) } // Call1 succeeds if closure with an additional argument succeeds. @@ -507,6 +512,7 @@ var operatorSpecifiers = map[Atom]operatorSpecifier{ // Op defines operator with priority and specifier, or removes when priority is 0. func Op(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise { + module := callingModule(env) var p Integer switch priority := env.Resolve(priority).(type) { case Variable: @@ -554,32 +560,36 @@ func Op(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise { } for _, name := range names { - if p := validateOp(vm, p, spec, name, env); p != nil { + if p := validateOp(vm, module, 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) + l, _ := vm.moduleLocals[module] + if class := spec.class(); l.operators.definedInClass(name, class) { + l.operators.remove(name, class) } - vm.operators.define(p, spec, name) + l.operators.define(p, spec, name) + vm.moduleLocals[module] = l } return k(env) } -func validateOp(vm *VM, p Integer, spec operatorSpecifier, name Atom, env *Env) *Promise { +func validateOp(vm *VM, module Atom, p Integer, spec operatorSpecifier, name Atom, env *Env) *Promise { switch name { case atomComma: - if vm.operators.definedInClass(name, operatorClassInfix) { + l, _ := vm.moduleLocals[module] + if l.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) { + l, _ := vm.moduleLocals[module] + if l.operators.definedInClass(name, operatorClassInfix) { op = operationModify } return Error(permissionError(op, permissionTypeOperator, name, env)) @@ -591,11 +601,13 @@ 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) { + l, _ := vm.moduleLocals[module] + if l.operators.definedInClass(name, operatorClassPostfix) { return Error(permissionError(operationCreate, permissionTypeOperator, name, env)) } case operatorClassPostfix: - if vm.operators.definedInClass(name, operatorClassInfix) { + l, _ := vm.moduleLocals[module] + if l.operators.definedInClass(name, operatorClassInfix) { return Error(permissionError(operationCreate, permissionTypeOperator, name, env)) } } @@ -614,6 +626,8 @@ func appendUniqNewAtom(slice []Atom, elem Atom) []Atom { // CurrentOp succeeds if operator is defined with priority and specifier. func CurrentOp(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise { + module := callingModule(env) + switch p := env.Resolve(priority).(type) { case Variable: break @@ -652,8 +666,9 @@ func CurrentOp(vm *VM, priority, specifier, op Term, k Cont, env *Env) *Promise } pattern := tuple(priority, specifier, op) - ks := make([]func(context.Context) *Promise, 0, len(vm.operators)*int(_operatorClassLen)) - for _, ops := range vm.operators { + l, _ := vm.moduleLocals[module] + ks := make([]func(context.Context) *Promise, 0, len(l.operators)*int(_operatorClassLen)) + for _, ops := range l.operators { for _, op := range ops { op := op if op == (operator{}) { @@ -688,7 +703,13 @@ func Asserta(vm *VM, t Term, k Cont, env *Env) *Promise { } func assertMerge(vm *VM, t Term, merge func([]clause, []clause) []clause, env *Env) error { - pi, arg, err := piArg(t, env) + module := callingModule(env) + cm, c, err := moduleTerm(module, t, env) + if err != nil { + return err + } + + pi, arg, err := piArg(c, env) if err != nil { return err } @@ -700,26 +721,28 @@ func assertMerge(vm *VM, t Term, merge func([]clause, []clause) []clause, env *E } } + pi.module = cm + if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - p, ok := vm.procedures[pi] + e, ok := vm.procedures[pi] if !ok { - p = &userDefined{dynamic: true} - vm.procedures[pi] = p + e = procedureEntry{dynamic: true, definedIn: module, procedure: clauses{}} + vm.procedures[pi] = e } - added, err := compile(t, env) + added, err := compile(cm, t, env) if err != nil { return err } - u, ok := p.(*userDefined) - if !ok || !u.dynamic { + if !e.dynamic { return permissionError(operationModify, permissionTypeStaticProcedure, pi.Term(), env) } - u.clauses = merge(u.clauses, added) + e.procedure = clauses(merge(e.procedure.(clauses), added)) + vm.procedures[pi] = e return nil } @@ -1049,6 +1072,7 @@ func Catch(vm *VM, goal, catcher, recover Term, k Cont, env *Env) *Promise { // CurrentPredicate matches pi with a predicate indicator of the user-defined procedures in the database. func CurrentPredicate(vm *VM, pi Term, k Cont, env *Env) *Promise { + module := callingModule(env) switch pi := env.Resolve(pi).(type) { case Variable: break @@ -1067,9 +1091,12 @@ func CurrentPredicate(vm *VM, pi Term, k Cont, env *Env) *Promise { } ks := make([]func(context.Context) *Promise, 0, len(vm.procedures)) - for key, p := range vm.procedures { - switch p.(type) { - case *userDefined: + for key, e := range vm.procedures { + if key.module != module { + continue + } + switch e.procedure.(type) { + case clauses: c := key.Term() ks = append(ks, func(context.Context) *Promise { return Unify(vm, pi, c, k, env) @@ -1083,7 +1110,13 @@ 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 { - t = rulify(t, env) + module := callingModule(env) + dm, c, err := moduleTerm(module, t, env) + if err != nil { + return Error(err) + } + + t = rulify(c, env) h := t.(Compound).Arg(0) pi, _, err := piArg(h, env) @@ -1091,25 +1124,30 @@ func Retract(vm *VM, t Term, k Cont, env *Env) *Promise { return Error(err) } - p, ok := vm.procedures[pi] + pi.module = dm + + e, ok := vm.procedures[pi] if !ok { return Bool(false) } - u, ok := p.(*userDefined) - if !ok || !u.dynamic { + if !e.dynamic { return Error(permissionError(operationModify, permissionTypeStaticProcedure, pi.Term(), env)) } + cs := e.procedure.(clauses) + 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 + vm.procedures[pi] = e deleted++ return k(env) }, env) @@ -1120,42 +1158,52 @@ func Retract(vm *VM, t Term, k Cont, env *Env) *Promise { // Abolish removes the procedure indicated by pi from the database. func Abolish(vm *VM, pi Term, k Cont, env *Env) *Promise { + module := callingModule(env) + dm, pi, err := moduleTerm(module, pi, env) + if err != nil { + return Error(err) + } + + var name, arity Term switch pi := env.Resolve(pi).(type) { - case Variable: - return Error(InstantiationError(env)) case Compound: if pi.Functor() != atomSlash || pi.Arity() != 2 { return Error(typeError(validTypePredicateIndicator, pi, env)) } + name, arity = pi.Arg(0), pi.Arg(1) + default: + return Error(typeError(validTypePredicateIndicator, pi, env)) + } - name, arity := pi.Arg(0), pi.Arg(1) + var n Atom + switch name := env.Resolve(name).(type) { + case Variable: + return Error(InstantiationError(env)) + case Atom: + n = name + default: + return Error(typeError(validTypeAtom, name, env)) + } - switch name := env.Resolve(name).(type) { - case Variable: - return Error(InstantiationError(env)) - case Atom: - switch arity := env.Resolve(arity).(type) { - case Variable: - return Error(InstantiationError(env)) - case Integer: - 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 { - return Error(permissionError(operationModify, permissionTypeStaticProcedure, key.Term(), env)) - } - delete(vm.procedures, key) - return k(env) - default: - return Error(typeError(validTypeInteger, arity, env)) - } - default: - return Error(typeError(validTypeAtom, name, env)) + var a Integer + switch arity := env.Resolve(arity).(type) { + case Variable: + return Error(InstantiationError(env)) + case Integer: + if arity < 0 { + return Error(domainError(validDomainNotLessThanZero, arity, env)) } + a = arity default: - return Error(typeError(validTypePredicateIndicator, pi, env)) + return Error(typeError(validTypeInteger, arity, env)) + } + + key := procedureIndicator{module: dm, name: n, arity: a} + if e := vm.procedures[key]; !e.dynamic { + return Error(permissionError(operationModify, permissionTypeStaticProcedure, key.Term(), env)) } + delete(vm.procedures, key) + return k(env) } // CurrentInput unifies stream with the current input stream. @@ -1266,7 +1314,7 @@ func Open(vm *VM, sourceSink, mode, stream, options Term, k Cont, env *Env) *Pro case err == nil: if s.mode == ioModeRead { s.source = f - s.initRead() + _ = s.initRead() } else { s.sink = f } @@ -1455,13 +1503,16 @@ func FlushOutput(vm *VM, streamOrAlias Term, k Cont, env *Env) *Promise { // WriteTerm outputs term to stream with options. func WriteTerm(vm *VM, streamOrAlias, t, options Term, k Cont, env *Env) *Promise { + module := callingModule(env) + s, err := stream(vm, streamOrAlias, env) if err != nil { return Error(err) } + l, _ := vm.moduleLocals[module] opts := WriteOptions{ - ops: vm.operators, + ops: l.operators, priority: 1200, } iter := ListIterator{List: options, Env: env} @@ -1732,7 +1783,8 @@ func ReadTerm(vm *VM, streamOrAlias, out, options Term, k Cont, env *Env) *Promi return Error(err) } - p := NewParser(vm, s) + module := callingModule(env) + p := newParserModule(vm, &module, s) defer func() { _ = s.UnreadRune() }() @@ -1970,11 +2022,18 @@ func Halt(_ *VM, n Term, k Cont, env *Env) *Promise { // Clause unifies head and body with H and B respectively where H :- B is in the database. func Clause(vm *VM, head, body Term, k Cont, env *Env) *Promise { - pi, _, err := piArg(head, env) + module := callingModule(env) + dm, hh, err := moduleTerm(module, head, env) if err != nil { return Error(err) } + pi, _, err := piArg(hh, env) + if err != nil { + return Error(err) + } + pi.module = dm + switch env.Resolve(body).(type) { case Variable, Atom, Compound: break @@ -1982,18 +2041,19 @@ func Clause(vm *VM, head, body Term, k Cont, env *Env) *Promise { return Error(typeError(validTypeCallable, body, env)) } - p, ok := vm.procedures[pi] + e, ok := vm.procedures[pi] if !ok { return Bool(false) } - u, ok := p.(*userDefined) - if !ok || !u.public { + if !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 { + cs := e.procedure.(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) @@ -2518,43 +2578,54 @@ func SetStreamPosition(vm *VM, streamOrAlias, position Term, k Cont, env *Env) * // CharConversion registers a character conversion from inChar to outChar, or remove the conversion if inChar = outChar. func CharConversion(vm *VM, inChar, outChar Term, k Cont, env *Env) *Promise { + module := callingModule(env) + var i rune switch in := env.Resolve(inChar).(type) { case Variable: return Error(InstantiationError(env)) case Atom: - i := []rune(in.String()) - if len(i) != 1 { + rs := []rune(in.String()) + if len(rs) != 1 { return Error(representationError(flagCharacter, env)) } + i = rs[0] + default: + return Error(representationError(flagCharacter, env)) + } - switch out := env.Resolve(outChar).(type) { - case Variable: - return Error(InstantiationError(env)) - case Atom: - o := []rune(out.String()) - if len(o) != 1 { - return Error(representationError(flagCharacter, env)) - } - - if vm.charConversions == nil { - vm.charConversions = map[rune]rune{} - } - if i[0] == o[0] { - delete(vm.charConversions, i[0]) - return k(env) - } - vm.charConversions[i[0]] = o[0] - return k(env) - default: + var o rune + switch out := env.Resolve(outChar).(type) { + case Variable: + return Error(InstantiationError(env)) + case Atom: + rs := []rune(out.String()) + if len(rs) != 1 { return Error(representationError(flagCharacter, env)) } + o = rs[0] default: return Error(representationError(flagCharacter, env)) } + + if vm.moduleLocals == nil { + vm.moduleLocals = map[Atom]moduleLocal{} + } + l, _ := vm.moduleLocals[module] + if l.charConversions == nil { + l.charConversions = map[rune]rune{} + } + if i == o { + delete(l.charConversions, i) + } else { + l.charConversions[i] = o + } + vm.moduleLocals[module] = l + return k(env) } // CurrentCharConversion succeeds iff a conversion from inChar to outChar is defined. func CurrentCharConversion(vm *VM, inChar, outChar Term, k Cont, env *Env) *Promise { + module := callingModule(env) switch in := env.Resolve(inChar).(type) { case Variable: break @@ -2581,7 +2652,8 @@ func CurrentCharConversion(vm *VM, inChar, outChar Term, k Cont, env *Env) *Prom if c1, ok := env.Resolve(inChar).(Atom); ok { r := []rune(c1.String()) - if r, ok := vm.charConversions[r[0]]; ok { + l, _ := vm.moduleLocals[module] + if r, ok := l.charConversions[r[0]]; ok { return Unify(vm, outChar, Atom(r), k, env) } return Unify(vm, outChar, c1, k, env) @@ -2591,7 +2663,8 @@ 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] + l, _ := vm.moduleLocals[module] + cr, ok := l.charConversions[r] if !ok { cr = r } @@ -2605,11 +2678,12 @@ func CurrentCharConversion(vm *VM, inChar, outChar Term, k Cont, env *Env) *Prom // SetPrologFlag sets flag to value. func SetPrologFlag(vm *VM, flag, value Term, k Cont, env *Env) *Promise { + module := callingModule(env) + var modify func(vm *VM, module, value Atom) error switch f := env.Resolve(flag).(type) { case Variable: return Error(InstantiationError(env)) case Atom: - var modify func(vm *VM, value Atom) error switch f { case atomBounded, atomMaxInteger, atomMinInteger, atomIntegerRoundingFunction, atomMaxArity: return Error(permissionError(operationModify, permissionTypeFlag, f, env)) @@ -2624,69 +2698,97 @@ func SetPrologFlag(vm *VM, flag, value Term, k Cont, env *Env) *Promise { default: return Error(domainError(validDomainPrologFlag, f, env)) } - - switch v := env.Resolve(value).(type) { - case Variable: - return Error(InstantiationError(env)) - case Atom: - if err := modify(vm, v); err != nil { - return Error(err) - } - return k(env) - default: - return Error(domainError(validDomainFlagValue, atomPlus.Apply(flag, value), env)) - } default: return Error(typeError(validTypeAtom, f, env)) } + + var v Atom + switch value := env.Resolve(value).(type) { + case Variable: + return Error(InstantiationError(env)) + case Atom: + v = value + default: + return Error(domainError(validDomainFlagValue, atomPlus.Apply(flag, value), env)) + } + + if err := modify(vm, module, v); err != nil { + return Error(err) + } + return k(env) } -func modifyCharConversion(vm *VM, value Atom) error { +func modifyCharConversion(vm *VM, module, value Atom) error { + if vm.moduleLocals == nil { + vm.moduleLocals = map[Atom]moduleLocal{} + } + l, _ := vm.moduleLocals[module] + defer func() { + vm.moduleLocals[module] = l + }() switch value { case atomOn: - vm.charConvEnabled = true + l.charConversion = true case atomOff: - vm.charConvEnabled = false + l.charConversion = false default: return domainError(validDomainFlagValue, atomPlus.Apply(atomCharConversion, value), nil) } return nil } -func modifyDebug(vm *VM, value Atom) error { +func modifyDebug(vm *VM, module, value Atom) error { + if vm.moduleLocals == nil { + vm.moduleLocals = map[Atom]moduleLocal{} + } + l, _ := vm.moduleLocals[module] + defer func() { + vm.moduleLocals[module] = l + }() switch value { case atomOn: - vm.debug = true + l.debug = true case atomOff: - vm.debug = false + l.debug = false default: return domainError(validDomainFlagValue, atomPlus.Apply(atomDebug, value), nil) } return nil } -func modifyUnknown(vm *VM, value Atom) error { +func modifyUnknown(vm *VM, module, value Atom) error { + l, _ := vm.moduleLocals[module] + defer func() { + vm.moduleLocals[module] = l + }() switch value { case atomError: - vm.unknown = unknownError + l.unknown = unknownError case atomWarning: - vm.unknown = unknownWarning + l.unknown = unknownWarning case atomFail: - vm.unknown = unknownFail + l.unknown = unknownFail default: return domainError(validDomainFlagValue, atomPlus.Apply(atomUnknown, value), nil) } return nil } -func modifyDoubleQuotes(vm *VM, value Atom) error { +func modifyDoubleQuotes(vm *VM, module, value Atom) error { + if vm.moduleLocals == nil { + vm.moduleLocals = map[Atom]moduleLocal{} + } + l, _ := vm.moduleLocals[module] + defer func() { + vm.moduleLocals[module] = l + }() switch value { case atomCodes: - vm.doubleQuotes = doubleQuotesCodes + l.doubleQuotes = doubleQuotesCodes case atomChars: - vm.doubleQuotes = doubleQuotesChars + l.doubleQuotes = doubleQuotesChars case atomAtom: - vm.doubleQuotes = doubleQuotesAtom + l.doubleQuotes = doubleQuotesAtom default: return domainError(validDomainFlagValue, atomPlus.Apply(atomDoubleQuotes, value), nil) } @@ -2695,6 +2797,7 @@ func modifyDoubleQuotes(vm *VM, value Atom) error { // CurrentPrologFlag succeeds iff flag is set to value. func CurrentPrologFlag(vm *VM, flag, value Term, k Cont, env *Env) *Promise { + module := callingModule(env) switch f := env.Resolve(flag).(type) { case Variable: break @@ -2709,17 +2812,18 @@ func CurrentPrologFlag(vm *VM, flag, value Term, k Cont, env *Env) *Promise { return Error(typeError(validTypeAtom, f, env)) } + l, _ := vm.moduleLocals[module] 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(l.charConversion)), + tuple(atomDebug, onOff(l.debug)), tuple(atomMaxArity, atomUnbounded), - tuple(atomUnknown, NewAtom(vm.unknown.String())), - tuple(atomDoubleQuotes, NewAtom(vm.doubleQuotes.String())), + tuple(atomUnknown, NewAtom(l.unknown.String())), + tuple(atomDoubleQuotes, NewAtom(l.doubleQuotes.String())), } ks := make([]func(context.Context) *Promise, len(flags)) for i := range flags { @@ -2749,7 +2853,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 { + module := callingModule(env) + if _, ok := vm.procedures[procedureIndicator{module: module, name: atomTermExpansion, arity: 2}]; ok { var ret Term v := NewVariable() ok, err := Call(vm, atomTermExpansion.Apply(term, v), func(env *Env) *Promise { @@ -3006,3 +3111,156 @@ func appendLists(vm *VM, xs, ys, zs Term, k Cont, env *Env) *Promise { }, env) }) } + +func UseModule(vm *VM, module, file, imports Term, k Cont, env *Env) *Promise { + var pis []procedureIndicator + if i, ok := env.Resolve(imports).(Atom); !ok || i != atomAll { + iter := ListIterator{List: imports, Env: env} + for iter.Next() { + var pi procedureIndicator + switch i := iter.Current().(type) { + case Variable: + return Error(InstantiationError(env)) + case Compound: + if i.Functor() != atomSlash || i.Arity() != 2 { + return Error(typeError(validTypePredicateIndicator, i, env)) + } + switch n := i.Arg(0).(type) { + case Variable: + return Error(InstantiationError(env)) + case Atom: + switch a := i.Arg(1).(type) { + case Variable: + return Error(InstantiationError(env)) + case Integer: + pi = procedureIndicator{name: n, arity: a} + default: + return Error(typeError(validTypePredicateIndicator, i, env)) + } + default: + return Error(typeError(validTypePredicateIndicator, i, env)) + } + default: + return Error(typeError(validTypePredicateIndicator, i, env)) + } + pis = append(pis, pi) + } + } + + switch module := env.Resolve(module).(type) { + case Variable: + break + case Atom: + vm.importPredicates(callingModule(env), module, pis) + return k(env) + default: + return Error(typeError(validTypeAtom, module, env)) + } + + switch file := env.Resolve(file).(type) { + case Variable: + return Error(InstantiationError(env)) + case Atom: + break + default: + return Error(typeError(validTypeAtom, file, env)) + } + + return Delay(func(ctx context.Context) *Promise { + m, err := vm.ensureLoaded(ctx, file, env) + if err != nil { + return Error(err) + } + + vm.importPredicates(callingModule(env), m, pis) + return Unify(vm, module, m, k, env) + }) +} + +func CurrentModule(vm *VM, module Term, k Cont, env *Env) *Promise { + switch m := env.Resolve(module).(type) { + case Variable, Atom: + break + default: + return Error(typeError(validTypeAtom, m, env)) + } + + ks := make([]func(context.Context) *Promise, 0, len(vm.moduleLocals)) + for m := range vm.moduleLocals { + m := m + ks = append(ks, func(context.Context) *Promise { + return Unify(vm, module, m, k, env) + }) + } + return Delay(ks...) +} + +func PredicateProperty(vm *VM, prototype, property Term, k Cont, env *Env) *Promise { + module, prototype, err := moduleTerm(atomUser, prototype, env) + if err != nil { + return Error(err) + } + + pi, _, err := piArg(prototype, env) + if err != nil { + return Error(err) + } + pi.module = module + + e, ok := vm.procedures[pi] + if !ok { + return Bool(false) + } + + ks := []func(context.Context) *Promise{ + func(ctx context.Context) *Promise { + p := atomStatic + if e.dynamic { + p = atomDynamic + } + return Unify(vm, property, p, k, env) + }, + func(ctx context.Context) *Promise { + p := atomPrivate + if e.public { + p = atomPublic + } + return Unify(vm, property, p, k, env) + }, + func(ctx context.Context) *Promise { + return Unify(vm, property, atomDefinedIn.Apply(e.definedIn), k, env) + }, + } + if e.builtIn { + ks = append(ks, func(ctx context.Context) *Promise { + return Unify(vm, property, atomBuiltIn, k, env) + }) + } + if e.multifile { + ks = append(ks, func(ctx context.Context) *Promise { + return Unify(vm, property, atomMultifile, k, env) + }) + } + if e.exported { + ks = append(ks, func(ctx context.Context) *Promise { + return Unify(vm, property, atomExported, k, env) + }) + } + if e.metapredicate != nil { + ks = append(ks, func(ctx context.Context) *Promise { + return Unify(vm, property, pi.name.Apply(e.metapredicate...), k, env) + }) + } + if e.importedFrom != 0 { + ks = append(ks, func(ctx context.Context) *Promise { + return Unify(vm, property, atomImportedFrom.Apply(e.importedFrom), k, env) + }) + } + return Delay(ks...) +} + +// CallingContext unifies moduleName with the calling context. +func CallingContext(vm *VM, moduleName Term, k Cont, env *Env) *Promise { + pi := env.Resolve(varCallingContext).(procedureIndicator) + return Unify(vm, moduleName, pi.module, k, env) +} diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 28deaad3..173cbdf6 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "io" "math" "os" @@ -30,6 +31,7 @@ func TestCall(t *testing.T) { panic("told you") }) }) + require.NoError(t, vm.Compile(context.Background(), `:-(module(user, [])).`)) assert.NoError(t, vm.Compile(context.Background(), ` foo. foo(_, _). @@ -91,10 +93,10 @@ func TestCall1(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 2}: Predicate2(func(_ *VM, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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) @@ -122,10 +124,10 @@ func TestCall2(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 3}: Predicate3(func(_ *VM, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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) @@ -153,10 +155,10 @@ func TestCall3(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 4}: Predicate4(func(_ *VM, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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) @@ -184,10 +186,10 @@ func TestCall4(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 5}: Predicate5(func(_ *VM, _, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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) @@ -215,10 +217,10 @@ func TestCall5(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 6}: Predicate6(func(_ *VM, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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) @@ -246,10 +248,10 @@ func TestCall6(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 7}: Predicate7(func(_ *VM, _, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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) @@ -277,10 +279,10 @@ func TestCall7(t *testing.T) { t.Run(tt.title, func(t *testing.T) { defer setMemFree(tt.mem)() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("p"), arity: 8}: Predicate8(func(_ *VM, _, _, _, _, _, _, _, _ Term, k Cont, env *Env) *Promise { + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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) @@ -291,8 +293,8 @@ 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 { + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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 { @@ -300,7 +302,7 @@ func TestCallNth(t *testing.T) { }, func(context.Context) *Promise { return Error(errors.New("three")) }) - }), + })}, }, } @@ -939,9 +941,15 @@ 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{moduleLocals: map[Atom]moduleLocal{ + atomUser: { + operators: ops, + }, + }} ok, err := Op(&vm, Integer(1000), atomXFX, NewAtom("++"), Success, nil).Force(context.Background()) assert.NoError(t, err) @@ -969,24 +977,28 @@ func TestOp(t *testing.T) { name: atomPlus, }, }, - }, vm.operators) + }, vm.moduleLocals[atomUser].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, + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + operators: operators{ + NewAtom(`+++`): { + operatorClassInfix: { + priority: 900, + specifier: operatorSpecifierXFX, + name: NewAtom("+++"), + }, + }, + NewAtom(`+`): { + operatorClassInfix: { + priority: 1100, + specifier: operatorSpecifierXFX, + name: atomPlus, + }, + }, }, }, }, @@ -1017,32 +1029,36 @@ func TestOp(t *testing.T) { name: atomPlus, }, }, - }, vm.operators) + }, vm.moduleLocals[atomUser].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, + moduleLocals: map[Atom]moduleLocal{ + 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 +1082,7 @@ func TestOp(t *testing.T) { name: atomPlus, }, }, - }, vm.operators) + }, vm.moduleLocals[atomUser].operators) }) t.Run("priority is a variable", func(t *testing.T) { @@ -1135,16 +1151,18 @@ 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{moduleLocals: map[Atom]moduleLocal{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{moduleLocals: map[Atom]moduleLocal{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) @@ -1174,8 +1192,9 @@ func TestOp(t *testing.T) { }) 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{moduleLocals: map[Atom]moduleLocal{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) @@ -1207,8 +1226,9 @@ func TestOp(t *testing.T) { }) 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{moduleLocals: map[Atom]moduleLocal{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 +1238,18 @@ 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{moduleLocals: map[Atom]moduleLocal{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{moduleLocals: map[Atom]moduleLocal{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 +1258,15 @@ 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, opsFoo operators + ops.define(900, operatorSpecifierXFX, NewAtom(`+++`)) + ops.define(1000, operatorSpecifierXFX, NewAtom(`++`)) + ops.define(1100, operatorSpecifierXFX, NewAtom(`+`)) + opsFoo.define(1200, operatorSpecifierFX, NewAtom(`-`)) + vm := VM{moduleLocals: map[Atom]moduleLocal{ + atomUser: {operators: ops}, + NewAtom("foo"): {operators: opsFoo}, + }} t.Run("single solution", func(t *testing.T) { ok, err := CurrentOp(&vm, Integer(1100), atomXFX, atomPlus, Success, nil).Force(context.Background()) @@ -1511,51 +1538,57 @@ func TestBagOf(t *testing.T) { } vm := VM{ - unknown: unknownWarning, + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: atomEqual, arity: 2}: {procedure: Predicate2(Unify)}, + {module: atomUser, name: atomComma, arity: 2}: {procedure: Predicate2(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) + })}, + {module: atomUser, name: atomSemiColon, arity: 2}: {procedure: Predicate2(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) + }) + })}, + {module: atomUser, name: atomTrue, arity: 0}: {procedure: Predicate0(func(_ *VM, k Cont, env *Env) *Promise { + return k(env) + })}, + {module: atomUser, name: atomFail, arity: 0}: {procedure: Predicate0(func(*VM, Cont, *Env) *Promise { + return Bool(false) + })}, + {module: atomUser, name: NewAtom("a"), arity: 2}: {procedure: Predicate2(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) + }, func(context.Context) *Promise { + return Unify(vm, a.Apply(x, y), a.Apply(Integer(2), f.Apply(NewVariable())), k, env) + }) + })}, + {module: atomUser, name: NewAtom("b"), arity: 2}: {procedure: Predicate2(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) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(1)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(2)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(1)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) + }) + })}, + }, + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + unknown: unknownWarning, + }, + }, } - vm.Register2(atomEqual, Unify) - vm.Register2(atomComma, 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 { - 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 { - return k(env) - }) - vm.Register0(atomFail, func(*VM, Cont, *Env) *Promise { - return Bool(false) - }) - vm.Register2(NewAtom("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) - }, func(context.Context) *Promise { - 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 { - b := NewAtom("$b") - return Delay(func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(1)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(1)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(2)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(1)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) - }) - }) for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { @@ -1910,83 +1943,89 @@ func TestSetOf(t *testing.T) { } vm := VM{ - unknown: unknownWarning, + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: atomEqual, arity: 2}: {procedure: Predicate2(Unify)}, + {module: atomUser, name: atomComma, arity: 2}: {procedure: Predicate2(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) + })}, + {module: atomUser, name: atomSemiColon, arity: 2}: {procedure: Predicate2(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) + }) + })}, + {module: atomUser, name: atomTrue, arity: 0}: {procedure: Predicate0(func(_ *VM, k Cont, env *Env) *Promise { + return k(env) + })}, + {module: atomUser, name: atomFail, arity: 0}: {procedure: Predicate0(func(*VM, Cont, *Env) *Promise { + return Bool(false) + })}, + {module: atomUser, name: NewAtom("a"), arity: 2}: {procedure: Predicate2(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) + }, func(context.Context) *Promise { + return Unify(vm, a.Apply(x, y), a.Apply(Integer(2), f.Apply(NewVariable())), k, env) + }) + })}, + {module: atomUser, name: NewAtom("b"), arity: 2}: {procedure: Predicate2(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) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(1)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(2)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(1)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) + }) + })}, + {module: atomUser, name: NewAtom("d"), arity: 2}: {procedure: Predicate2(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) + }, func(context.Context) *Promise { + return Unify(vm, d.Apply(x, y), d.Apply(Integer(1), Integer(2)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, d.Apply(x, y), d.Apply(Integer(1), Integer(1)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, d.Apply(x, y), d.Apply(Integer(2), Integer(2)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, d.Apply(x, y), d.Apply(Integer(2), Integer(1)), k, env) + }, func(context.Context) *Promise { + return Unify(vm, d.Apply(x, y), d.Apply(Integer(2), Integer(2)), k, env) + }) + })}, + {module: atomUser, name: NewAtom("member"), arity: 2}: {procedure: Predicate2(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() { + e := iter.Current() + ks = append(ks, func(context.Context) *Promise { + return Unify(vm, elem, e, k, env) + }) + } + if err := iter.Err(); err != nil { + return Error(err) + } + return Delay(ks...) + })}, + {module: atomUser, name: NewAtom("setof"), arity: 3}: {procedure: Predicate3(SetOf)}, + {module: atomUser, name: NewAtom("bagof"), arity: 3}: {procedure: Predicate3(BagOf)}, + }, + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + unknown: unknownWarning, + }, + }, } - vm.Register2(atomEqual, Unify) - vm.Register2(atomComma, 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 { - 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 { - return k(env) - }) - vm.Register0(atomFail, func(*VM, Cont, *Env) *Promise { - return Bool(false) - }) - vm.Register2(NewAtom("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) - }, func(context.Context) *Promise { - 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 { - b := NewAtom("$b") - return Delay(func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(1)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(1)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(1), Integer(2)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(1)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, b.Apply(x, y), b.Apply(Integer(2), Integer(2)), k, env) - }, func(context.Context) *Promise { - 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 { - d := NewAtom("$d") - return Delay(func(context.Context) *Promise { - return Unify(vm, d.Apply(x, y), d.Apply(Integer(1), Integer(1)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, d.Apply(x, y), d.Apply(Integer(1), Integer(2)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, d.Apply(x, y), d.Apply(Integer(1), Integer(1)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, d.Apply(x, y), d.Apply(Integer(2), Integer(2)), k, env) - }, func(context.Context) *Promise { - return Unify(vm, d.Apply(x, y), d.Apply(Integer(2), Integer(1)), k, env) - }, func(context.Context) *Promise { - 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 { - var ks []func(context.Context) *Promise - iter := ListIterator{List: list, Env: env, AllowPartial: true} - for iter.Next() { - e := iter.Current() - ks = append(ks, func(context.Context) *Promise { - return Unify(vm, elem, e, k, env) - }) - } - if err := iter.Err(); err != nil { - return Error(err) - } - return Delay(ks...) - }) - vm.Register3(NewAtom("setof"), SetOf) - vm.Register3(NewAtom("bagof"), BagOf) for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { @@ -2048,7 +2087,7 @@ func TestFindAll(t *testing.T) { template: tuple(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable()), goal: atomEqual.Apply(x, Integer(1)), instances: s, - err: Exception{term: atomError.Apply(atomResourceError.Apply(resourceMemory.Term()), atomSlash.Apply(atomEqual, Integer(2)))}, + err: Exception{term: atomError.Apply(atomResourceError.Apply(resourceMemory.Term()), procedureIndicator{module: atomUser, name: atomEqual, arity: 2})}, mem: 1, }, } @@ -2065,6 +2104,7 @@ func TestFindAll(t *testing.T) { vm.Register0(atomFail, func(*VM, Cont, *Env) *Promise { return Bool(false) }) + require.NoError(t, vm.Compile(context.Background(), `:-(module(user, [])).`)) for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { @@ -2395,6 +2435,7 @@ func TestCatch(t *testing.T) { vm.Register0(atomFail, func(*VM, Cont, *Env) *Promise { return Bool(false) }) + require.NoError(t, vm.Compile(context.Background(), `:-(module(user, [])).`)) t.Run("match", func(t *testing.T) { v := NewVariable() @@ -2443,8 +2484,8 @@ func TestCatch(t *testing.T) { func TestCurrentPredicate(t *testing.T) { t.Run("user defined predicate", func(t *testing.T) { - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{}, + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: {procedure: clauses{}}, }} ok, err := CurrentPredicate(&vm, &compound{ functor: atomSlash, @@ -2462,10 +2503,10 @@ func TestCurrentPredicate(t *testing.T) { v := NewVariable() - vm := VM{procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{}, - {name: NewAtom("bar"), arity: 1}: &userDefined{}, - {name: NewAtom("baz"), arity: 1}: &userDefined{}, + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: {procedure: clauses{}}, + {module: atomUser, name: NewAtom("bar"), arity: 1}: {procedure: clauses{}}, + {module: atomUser, name: NewAtom("baz"), arity: 1}: {procedure: clauses{}}, }} ok, err := CurrentPredicate(&vm, v, func(env *Env) *Promise { c, ok := env.Resolve(v).(*compound) @@ -2494,8 +2535,8 @@ 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{procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: atomEqual, arity: 2}: {procedure: Predicate2(Unify)}, }} ok, err := CurrentPredicate(&vm, &compound{ functor: atomSlash, @@ -2557,6 +2598,21 @@ func TestCurrentPredicate(t *testing.T) { }) }) }) + + t.Run("different module", func(t *testing.T) { + vm := VM{procedures: map[procedureIndicator]procedureEntry{ + {module: NewAtom("bar"), name: NewAtom("foo"), arity: 1}: {procedure: clauses{}}, + }} + ok, err := CurrentPredicate(&vm, &compound{ + functor: atomSlash, + args: []Term{ + NewAtom("foo"), + Integer(1), + }, + }, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.False(t, ok) + }) } func TestAssertz(t *testing.T) { @@ -2577,7 +2633,7 @@ 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, definedIn: atomUser, procedure: clauses{ { pi: procedureIndicator{ name: NewAtom("foo"), @@ -2607,8 +2663,9 @@ func TestAssertz(t *testing.T) { }, }, }}, vm.procedures[procedureIndicator{ - name: NewAtom("foo"), - arity: 1, + module: atomUser, + name: NewAtom("foo"), + arity: 1, }]) }) @@ -2673,8 +2730,8 @@ func TestAssertz(t *testing.T) { t.Run("static", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{dynamic: false}, + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 0}: {dynamic: false}, }, } @@ -2707,7 +2764,7 @@ 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, definedIn: atomUser, procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{ @@ -2730,7 +2787,7 @@ func TestAsserta(t *testing.T) { {opcode: opExit}, }, }, - }}, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}]) + }}, vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 1}]) }) t.Run("rule", func(t *testing.T) { @@ -2758,7 +2815,7 @@ 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, definedIn: atomUser, procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, raw: &compound{ @@ -2780,7 +2837,7 @@ 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: procedureIndicator{module: atomUser, name: NewAtom("p"), arity: 1}}, {opcode: opCut}, {opcode: opExit}, }, @@ -2800,11 +2857,11 @@ 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: procedureIndicator{module: atomUser, name: NewAtom("p"), arity: 1}}, {opcode: opExit}, }, }, - }}, vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 0}]) + }}, vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 0}]) }) t.Run("clause is a variable", func(t *testing.T) { @@ -2876,8 +2933,8 @@ func TestAsserta(t *testing.T) { t.Run("static", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{dynamic: false}, + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 0}: {dynamic: false}, }, } @@ -2909,8 +2966,8 @@ 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{ + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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,16 +2982,16 @@ 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.procedures[procedureIndicator{module: atomUser, 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{ + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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,16 +3006,16 @@ 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.procedures[procedureIndicator{module: atomUser, 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{ + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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 +3029,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.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 1}].procedure.(clauses)) }) t.Run("variable", func(t *testing.T) { @@ -3002,8 +3059,8 @@ func TestRetract(t *testing.T) { t.Run("static", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{dynamic: false}, + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 0}: {dynamic: false}, }, } @@ -3017,8 +3074,8 @@ 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{ + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: {dynamic: true, procedure: clauses{ {raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}}, }}, }, @@ -3034,15 +3091,15 @@ 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.procedures[procedureIndicator{module: atomUser, 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{ + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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")}}}, @@ -3057,7 +3114,7 @@ func TestAbolish(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) - _, ok = vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}] + _, ok = vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 1}] assert.False(t, ok) }) @@ -3138,8 +3195,8 @@ func TestAbolish(t *testing.T) { t.Run("The predicate indicator pi is that of a static procedure", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{dynamic: false}, + procedures: map[procedureIndicator]procedureEntry{ + {name: NewAtom("foo"), arity: 0}: {dynamic: false}, }, } ok, err := Abolish(&vm, &compound{ @@ -4057,10 +4114,11 @@ 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{moduleLocals: map[Atom]moduleLocal{atomUser: {operators: ops}}} for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { buf.Reset() @@ -5400,8 +5458,8 @@ func TestClause(t *testing.T) { var c int vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("green"), arity: 1}: &userDefined{public: true, clauses: []clause{ + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("green"), arity: 1}: {public: true, procedure: clauses{ {raw: &compound{ functor: atomIf, args: []Term{ &compound{functor: NewAtom("green"), args: []Term{x}}, @@ -5460,10 +5518,10 @@ 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 { + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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,8 +5546,8 @@ func TestClause(t *testing.T) { defer setMemFree(1)() vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("green"), arity: 1}: &userDefined{public: true, clauses: []clause{ + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("green"), arity: 1}: {public: true, procedure: clauses{ {raw: NewAtom("green").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable())}, }}, }, @@ -6263,20 +6321,26 @@ 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.moduleLocals[atomUser].charConversions['a']) }) t.Run("remove", func(t *testing.T) { vm := VM{ - charConversions: map[rune]rune{ - 'a': 'b', + moduleLocals: map[Atom]moduleLocal{ + 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'] + l, ok := vm.moduleLocals[atomUser] + assert.True(t, ok) + _, ok = l.charConversions['a'] assert.False(t, ok) }) @@ -6338,8 +6402,12 @@ func TestCurrentCharConversion(t *testing.T) { t.Run("converted", func(t *testing.T) { vm := VM{ - charConversions: map[rune]rune{ - 'a': 'b', + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + charConversions: map[rune]rune{ + 'a': 'b', + }, + }, }, } ok, err := CurrentCharConversion(&vm, NewAtom("a"), NewAtom("b"), Success, nil).Force(context.Background()) @@ -6444,19 +6512,31 @@ 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.moduleLocals[atomUser].charConversion) }) t.Run("off", func(t *testing.T) { - vm := VM{charConvEnabled: true} + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + charConversion: 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.moduleLocals[atomUser].charConversion) }) t.Run("unknown", func(t *testing.T) { - vm := VM{charConvEnabled: true} + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + charConversion: true, + }, + }, + } ok, err := SetPrologFlag(&vm, atomCharConversion, NewAtom("foo"), Success, nil).Force(context.Background()) assert.Error(t, err) assert.False(t, ok) @@ -6469,19 +6549,25 @@ 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.moduleLocals[atomUser].debug) }) t.Run("off", func(t *testing.T) { - vm := VM{debug: true} + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + 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.moduleLocals[atomUser].debug) }) t.Run("unknown", func(t *testing.T) { - vm := VM{debug: true} + var vm VM ok, err := SetPrologFlag(&vm, atomDebug, NewAtom("foo"), Success, nil).Force(context.Background()) assert.Error(t, err) assert.False(t, ok) @@ -6497,31 +6583,47 @@ 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{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: {}, + }, + } 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.moduleLocals[atomUser].unknown) }) t.Run("warning", func(t *testing.T) { - var vm VM + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: {}, + }, + } 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.moduleLocals[atomUser].unknown) }) t.Run("fail", func(t *testing.T) { - var vm VM + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: {}, + }, + } 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.moduleLocals[atomUser].unknown) }) t.Run("fail", func(t *testing.T) { - var vm VM + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: {}, + }, + } ok, err := SetPrologFlag(&vm, atomUnknown, NewAtom("foo"), Success, nil).Force(context.Background()) assert.Error(t, err) assert.False(t, ok) @@ -6530,27 +6632,39 @@ func TestSetPrologFlag(t *testing.T) { t.Run("double_quotes", func(t *testing.T) { t.Run("codes", func(t *testing.T) { - var vm VM + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: {}, + }, + } 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.moduleLocals[atomUser].doubleQuotes) }) t.Run("chars", func(t *testing.T) { - var vm VM + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: {}, + }, + } 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.moduleLocals[atomUser].doubleQuotes) }) t.Run("atom", func(t *testing.T) { - var vm VM + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: {}, + }, + } 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.moduleLocals[atomUser].doubleQuotes) }) t.Run("unknown", func(t *testing.T) { @@ -6672,10 +6786,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.moduleLocals[atomUser].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.moduleLocals[atomUser].doubleQuotes.String()), env.Resolve(value)) default: assert.Fail(t, "unreachable") } @@ -7418,6 +7532,7 @@ func TestNegation(t *testing.T) { vm.Register0(atomError, func(*VM, Cont, *Env) *Promise { return Error(e) }) + require.NoError(t, vm.Compile(context.Background(), `:-(module(user, [])).`)) ok, err := Negate(&vm, atomTrue, Success, nil).Force(context.Background()) assert.NoError(t, err) @@ -7566,6 +7681,47 @@ func Test_iteratedGoalTerm(t *testing.T) { } } +func TestUseModule(t *testing.T) { + tests := []struct { + title string + }{ + {}, + } + + for _, tt := range tests { + t.Run(tt.title, func(t *testing.T) { + + }) + } +} + +func TestCurrentModule(t *testing.T) { + tests := []struct { + title string + module Term + ok bool + err error + }{ + {title: `current_module(foo).`, module: NewAtom("foo"), ok: true}, + {title: `current_module(fred:sid).`, module: atomColon.Apply(NewAtom("fred"), NewAtom("sid")), err: typeError(validTypeAtom, atomColon.Apply(NewAtom("fred"), NewAtom("sid")), nil)}, + } + + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + NewAtom("foo"): {}, + NewAtom("bar"): {}, + }, + } + + for _, tt := range tests { + t.Run(tt.title, func(t *testing.T) { + ok, err := CurrentModule(&vm, tt.module, Success, nil).Force(context.Background()) + assert.Equal(t, tt.ok, ok) + assert.Equal(t, tt.err, err) + }) + } +} + type mockWriter struct { mock.Mock } diff --git a/engine/clause.go b/engine/clause.go index 51e7e8f8..e8770451 100644 --- a/engine/clause.go +++ b/engine/clause.go @@ -5,16 +5,7 @@ import ( "errors" ) -type userDefined struct { - public bool - dynamic bool - multifile bool - discontiguous bool - - // 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 -} - +// 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." type clauses []clause func (cs clauses) call(vm *VM, args []Term, k Cont, env *Env) *Promise { @@ -34,14 +25,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 compile(module Atom, t Term, env *Env) (clauses, error) { t = env.Resolve(t) if t, ok := t.(Compound); ok && t.Functor() == atomIf && 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 := compileClause(module, head, iter.Current(), env) if err != nil { return nil, typeError(validTypeCallable, body, env) } @@ -51,7 +42,7 @@ func compile(t Term, env *Env) (clauses, error) { return cs, nil } - c, err := compileClause(t, nil, env) + c, err := compileClause(module, t, nil, env) c.raw = env.simplify(t) return []clause{c}, err } @@ -63,11 +54,11 @@ type clause struct { bytecode bytecode } -func compileClause(head Term, body Term, env *Env) (clause, error) { +func compileClause(module Atom, head Term, body Term, env *Env) (clause, error) { var c clause c.compileHead(head, env) if body != nil { - if err := c.compileBody(body, env); err != nil { + if err := c.compileBody(module, body, env); err != nil { return c, typeError(validTypeCallable, body, env) } } @@ -87,11 +78,11 @@ func (c *clause) compileHead(head Term, env *Env) { } } -func (c *clause) compileBody(body Term, env *Env) error { +func (c *clause) compileBody(module Atom, 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 := c.compilePred(module, iter.Current(), env); err != nil { return err } } @@ -100,23 +91,35 @@ func (c *clause) compileBody(body Term, env *Env) error { var errNotCallable = errors.New("not callable") -func (c *clause) compilePred(p Term, env *Env) error { +func (c *clause) compilePred(module Atom, p Term, env *Env) error { switch p := env.Resolve(p).(type) { case Variable: - return c.compilePred(atomCall.Apply(p), env) + return c.compilePred(module, atomCall.Apply(p), env) case Atom: switch p { 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}}) + pi := procedureIndicator{module: module, name: p, arity: 0} + c.bytecode = append(c.bytecode, instruction{opcode: opCall, operand: pi}) return nil case Compound: + if p.Functor() == atomColon && p.Arity() == 2 { + switch m := env.Resolve(p.Arg(0)).(type) { + case Variable: + return c.compilePred(module, atomCall.Apply(p), env) + case Atom: + return c.compilePred(m, p.Arg(1), env) + default: + break + } + } for i := 0; i < p.Arity(); i++ { c.compileBodyArg(p.Arg(i), env) } - c.bytecode = append(c.bytecode, instruction{opcode: opCall, operand: procedureIndicator{name: p.Functor(), arity: Integer(p.Arity())}}) + pi := procedureIndicator{module: module, name: p.Functor(), arity: Integer(p.Arity())} + c.bytecode = append(c.bytecode, instruction{opcode: opCall, operand: pi}) return nil default: return errNotCallable diff --git a/engine/dcg_test.go b/engine/dcg_test.go index 87b0a949..77e98882 100644 --- a/engine/dcg_test.go +++ b/engine/dcg_test.go @@ -11,11 +11,11 @@ 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 { + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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/env.go b/engine/env.go index 8583fffb..e71900de 100644 --- a/engine/env.go +++ b/engine/env.go @@ -1,8 +1,14 @@ package engine -var varContext = NewVariable() +var ( + varContext = NewVariable() + varCallingContext = NewVariable() +) -var rootContext = NewAtom("root") +var rootContext = procedureIndicator{ + module: atomUser, + name: NewAtom("root"), +} type envKey int64 diff --git a/engine/env_test.go b/engine/env_test.go index ffccc008..7470bb3d 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -22,7 +22,7 @@ func TestEnv_Bind(t *testing.T) { }, binding: binding{ key: newEnvKey(varContext), - value: NewAtom("root"), + value: rootContext, }, }, env.bind(a, NewAtom("a"))) } diff --git a/engine/exception_test.go b/engine/exception_test.go index eddcc2e1..85ae4791 100644 --- a/engine/exception_test.go +++ b/engine/exception_test.go @@ -20,7 +20,7 @@ func TestException_Error(t *testing.T) { func TestInstantiationError(t *testing.T) { assert.Equal(t, Exception{ - term: atomError.Apply(atomInstantiationError, rootContext), + term: atomError.Apply(atomInstantiationError, atomSlash.Apply(NewAtom("root"), Integer(0))), }, InstantiationError(nil)) } @@ -28,7 +28,7 @@ func TestDomainError(t *testing.T) { assert.Equal(t, Exception{ term: atomError.Apply( atomDomainError.Apply(atomNotLessThanZero, Integer(-1)), - rootContext, + atomSlash.Apply(NewAtom("root"), Integer(0)), ), }, DomainError(atomNotLessThanZero, Integer(-1), nil)) } @@ -37,7 +37,7 @@ func TestTypeError(t *testing.T) { assert.Equal(t, Exception{ term: atomError.Apply( atomTypeError.Apply(atomAtom, Integer(0)), - rootContext, + atomSlash.Apply(NewAtom("root"), Integer(0)), ), }, TypeError(atomAtom, Integer(0), nil)) } diff --git a/engine/module.go b/engine/module.go new file mode 100644 index 00000000..4464ccc0 --- /dev/null +++ b/engine/module.go @@ -0,0 +1,111 @@ +package engine + +import ( + "io/fs" + "os" +) + +// Module is a virtual module file that stores native predicates. +type Module struct { + fs.File + procedures map[procedureIndicator]procedureEntry +} + +// Register0 registers a predicate of arity 0. +func (m *Module) Register0(name Atom, p Predicate0) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 0}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +// Register1 registers a predicate of arity 1. +func (m *Module) Register1(name Atom, p Predicate1) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 1}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +// Register2 registers a predicate of arity 2. +func (m *Module) Register2(name Atom, p Predicate2) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 2}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +// Register3 registers a predicate of arity 3. +func (m *Module) Register3(name Atom, p Predicate3) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 3}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +// Register4 registers a predicate of arity 4. +func (m *Module) Register4(name Atom, p Predicate4) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 4}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +// Register5 registers a predicate of arity 5. +func (m *Module) Register5(name Atom, p Predicate5) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 5}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +// Register6 registers a predicate of arity 6. +func (m *Module) Register6(name Atom, p Predicate6) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 6}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +// Register7 registers a predicate of arity 7. +func (m *Module) Register7(name Atom, p Predicate7) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 7}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +// Register8 registers a predicate of arity 8. +func (m *Module) Register8(name Atom, p Predicate8) { + if m.procedures == nil { + m.procedures = map[procedureIndicator]procedureEntry{} + } + m.procedures[procedureIndicator{name: name, arity: 8}] = procedureEntry{builtIn: true, exported: true, procedure: p} +} + +type RealFS struct{} + +func (r RealFS) Open(name string) (fs.File, error) { + return os.Open(name) +} + +type MapFS map[string]fs.File + +func (m MapFS) Open(name string) (fs.File, error) { + f, ok := m[name] + if !ok { + return nil, fs.ErrNotExist + } + return f, nil +} + +type OverlayFS []fs.FS + +func (o OverlayFS) Open(name string) (fs.File, error) { + for _, e := range o { + if f, err := e.Open(name); err == nil { + return f, nil + } + } + return nil, fs.ErrNotExist +} diff --git a/engine/module_test.go b/engine/module_test.go new file mode 100644 index 00000000..45b75b50 --- /dev/null +++ b/engine/module_test.go @@ -0,0 +1,13 @@ +package engine + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRealFS_Open(t *testing.T) { + var fs RealFS + f, err := fs.Open("module.go") + assert.NoError(t, err) + assert.NotNil(t, f) +} diff --git a/engine/parser.go b/engine/parser.go index 9ca9ddf3..a49556cf 100644 --- a/engine/parser.go +++ b/engine/parser.go @@ -20,9 +20,9 @@ var ( // Parser turns bytes into Term. type Parser struct { - lexer Lexer - operators operators - doubleQuotes doubleQuotes + vm *VM + module *Atom + lexer Lexer Vars []ParsedVariable @@ -41,21 +41,35 @@ type ParsedVariable struct { // NewParser creates a new parser from the current VM and io.RuneReader. func NewParser(vm *VM, r io.RuneReader) *Parser { - if vm.operators == nil { - vm.operators = operators{} - } + return newParserModule(vm, &atomUser, r) +} + +func newParserModule(vm *VM, module *Atom, r io.RuneReader) *Parser { return &Parser{ + vm: vm, + module: module, lexer: Lexer{ input: newRuneRingBuffer(r), }, - operators: vm.operators, - doubleQuotes: vm.doubleQuotes, } } +func (p *Parser) doubleQuotes() doubleQuotes { + l, _ := p.vm.moduleLocals[*p.module] + return l.doubleQuotes +} + +func (p *Parser) operators() operators { + l, _ := p.vm.moduleLocals[*p.module] + return l.operators +} + // 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 { + if len(args) == 0 { // If no arguments given, treat `?` as is (e.g. meta predicate mode indicators). + return nil + } p.placeholder = placeholder p.args = make([]Term, len(args)) for i, a := range args { @@ -75,7 +89,7 @@ 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 { + switch p.doubleQuotes() { case doubleQuotesCodes: return CodeList(o.String()), nil case doubleQuotesAtom: @@ -419,7 +433,7 @@ func (p *Parser) prefix(maxPriority Integer) (operator, error) { p.backup() } - if op := p.operators[a][operatorClassPrefix]; op != (operator{}) && op.priority <= maxPriority { + if op := p.operators()[a][operatorClassPrefix]; op != (operator{}) && op.priority <= maxPriority { return op, nil } @@ -433,13 +447,13 @@ func (p *Parser) infix(maxPriority Integer) (operator, error) { return operator{}, errNoOp } - if op := p.operators[a][operatorClassInfix]; op != (operator{}) { + if op := p.operators()[a][operatorClassInfix]; op != (operator{}) { l, _ := op.bindingPriorities() if l <= maxPriority { return op, nil } } - if op := p.operators[a][operatorClassPostfix]; op != (operator{}) { + if op := p.operators()[a][operatorClassPostfix]; op != (operator{}) { l, _ := op.bindingPriorities() if l <= maxPriority { return op, nil @@ -518,7 +532,7 @@ func (p *Parser) term0(maxPriority Integer) (Term, error) { p.backup() return p.curlyBracketedTerm() case tokenDoubleQuotedList: - switch p.doubleQuotes { + switch p.doubleQuotes() { case doubleQuotesChars: return CharList(unDoubleQuote(t.val)), nil case doubleQuotesCodes: @@ -561,9 +575,12 @@ 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) { - p.backup() - return nil, errExpectation + if t, ok := t.(Atom); ok && maxPriority < 1201 { + ops := p.operators() + if ops.defined(t) { + p.backup() + return nil, errExpectation + } } if p.placeholder != 0 && t == p.placeholder { @@ -641,7 +658,7 @@ func (p *Parser) atom() (Atom, error) { return 0, errExpectation } case tokenDoubleQuotedList: - switch p.doubleQuotes { + switch p.doubleQuotes() { case doubleQuotesAtom: return NewAtom(unDoubleQuote(t.val)), nil default: @@ -754,7 +771,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) { + ops := p.operators() + if ops.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..8d42d034 100644 --- a/engine/parser_test.go +++ b/engine/parser_test.go @@ -159,12 +159,20 @@ func TestParser_Term(t *testing.T) { for _, tc := range tests { t.Run(tc.input, func(t *testing.T) { + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + operators: ops, + doubleQuotes: tc.doubleQuotes, + }, + }, + } p := Parser{ + vm: &vm, + module: &atomUser, lexer: Lexer{ input: newRuneRingBuffer(strings.NewReader(tc.input)), }, - operators: ops, - doubleQuotes: tc.doubleQuotes, } term, err := p.Term() assert.Equal(t, tc.err, err) @@ -234,8 +242,16 @@ func TestParser_Replace(t *testing.T) { for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { + vm := VM{ + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + doubleQuotes: tt.doubleQuotes, + }, + }, + } p := Parser{ - doubleQuotes: tt.doubleQuotes, + vm: &vm, + module: &atomUser, lexer: Lexer{ input: newRuneRingBuffer(strings.NewReader(tt.input)), }, @@ -308,7 +324,10 @@ func TestParser_Number(t *testing.T) { } func TestParser_More(t *testing.T) { + var vm VM p := Parser{ + vm: &vm, + module: &atomUser, lexer: Lexer{ input: newRuneRingBuffer(strings.NewReader(`foo. bar.`)), }, diff --git a/engine/term.go b/engine/term.go index 8bbf9f5e..64e04126 100644 --- a/engine/term.go +++ b/engine/term.go @@ -59,6 +59,9 @@ func (o WriteOptions) withRight(op operator) *WriteOptions { var defaultWriteOptions = WriteOptions{ ops: operators{ + atomColon: [_operatorClassLen]operator{ + operatorClassInfix: {priority: 600, specifier: operatorSpecifierXFY, name: atomColon}, // for module qualification + }, atomPlus: [_operatorClassLen]operator{ operatorClassInfix: {priority: 500, specifier: operatorSpecifierYFX, name: atomPlus}, // for flag+value }, @@ -104,3 +107,32 @@ func id(t Term) termID { return t // Assuming it's comparable. } } + +func moduleTerm(module Atom, t Term, env *Env) (qualifyingModule Atom, unqualifiedTerm Term, _ error) { + var c Compound + switch t := env.Resolve(t).(type) { + case Variable: + return 0, nil, InstantiationError(env) + case Compound: + if t.Functor() != atomColon || t.Arity() != 2 { + return module, t, nil + } + c = t + default: + return module, t, nil + } + + var mm Atom + switch a := env.Resolve(c.Arg(0)).(type) { + case Variable: + return 0, nil, InstantiationError(env) + case Atom: + mm = a + default: + return module, t, nil + } + + tt := c.Arg(1) + + return moduleTerm(mm, tt, env) +} diff --git a/engine/text.go b/engine/text.go index 9e00b065..c0bd6685 100644 --- a/engine/text.go +++ b/engine/text.go @@ -2,8 +2,12 @@ package engine import ( "context" + "errors" "fmt" + "io" "io/fs" + "os" + "path/filepath" "strings" ) @@ -16,43 +20,127 @@ 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 +// CompileModule compiles the Prolog text and updates the DB accordingly. +// If the given Prolog text defines a module, it returns the module name. +// Otherwise, it returns an atom `user`. +func (vm *VM) CompileModule(ctx context.Context, s string, args ...interface{}) (Atom, error) { + t := text{module: atomUser} if err := vm.compile(ctx, &t, s, args...); err != nil { - return err + return 0, err } if err := t.flush(); err != nil { - return err + return 0, err } if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - 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 + for pi, e := range t.procs { + pi.module = t.module + + if existing, ok := vm.procedures[pi]; ok { + if ecs, ok := existing.procedure.(clauses); ok && existing.multifile { + if cs, ok := e.procedure.(clauses); ok && e.multifile { + e.definedIn = existing.definedIn + e.procedure = append(ecs, cs...) + } + } + + // Imported predicates are already in the database. + if e.procedure == nil { + e.definedIn = existing.definedIn + e.procedure = existing.procedure + } + } + + if e.definedIn == 0 { + e.definedIn = t.module + } + + if e.procedure == nil { + e.procedure = clauses{} } - vm.procedures[pi] = u + vm.procedures[pi] = e } + env := NewEnv().bind(varContext, procedureIndicator{module: t.module, name: atomInitialization, arity: 1}) for _, g := range t.goals { - ok, err := Call(vm, g, Success, nil).Force(ctx) + ok, err := Call(vm, g, Success, env).Force(ctx) if err != nil { - return err + 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 fmt.Errorf("failed initialization goal: %s", sb.String()) + return 0, fmt.Errorf("failed initialization goal: %s", sb.String()) } } - return nil + return t.module, nil +} + +// Compile compiles the Prolog text and updates the DB accordingly. +func (vm *VM) Compile(ctx context.Context, s string, args ...interface{}) error { + _, err := vm.CompileModule(ctx, s, args...) + return err +} + +func (vm *VM) resetModule(module Atom) { + for pi, e := range vm.procedures { + if pi.module != module || e.builtIn { + continue + } + delete(vm.procedures, pi) + } + delete(vm.moduleLocals, module) +} + +func (vm *VM) importPredicates(dst, src Atom, limit []procedureIndicator) { + target := func(pi procedureIndicator) bool { + if limit == nil { + return true + } + for _, e := range limit { + if e == (procedureIndicator{name: pi.name, arity: pi.arity}) { + return true + } + } + return false + } + for pi, e := range vm.procedures { + if pi.module != src || !e.exported || !target(pi) { + continue + } + p := e.procedure + definedIn := e.definedIn + pi.module = dst + e := vm.procedures[pi] + e.exported = true + e.importedFrom = src + e.definedIn = definedIn + e.procedure = p + vm.procedures[pi] = e + } +} + +func (vm *VM) importOps(dst, src Atom) { + if vm.moduleLocals == nil { + vm.moduleLocals = map[Atom]moduleLocal{} + } + srcL, _ := vm.moduleLocals[src] + dstL, ok := vm.moduleLocals[dst] + if !ok { + dstL.operators = operators{} + } + defer func() { + vm.moduleLocals[dst] = dstL + }() + for name, ops := range srcL.operators { + dstL.operators[name] = ops + } } // Consult executes Prolog texts in files. @@ -68,7 +156,7 @@ func Consult(vm *VM, files Term, k Cont, env *Env) *Promise { return Delay(func(ctx context.Context) *Promise { for _, filename := range filenames { - if err := vm.ensureLoaded(ctx, filename, env); err != nil { + if _, err := vm.ensureLoaded(ctx, filename, env); err != nil { return Error(err) } } @@ -78,12 +166,12 @@ func Consult(vm *VM, files Term, k Cont, env *Env) *Promise { } func (vm *VM) compile(ctx context.Context, text *text, s string, args ...interface{}) error { - if text.clauses == nil { - text.clauses = map[procedureIndicator]*userDefined{} + if text.procs == nil { + text.procs = map[procedureIndicator]procedureEntry{} } s = ignoreShebangLine(s) - p := NewParser(vm, strings.NewReader(s)) + p := newParserModule(vm, &text.module, strings.NewReader(s)) if err := p.SetPlaceholder(NewAtom("?"), args...); err != nil { return err } @@ -123,7 +211,7 @@ func (vm *VM) compile(ctx context.Context, text *text, s string, args ...interfa } } - cs, err := compile(et, nil) + cs, err := compile(text.module, et, nil) if err != nil { return err } @@ -139,34 +227,123 @@ func (vm *VM) directive(ctx context.Context, text *text, d Term) error { return err } - switch pi, arg, _ := piArg(d, nil); pi { + if text.module == 0 { + text.module = atomUser + } + env := NewEnv().bind(varContext, procedureIndicator{module: text.module, name: atomIf, arity: 1}) + + switch pi, arg, _ := piArg(d, env); pi { + case procedureIndicator{name: atomModule, arity: 2}: + switch m := arg(0).(type) { + case Variable: + return InstantiationError(env) + case Atom: + text.module = m + default: + return typeError(validTypeAtom, m, env) + } + + env = NewEnv().bind(varContext, procedureIndicator{module: text.module, name: atomIf, arity: 1}) + vm.resetModule(text.module) + vm.importPredicates(text.module, atomProlog, nil) + vm.importOps(text.module, atomProlog) + + iter := ListIterator{List: arg(1)} + for iter.Next() { + var pi procedureIndicator + switch i := iter.Current().(type) { + case Variable: + return InstantiationError(nil) + case Compound: + if i.Functor() != atomSlash || i.Arity() != 2 { + return typeError(validTypePredicateIndicator, i, env) + } + switch n := i.Arg(0).(type) { + case Variable: + return InstantiationError(nil) + case Atom: + switch a := i.Arg(1).(type) { + case Variable: + return InstantiationError(nil) + case Integer: + pi = procedureIndicator{name: n, arity: a} + default: + return typeError(validTypePredicateIndicator, i, env) + } + default: + return typeError(validTypePredicateIndicator, i, env) + } + default: + return typeError(validTypePredicateIndicator, i, env) + } + e := text.procs[pi] + e.exported = true + text.procs[pi] = e + } + return iter.Err() + case procedureIndicator{name: atomUseModule, arity: 2}: + _, err := UseModule(vm, NewVariable(), arg(0), arg(1), Success, env).Force(ctx) + return err + case procedureIndicator{name: atomMetaPredicate, arity: 1}: + iter := anyIterator{Any: arg(0)} + for iter.Next() { + switch c := iter.Current().(type) { + case Variable: + return InstantiationError(env) + case Compound: + key := procedureIndicator{name: c.Functor(), arity: Integer(c.Arity())} + e := text.procs[key] + e.metapredicate = make([]Term, c.Arity()) + for i := 0; i < c.Arity(); i++ { + e.metapredicate[i] = c.Arg(i) + } + text.procs[key] = e + default: + return typeError(validTypeCompound, c, env) + } + } + return iter.Err() case procedureIndicator{name: atomDynamic, arity: 1}: - return text.forEachUserDefined(arg(0), func(u *userDefined) { - u.dynamic = true - u.public = true + return text.forEachProcedureEntry(arg(0), func(pi procedureIndicator, e procedureEntry) { + e.dynamic = true + e.public = true + text.procs[pi] = e }) case procedureIndicator{name: atomMultifile, arity: 1}: - return text.forEachUserDefined(arg(0), func(u *userDefined) { - u.multifile = true + return text.forEachProcedureEntry(arg(0), func(pi procedureIndicator, e procedureEntry) { + e.multifile = true + text.procs[pi] = e }) case procedureIndicator{name: atomDiscontiguous, arity: 1}: - return text.forEachUserDefined(arg(0), func(u *userDefined) { - u.discontiguous = true + return text.forEachProcedureEntry(arg(0), func(pi procedureIndicator, e procedureEntry) { + e.discontiguous = true + text.procs[pi] = e }) 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) + var n string + switch a := arg(0).(type) { + case Variable: + return InstantiationError(nil) + case Atom: + n = a.String() + default: + return typeError(validTypeAtom, a, nil) + } + + b, err := os.ReadFile(n) if err != nil { - return err + return existenceError(objectTypeSourceSink, arg(0), nil) } return vm.compile(ctx, text, string(b)) case procedureIndicator{name: atomEnsureLoaded, arity: 1}: - return vm.ensureLoaded(ctx, arg(0), nil) + _, err := vm.ensureLoaded(ctx, arg(0), nil) + return err default: - ok, err := Call(vm, d, Success, nil).Force(ctx) + ok, err := Call(vm, atomColon.Apply(text.module, d), Success, env).Force(ctx) if err != nil { return err } @@ -180,52 +357,80 @@ func (vm *VM) directive(ctx context.Context, text *text, d Term) error { } } -func (vm *VM) ensureLoaded(ctx context.Context, file Term, env *Env) error { - f, b, err := vm.open(file, env) - if err != nil { - return err +func (vm *VM) ensureLoaded(ctx context.Context, file Term, env *Env) (Atom, error) { + var fn string + switch f := env.Resolve(file).(type) { + case Variable: + return 0, InstantiationError(env) + case Atom: + fn = f.String() + default: + return 0, typeError(validTypeAtom, file, env) } + for _, f := range []string{fn, fn + ".pl"} { + switch m, err := vm.load(ctx, f); { + case err == nil: + return m, nil + case errors.Is(err, fs.ErrNotExist): + continue + default: + return 0, err + } + } + return 0, existenceError(objectTypeSourceSink, file, env) +} + +func (vm *VM) load(ctx context.Context, filename string) (Atom, error) { if vm.loaded == nil { - vm.loaded = map[string]struct{}{} + vm.loaded = map[string]Atom{} } - if _, ok := vm.loaded[f]; ok { - return nil + if m, ok := vm.loaded[filename]; ok { + return m, 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 - } + f, err := vm.FS.Open(filename) + if err != nil { + return 0, err + } - return f, b, nil + var m Atom + switch f := f.(type) { + case Module: + base := filepath.Base(filename) + m = NewAtom(strings.TrimSuffix(base, filepath.Ext(base))) + if vm.procedures == nil { + vm.procedures = map[procedureIndicator]procedureEntry{} + } + for pi, e := range f.procedures { + pi.module = m + e.definedIn = m + vm.procedures[pi] = e } - return "", nil, existenceError(objectTypeSourceSink, file, env) default: - return "", nil, typeError(validTypeAtom, file, env) + b, err := io.ReadAll(f) + _ = f.Close() + if err != nil { + return 0, err + } + + if m, err = vm.CompileModule(ctx, string(b)); err != nil { + return 0, err + } } + + vm.loaded[filename] = m + return m, nil } type text struct { - buf clauses - clauses map[procedureIndicator]*userDefined - goals []Term + module Atom + buf clauses + procs map[procedureIndicator]procedureEntry + goals []Term } -func (t *text) forEachUserDefined(pi Term, f func(u *userDefined)) error { +func (t *text) forEachProcedureEntry(pi Term, f func(pi procedureIndicator, e procedureEntry)) error { iter := anyIterator{Any: pi} for iter.Next() { switch pi := iter.Current().(type) { @@ -244,12 +449,7 @@ func (t *text) forEachUserDefined(pi Term, f func(u *userDefined)) error { return InstantiationError(nil) case Integer: pi := procedureIndicator{name: n, arity: a} - u, ok := t.clauses[pi] - if !ok { - u = &userDefined{} - t.clauses[pi] = u - } - f(u) + f(pi, t.procs[pi]) default: return typeError(validTypePredicateIndicator, pi, nil) } @@ -269,15 +469,16 @@ func (t *text) flush() error { } pi := t.buf[0].pi - u, ok := t.clauses[pi] - if !ok { - u = &userDefined{} - t.clauses[pi] = u + e := t.procs[pi] + if e.procedure == nil { + e.procedure = clauses{} } - if len(u.clauses) > 0 && !u.discontiguous { + cs := e.procedure.(clauses) + if len(cs) > 0 && !e.discontiguous { return &discontiguousError{pi: pi} } - u.clauses = append(u.clauses, t.buf...) + e.procedure = append(cs, t.buf...) + t.procs[pi] = e t.buf = t.buf[:0] return nil } diff --git a/engine/text_test.go b/engine/text_test.go index e18fc2e3..4869b11b 100644 --- a/engine/text_test.go +++ b/engine/text_test.go @@ -28,13 +28,14 @@ func TestVM_Compile(t *testing.T) { text string args []interface{} err error - result map[procedureIndicator]procedure + result map[procedureIndicator]procedureEntry }{ {title: "shebang", text: `#!/foo/bar foo(a). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - clauses: clauses{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, @@ -46,10 +47,10 @@ foo(a). }, }, }}, - {title: "shebang: no following lines", text: `#!/foo/bar`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ + {title: "shebang: no following lines", text: `#!/foo/bar`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { multifile: true, - clauses: clauses{ + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, @@ -64,9 +65,10 @@ foo(a). {title: "facts", text: ` foo(a). foo(b). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - clauses: clauses{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, @@ -89,10 +91,10 @@ foo(b). {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{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { multifile: true, - clauses: clauses{ + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, @@ -103,21 +105,23 @@ bar(X, "abc", [a, b], [a, b|Y], f(a)) :- X, !, foo(X, "abc", [a, b], [a, b|Y], f }, }, }, - {name: NewAtom("bar"), arity: 0}: &userDefined{ - clauses: clauses{ + {module: atomUser, name: NewAtom("bar"), arity: 0}: { + definedIn: atomUser, + procedure: 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: opCall, operand: procedureIndicator{module: atomUser, name: atomTrue, arity: 0}}, {opcode: opExit}, }, }, }, }, - {name: NewAtom("bar"), arity: 5}: &userDefined{ - clauses: clauses{ + {module: atomUser, name: NewAtom("bar"), arity: 5}: { + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("bar"), arity: 5}, raw: atomIf.Apply( @@ -146,7 +150,7 @@ bar(X, "abc", [a, b], [a, b|Y], f(a)) :- X, !, foo(X, "abc", [a, b], [a, b|Y], f {opcode: opPop}, {opcode: opEnter}, {opcode: opPutVar, operand: Integer(0)}, - {opcode: opCall, operand: procedureIndicator{name: atomCall, arity: 1}}, + {opcode: opCall, operand: procedureIndicator{module: atomUser, name: atomCall, arity: 1}}, {opcode: opCut}, {opcode: opPutVar, operand: Integer(0)}, {opcode: opPutConst, operand: charList("abc")}, @@ -162,7 +166,7 @@ bar(X, "abc", [a, b], [a, b|Y], f(a)) :- X, !, foo(X, "abc", [a, b], [a, b|Y], f {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: opCall, operand: procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 5}}, {opcode: opExit}, }, }, @@ -173,11 +177,12 @@ bar(X, "abc", [a, b], [a, b|Y], f(a)) :- X, !, foo(X, "abc", [a, b], [a, b|Y], f :- dynamic(foo/1). foo(a). foo(b). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ - public: true, - dynamic: true, - clauses: clauses{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { + public: true, + dynamic: true, + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, @@ -201,10 +206,11 @@ foo(b). :- multifile(foo/1). foo(a). foo(b). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { multifile: true, - clauses: clauses{ + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, @@ -237,10 +243,11 @@ foo(b). foo(a). bar(a). foo(b). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { discontiguous: true, - clauses: clauses{ + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("a")}}, @@ -259,8 +266,9 @@ foo(b). }, }, }, - {name: NewAtom("bar"), arity: 1}: &userDefined{ - clauses: clauses{ + {module: atomUser, name: NewAtom("bar"), arity: 1}: { + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("bar"), arity: 1}, raw: &compound{functor: NewAtom("bar"), args: []Term{NewAtom("a")}}, @@ -273,10 +281,11 @@ foo(b). }, }}, {title: "include", text: ` -:- include('testdata/foo'). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{ - clauses: clauses{ +:- include('testdata/foo.pl'). +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 0}: { + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, raw: NewAtom("foo"), @@ -286,9 +295,9 @@ foo(b). }, }, }, - {name: NewAtom("foo"), arity: 1}: &userDefined{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { multifile: true, - clauses: clauses{ + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, @@ -302,9 +311,10 @@ foo(b). }}, {title: "ensure_loaded", text: ` :- ensure_loaded('testdata/foo'). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 0}: &userDefined{ - clauses: clauses{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 0}: { + definedIn: atomUser, + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 0}, raw: NewAtom("foo"), @@ -314,9 +324,9 @@ foo(b). }, }, }, - {name: NewAtom("foo"), arity: 1}: &userDefined{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { multifile: true, - clauses: clauses{ + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, @@ -330,10 +340,10 @@ foo(b). }}, {title: "initialization", text: ` :- initialization(foo(c)). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { multifile: true, - clauses: clauses{ + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, @@ -347,10 +357,10 @@ foo(b). }}, {title: "predicate-backed directive", text: ` :- foo(c). -`, result: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ +`, result: map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("foo"), arity: 1}: { multifile: true, - clauses: clauses{ + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, @@ -400,13 +410,13 @@ foo :- 1. `, err: typeError(validTypeAtom, Integer(1), nil)}, {title: "error: initialization exception", text: ` :- initialization(bar). -`, err: existenceError(objectTypeProcedure, atomSlash.Apply(NewAtom("bar"), Integer(0)), nil)}, +`, err: existenceError(objectTypeProcedure, atomSlash.Apply(NewAtom("bar"), Integer(0)), NewEnv().bind(varContext, procedureIndicator{module: atomUser, name: atomInitialization, arity: 1}))}, {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)}, +`, err: existenceError(objectTypeProcedure, atomSlash.Apply(NewAtom("bar"), Integer(0)), NewEnv().bind(varContext, procedureIndicator{module: atomUser, name: atomIf, arity: 1}))}, {title: "error: predicate-backed directive failure", text: ` :- foo(d). `, err: errors.New("failed directive: foo(d)")}, @@ -431,16 +441,18 @@ bar(b). for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { - var vm VM - vm.operators.define(1200, operatorSpecifierXFX, atomIf) - vm.operators.define(1200, operatorSpecifierXFX, atomArrow) - vm.operators.define(1200, operatorSpecifierFX, atomIf) - vm.operators.define(1000, operatorSpecifierXFY, atomComma) - vm.operators.define(400, operatorSpecifierYFX, atomSlash) - vm.procedures = map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: &userDefined{ + ops := operators{} + ops.define(1200, operatorSpecifierXFX, atomIf) + ops.define(1200, operatorSpecifierXFX, atomArrow) + ops.define(1200, operatorSpecifierFX, atomIf) + ops.define(1000, operatorSpecifierXFY, atomComma) + ops.define(400, operatorSpecifierYFX, atomSlash) + vm := VM{moduleLocals: map[Atom]moduleLocal{atomUser: {operators: ops}}} + vm.procedures = map[procedureIndicator]procedureEntry{ + {module: atomUser, name: NewAtom("throw"), arity: 1}: {procedure: Predicate1(Throw)}, + {module: atomUser, name: NewAtom("foo"), arity: 1}: { multifile: true, - clauses: clauses{ + procedure: clauses{ { pi: procedureIndicator{name: NewAtom("foo"), arity: 1}, raw: &compound{functor: NewAtom("foo"), args: []Term{NewAtom("c")}}, @@ -453,10 +465,10 @@ bar(b). }, } vm.FS = testdata - vm.Register1(NewAtom("throw"), Throw) - assert.Equal(t, tt.err, vm.Compile(context.Background(), tt.text, tt.args...)) + err := vm.Compile(context.Background(), tt.text, tt.args...) + assert.Equal(t, tt.err, err) if tt.err == nil { - delete(vm.procedures, procedureIndicator{name: NewAtom("throw"), arity: 1}) + delete(vm.procedures, procedureIndicator{module: atomUser, name: NewAtom("throw"), arity: 1}) assert.Equal(t, tt.result, vm.procedures) } }) diff --git a/engine/vm.go b/engine/vm.go index f39fbd16..1a1fb2ca 100644 --- a/engine/vm.go +++ b/engine/vm.go @@ -46,103 +46,121 @@ func Failure(*Env) *Promise { return Bool(false) } +type procedureEntry struct { + dynamic bool + public bool + builtIn bool + multifile bool + exported bool + metapredicate []Term + importedFrom Atom + definedIn Atom + + discontiguous bool + + procedure procedure +} + +type moduleLocal struct { + operators operators + charConversions map[rune]rune + + // flags + charConversion bool + debug bool + unknown unknownAction + doubleQuotes doubleQuotes +} + // 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 + procedures map[procedureIndicator]procedureEntry // 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{} - - // Internal/external expression - operators operators - charConversions map[rune]rune - charConvEnabled bool - doubleQuotes doubleQuotes + loaded map[string]Atom // I/O streams streams input, output *Stream - // Misc - debug bool + moduleLocals map[Atom]moduleLocal } // 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 = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 0}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 0}] = procedureEntry{builtIn: true, exported: true, procedure: p} } // Register1 registers a predicate of arity 1. func (vm *VM) Register1(name Atom, p Predicate1) { if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 1}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 1}] = procedureEntry{builtIn: true, exported: true, procedure: p} } // Register2 registers a predicate of arity 2. func (vm *VM) Register2(name Atom, p Predicate2) { if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 2}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 2}] = procedureEntry{builtIn: true, exported: true, procedure: p} } // Register3 registers a predicate of arity 3. func (vm *VM) Register3(name Atom, p Predicate3) { if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 3}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 3}] = procedureEntry{builtIn: true, exported: true, procedure: p} } // Register4 registers a predicate of arity 4. func (vm *VM) Register4(name Atom, p Predicate4) { if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 4}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 4}] = procedureEntry{builtIn: true, exported: true, procedure: p} } // Register5 registers a predicate of arity 5. func (vm *VM) Register5(name Atom, p Predicate5) { if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 5}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 5}] = procedureEntry{builtIn: true, exported: true, procedure: 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 = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 6}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 6}] = procedureEntry{builtIn: true, exported: true, procedure: p} } // Register7 registers a predicate of arity 7. func (vm *VM) Register7(name Atom, p Predicate7) { if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 7}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 7}] = procedureEntry{builtIn: true, exported: true, procedure: p} } // Register8 registers a predicate of arity 8. func (vm *VM) Register8(name Atom, p Predicate8) { if vm.procedures == nil { - vm.procedures = map[procedureIndicator]procedure{} + vm.procedures = map[procedureIndicator]procedureEntry{} } - vm.procedures[procedureIndicator{name: name, arity: 8}] = p + vm.procedures[procedureIndicator{module: atomUser, name: name, arity: 8}] = procedureEntry{builtIn: true, exported: true, procedure: p} } type unknownAction int @@ -162,24 +180,30 @@ func (u unknownAction) String() string { } type procedure interface { - call(*VM, []Term, Cont, *Env) *Promise + call(vm *VM, args []Term, k Cont, env *Env) *Promise } // 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(name Atom, args []Term, k Cont, env *Env) *Promise { + module := callingModule(env) + return vm.ArriveModule(module, name, args, k, env) +} + +// ArriveModule is the entry point of the VM. +func (vm *VM) ArriveModule(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] + pi := procedureIndicator{module: module, name: name, arity: Integer(len(args))} + e, ok := vm.procedures[pi] if !ok { - switch vm.unknown { + switch l, _ := vm.moduleLocals[module]; l.unknown { case unknownWarning: vm.Unknown(name, args, env) fallthrough @@ -190,10 +214,29 @@ func (vm *VM) Arrive(name Atom, args []Term, k Cont, env *Env) (promise *Promise } } + // Module name expansion + for i, mode := range e.metapredicate { + if !isMetaArg(mode) { + continue + } + args[i] = atomColon.Apply(module, args[i]) + } + // bind the special variable to inform the predicate about the context. - env = env.bind(varContext, pi.Term()) + env = env.bind(varCallingContext, env.Resolve(varContext)) + env = env.bind(varContext, pi) + return e.procedure.call(vm, args, k, env) +} - return p.call(vm, args, k, env) +func isMetaArg(mode Term) bool { + switch m := mode.(type) { + case Atom: + return m == atomColon + case Integer: + return m >= 0 + default: + return false + } } func (vm *VM) exec(pc bytecode, vars []Variable, cont Cont, args []Term, astack [][]Term, env *Env, cutParent *Promise) *Promise { @@ -238,7 +281,7 @@ func (vm *VM) exec(pc bytecode, vars []Variable, cont Cont, args []Term, astack break case opCall: pi := operand.(procedureIndicator) - return vm.Arrive(pi.name, args, func(env *Env) *Promise { + return vm.ArriveModule(pi.module, pi.name, args, func(env *Env) *Promise { return vm.exec(pc, vars, cont, nil, nil, env, cutParent) }, env) case opExit: @@ -403,8 +446,9 @@ func (p Predicate8) call(vm *VM, args []Term, k Cont, env *Env) *Promise { // procedureIndicator identifies a procedure e.g. (=)/2. type procedureIndicator struct { - name Atom - arity Integer + module Atom + name Atom + arity Integer } func (p procedureIndicator) WriteTerm(w io.Writer, opts *WriteOptions, env *Env) error { diff --git a/engine/vm_test.go b/engine/vm_test.go index 9e1381ee..a7c6dcad 100644 --- a/engine/vm_test.go +++ b/engine/vm_test.go @@ -13,7 +13,7 @@ func TestVM_Register0(t *testing.T) { vm.Register0(NewAtom("foo"), func(_ *VM, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 0}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 0}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{}, Success, nil).Force(context.Background()) @@ -35,7 +35,7 @@ func TestVM_Register1(t *testing.T) { vm.Register1(NewAtom("foo"), func(_ *VM, a Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 1}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 1}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a")}, Success, nil).Force(context.Background()) @@ -55,7 +55,7 @@ func TestVM_Register2(t *testing.T) { vm.Register2(NewAtom("foo"), func(_ *VM, a, b Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 2}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 2}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b")}, Success, nil).Force(context.Background()) @@ -75,7 +75,7 @@ func TestVM_Register3(t *testing.T) { vm.Register3(NewAtom("foo"), func(_ *VM, a, b, c Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 3}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 3}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c")}, Success, nil).Force(context.Background()) @@ -95,7 +95,7 @@ func TestVM_Register4(t *testing.T) { vm.Register4(NewAtom("foo"), func(_ *VM, a, b, c, d Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 4}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 4}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d")}, Success, nil).Force(context.Background()) @@ -115,7 +115,7 @@ func TestVM_Register5(t *testing.T) { vm.Register5(NewAtom("foo"), func(_ *VM, a, b, c, d, e Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 5}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 5}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, Success, nil).Force(context.Background()) @@ -135,7 +135,7 @@ func TestVM_Register6(t *testing.T) { vm.Register6(NewAtom("foo"), func(_ *VM, a, b, c, d, e, f Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 6}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 6}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, Success, nil).Force(context.Background()) @@ -155,7 +155,7 @@ func TestVM_Register7(t *testing.T) { vm.Register7(NewAtom("foo"), func(_ *VM, a, b, c, d, e, f, g Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 7}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 7}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, Success, nil).Force(context.Background()) @@ -175,7 +175,7 @@ func TestVM_Register8(t *testing.T) { vm.Register8(NewAtom("foo"), func(_ *VM, a, b, c, d, e, f, g, h Term, k Cont, env *Env) *Promise { return k(env) }) - p := vm.procedures[procedureIndicator{name: NewAtom("foo"), arity: 8}] + p := vm.procedures[procedureIndicator{module: atomUser, name: NewAtom("foo"), arity: 8}].procedure t.Run("ok", func(t *testing.T) { ok, err := p.call(&vm, []Term{NewAtom("a"), NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, Success, nil).Force(context.Background()) @@ -193,10 +193,10 @@ func TestVM_Register8(t *testing.T) { func TestVM_Arrive(t *testing.T) { t.Run("success", func(t *testing.T) { vm := VM{ - procedures: map[procedureIndicator]procedure{ - {name: NewAtom("foo"), arity: 1}: Predicate1(func(_ *VM, t Term, k Cont, env *Env) *Promise { + procedures: map[procedureIndicator]procedureEntry{ + {module: atomUser, 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()) @@ -207,7 +207,11 @@ 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, + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + unknown: unknownError, + }, + }, } ok, err := vm.Arrive(NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) assert.Equal(t, existenceError(objectTypeProcedure, &compound{ @@ -220,7 +224,11 @@ func TestVM_Arrive(t *testing.T) { t.Run("warning", func(t *testing.T) { var warned bool vm := VM{ - unknown: unknownWarning, + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + unknown: unknownWarning, + }, + }, Unknown: func(name Atom, args []Term, env *Env) { assert.Equal(t, NewAtom("foo"), name) assert.Equal(t, []Term{NewAtom("a")}, args) @@ -236,7 +244,11 @@ func TestVM_Arrive(t *testing.T) { t.Run("fail", func(t *testing.T) { vm := VM{ - unknown: unknownFail, + moduleLocals: map[Atom]moduleLocal{ + atomUser: { + unknown: unknownFail, + }, + }, } ok, err := vm.Arrive(NewAtom("foo"), []Term{NewAtom("a")}, Success, nil).Force(context.Background()) assert.NoError(t, err) diff --git a/interpreter.go b/interpreter.go index f0f88248..99d71a9b 100644 --- a/interpreter.go +++ b/interpreter.go @@ -6,155 +6,175 @@ import ( "errors" "github.com/ichiban/prolog/engine" "io" - "io/fs" - "os" "strings" ) -//go:embed bootstrap.pl -var bootstrap string - -// 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. -func New(in io.Reader, out io.Writer) *Interpreter { - var i Interpreter - i.FS = defaultFS{} - i.SetUserInput(engine.NewInputTextStream(in)) - i.SetUserOutput(engine.NewOutputTextStream(out)) +var system engine.Module +func init() { // Control constructs - i.Register1(engine.NewAtom("call"), engine.Call) - i.Register3(engine.NewAtom("catch"), engine.Catch) - i.Register1(engine.NewAtom("throw"), engine.Throw) + system.Register1(engine.NewAtom("call"), engine.Call) + system.Register3(engine.NewAtom("catch"), engine.Catch) + system.Register1(engine.NewAtom("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) + system.Register2(engine.NewAtom("="), engine.Unify) + system.Register2(engine.NewAtom("unify_with_occurs_check"), engine.UnifyWithOccursCheck) + system.Register2(engine.NewAtom("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) + system.Register1(engine.NewAtom("var"), engine.TypeVar) + system.Register1(engine.NewAtom("atom"), engine.TypeAtom) + system.Register1(engine.NewAtom("integer"), engine.TypeInteger) + system.Register1(engine.NewAtom("float"), engine.TypeFloat) + system.Register1(engine.NewAtom("compound"), engine.TypeCompound) + system.Register1(engine.NewAtom("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) + system.Register3(engine.NewAtom("compare"), engine.Compare) + system.Register2(engine.NewAtom("sort"), engine.Sort) + system.Register2(engine.NewAtom("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) + system.Register3(engine.NewAtom("functor"), engine.Functor) + system.Register3(engine.NewAtom("arg"), engine.Arg) + system.Register2(engine.NewAtom("=.."), engine.Univ) + system.Register2(engine.NewAtom("copy_term"), engine.CopyTerm) + system.Register2(engine.NewAtom("term_variables"), engine.TermVariables) // Arithmetic evaluation - i.Register2(engine.NewAtom("is"), engine.Is) + system.Register2(engine.NewAtom("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) + system.Register2(engine.NewAtom("=:="), engine.Equal) + system.Register2(engine.NewAtom("=\\="), engine.NotEqual) + system.Register2(engine.NewAtom("<"), engine.LessThan) + system.Register2(engine.NewAtom("=<"), engine.LessThanOrEqual) + system.Register2(engine.NewAtom(">"), engine.GreaterThan) + system.Register2(engine.NewAtom(">="), engine.GreaterThanOrEqual) // Clause retrieval and information - i.Register2(engine.NewAtom("clause"), engine.Clause) - i.Register1(engine.NewAtom("current_predicate"), engine.CurrentPredicate) + system.Register2(engine.NewAtom("clause"), engine.Clause) + system.Register1(engine.NewAtom("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) + system.Register1(engine.NewAtom("asserta"), engine.Asserta) + system.Register1(engine.NewAtom("assertz"), engine.Assertz) + system.Register1(engine.NewAtom("retract"), engine.Retract) + system.Register1(engine.NewAtom("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) + system.Register3(engine.NewAtom("findall"), engine.FindAll) + system.Register3(engine.NewAtom("bagof"), engine.BagOf) + system.Register3(engine.NewAtom("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) + system.Register1(engine.NewAtom("current_input"), engine.CurrentInput) + system.Register1(engine.NewAtom("current_output"), engine.CurrentOutput) + system.Register1(engine.NewAtom("set_input"), engine.SetInput) + system.Register1(engine.NewAtom("set_output"), engine.SetOutput) + system.Register4(engine.NewAtom("open"), engine.Open) + system.Register2(engine.NewAtom("close"), engine.Close) + system.Register1(engine.NewAtom("flush_output"), engine.FlushOutput) + system.Register2(engine.NewAtom("stream_property"), engine.StreamProperty) + system.Register2(engine.NewAtom("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) + system.Register2(engine.NewAtom("get_char"), engine.GetChar) + system.Register2(engine.NewAtom("peek_char"), engine.PeekChar) + system.Register2(engine.NewAtom("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) + system.Register2(engine.NewAtom("get_byte"), engine.GetByte) + system.Register2(engine.NewAtom("peek_byte"), engine.PeekByte) + system.Register2(engine.NewAtom("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) + system.Register3(engine.NewAtom("read_term"), engine.ReadTerm) + system.Register3(engine.NewAtom("write_term"), engine.WriteTerm) + system.Register3(engine.NewAtom("op"), engine.Op) + system.Register3(engine.NewAtom("current_op"), engine.CurrentOp) + system.Register2(engine.NewAtom("char_conversion"), engine.CharConversion) + system.Register2(engine.NewAtom("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) + system.Register1(engine.NewAtom(`\+`), engine.Negate) + system.Register0(engine.NewAtom("repeat"), engine.Repeat) + system.Register2(engine.NewAtom("call"), engine.Call1) + system.Register3(engine.NewAtom("call"), engine.Call2) + system.Register4(engine.NewAtom("call"), engine.Call3) + system.Register5(engine.NewAtom("call"), engine.Call4) + system.Register6(engine.NewAtom("call"), engine.Call5) + system.Register7(engine.NewAtom("call"), engine.Call6) + system.Register8(engine.NewAtom("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) + system.Register2(engine.NewAtom("atom_length"), engine.AtomLength) + system.Register3(engine.NewAtom("atom_concat"), engine.AtomConcat) + system.Register5(engine.NewAtom("sub_atom"), engine.SubAtom) + system.Register2(engine.NewAtom("atom_chars"), engine.AtomChars) + system.Register2(engine.NewAtom("atom_codes"), engine.AtomCodes) + system.Register2(engine.NewAtom("char_code"), engine.CharCode) + system.Register2(engine.NewAtom("number_chars"), engine.NumberChars) + system.Register2(engine.NewAtom("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) + system.Register2(engine.NewAtom("set_prolog_flag"), engine.SetPrologFlag) + system.Register2(engine.NewAtom("current_prolog_flag"), engine.CurrentPrologFlag) + system.Register1(engine.NewAtom("halt"), engine.Halt) - // Definite clause grammar - i.Register3(engine.NewAtom("phrase"), engine.Phrase) - i.Register2(engine.NewAtom("expand_term"), engine.ExpandTerm) + // Modules + system.Register3(engine.NewAtom("use_module"), engine.UseModule) + system.Register1(engine.NewAtom("current_module"), engine.CurrentModule) + system.Register2(engine.NewAtom("predicate_property"), engine.PredicateProperty) + system.Register1(engine.NewAtom("calling_context"), engine.CallingContext) // 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) + system.Register3(engine.NewAtom("append"), engine.Append) + system.Register2(engine.NewAtom("length"), engine.Length) + system.Register3(engine.NewAtom("between"), engine.Between) + system.Register2(engine.NewAtom("succ"), engine.Succ) + system.Register3(engine.NewAtom("nth0"), engine.Nth0) + system.Register3(engine.NewAtom("nth1"), engine.Nth1) + system.Register2(engine.NewAtom("call_nth"), engine.CallNth) + + // Compatibility + system.Register1(engine.NewAtom("consult"), engine.Consult) + + // DCGs + system.Register3(engine.NewAtom("phrase"), engine.Phrase) + system.Register2(engine.NewAtom("expand_term"), engine.ExpandTerm) +} + +//go:embed bootstrap.pl +var bootstrap string - _ = i.Exec(bootstrap) +// 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. +func New(in io.Reader, out io.Writer) *Interpreter { + var i Interpreter + i.FS = engine.OverlayFS{ + engine.MapFS{ + "/prolog/system": system, + }, + engine.RealFS{}, + } + i.SetUserInput(engine.NewInputTextStream(in)) + i.SetUserOutput(engine.NewOutputTextStream(out)) + + if err := i.Exec(bootstrap); err != nil { + panic(err) + } + + // Explicitly initialize `user` module to reflect `system` module. + if err := i.Exec(`:-(module(user, [])).`); err != nil { + panic(err) + } return &i } @@ -237,9 +257,3 @@ func (i *Interpreter) QuerySolutionContext(ctx context.Context, query string, ar return &Solution{sols: sols, err: sols.Close()} } - -type defaultFS struct{} - -func (d defaultFS) Open(name string) (fs.File, error) { - return os.Open(name) -} diff --git a/interpreter_test.go b/interpreter_test.go index 0784ba79..a558d6eb 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" @@ -750,6 +751,7 @@ append(nil, L, L).`}, }) i.Register1(engine.NewAtom("consult"), engine.Consult) i.Register3(engine.NewAtom("op"), engine.Op) + require.NoError(t, i.Exec(`:-(module(user, [])).`)) assert.NoError(t, i.Exec(`:-(op(1200, xfx, :-)).`)) assert.NoError(t, i.Exec(`:-(op(1200, fx, :-)).`)) assert.NoError(t, i.Exec(tt.premise)) @@ -800,6 +802,7 @@ func TestInterpreter_Query(t *testing.T) { var i Interpreter i.Register3(engine.NewAtom("op"), engine.Op) i.Register2(engine.NewAtom("set_prolog_flag"), engine.SetPrologFlag) + require.NoError(t, i.Exec(`:-(module(user, [])).`)) assert.NoError(t, i.Exec(` :-(op(1200, xfx, :-)). :-(set_prolog_flag(double_quotes, atom)). @@ -1232,6 +1235,7 @@ foo(c, d). i.Register0(engine.NewAtom("error"), func(_ *engine.VM, k engine.Cont, env *engine.Env) *engine.Promise { return engine.Error(err) }) + require.NoError(t, i.Exec(`:-(module(user, [])).`)) sol := i.QuerySolution(`error.`) assert.Equal(t, err, sol.Err()) @@ -1599,11 +1603,83 @@ 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) +func ExampleNew_module() { + // ISO/IEC 13211-2:2000(E) 6.2.6.1 Examples + p := New(nil, nil) + if err := p.Exec(` +:- module(utilities, [length/2, reverse/2]). + +length(List, Len) :- length1(List, 0, Len). +length1([], N, N). +length1([H|T], N, L) :- + N1 is N + 1, length1(T, N1, L). + +reverse(List, Reversed) :- + reverse1(List, [], Reversed). + +reverse1([], R, R). +reverse1([H|T], Acc, R) :- + reverse1(T, [H|Acc], R). +`); err != nil { + panic(err) + } + + if err := p.Exec(` +:- module(foo, []). +:- use_module(utilities, _, all). + +p(Y) :- q(X), length(X, Y). + +q([1, 2, 3, 4]). +`); err != nil { + panic(err) + } + + { + var s struct { + X int + } + if err := p.QuerySolution(`foo:p(X).`).Scan(&s); err != nil { + panic(err) + } + fmt.Printf("X = %d\n", s.X) + } + + { + var s struct { + L []int + } + if err := p.QuerySolution(`foo:reverse([1, 2, 3], L).`).Scan(&s); err != nil { + panic(err) + } + fmt.Printf("L = %d\n", s.L) + } + + { + var s struct { + L []int + } + if err := p.QuerySolution(`utilities:reverse1([1, 2, 3], [], L).`).Scan(&s); err != nil { + panic(err) + } + fmt.Printf("L = %d\n", s.L) + } + + { + var s struct { + X TermString + } + if err := p.QuerySolution(`catch(foo:reverse1([1,2,3], [], L), error(X, _), true).`).Scan(&s); err != nil { + panic(err) + } + fmt.Printf("%s\n", s.X) + } + + // Output: + // X = 4 + // L = [3 2 1] + // L = [3 2 1] + // existence_error(procedure,reverse1/3) } type readFn func(p []byte) (n int, err error)