├── .gitignore ├── golog.sh ├── profile.sh ├── t ├── cut.pl ├── ignore.pl ├── length.pl ├── var.pl ├── sort.pl ├── msort.pl ├── downcase_atom.pl ├── memberchk.pl ├── unify.pl ├── atom_number.pl ├── not.pl ├── ground.pl ├── if-then.pl ├── numeric_comparison.pl ├── phrase.pl ├── term_comparison.pl ├── if-then-else.pl ├── findall.pl └── atom_codes.pl ├── util └── util.go ├── README.md ├── clauses_test.go ├── foreign_test.go ├── lex ├── list_test.go ├── list.go ├── LICENSE ├── lex_test.go └── lex.go ├── LICENSE ├── golog.komodoproject ├── doc ├── features.md └── architecture.md ├── term ├── error.go ├── float.go ├── atom.go ├── rational.go ├── pretty.go ├── variable.go ├── bindings.go ├── integer.go ├── term_test.go ├── compound.go ├── unify_test.go ├── number.go └── term.go ├── foreignreturn.go ├── prelude └── prelude.go ├── database_test.go ├── clauses.go ├── prolog_test.go ├── cmd └── golog │ └── main.go ├── read ├── read_test.go └── read.go ├── database.go ├── choice_point.go ├── interactive.go ├── bench_test.go ├── prove_test.go ├── builtin.go └── machine.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | golog 3 | *~ 4 | -------------------------------------------------------------------------------- /golog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | go build ./cmd/golog && 3 | rlwrap ./golog $@ 4 | -------------------------------------------------------------------------------- /profile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | go test -c 3 | ./golog.test -test.run=none -test.bench=$2 -test.$1profile=$1.profile 4 | -------------------------------------------------------------------------------- /t/cut.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(tap)). 2 | 3 | % Tests that caught me at least once 4 | multiple_cuts :- 5 | findall(X, ((X=1;X=2),!,!), Xs), 6 | Xs = [1]. 7 | -------------------------------------------------------------------------------- /t/ignore.pl: -------------------------------------------------------------------------------- 1 | % Tests for ignore/1 2 | 3 | true_cp. 4 | true_cp. 5 | 6 | :- use_module(library(tap)). 7 | 8 | goal_succeeds :- 9 | ignore(true). 10 | 11 | goal_fails :- 12 | ignore(fail). 13 | 14 | goal_succeeds_with_choicepoints :- 15 | ignore(true_cp). -------------------------------------------------------------------------------- /t/length.pl: -------------------------------------------------------------------------------- 1 | % Tests for sort/2 2 | % 3 | % Part of the de facto standard. 4 | :- use_module(library(tap)). 5 | 6 | typical :- 7 | length([one,two,three], 3). 8 | 9 | empty :- 10 | length([], 0). 11 | 12 | singleton :- 13 | length([x], 1). 14 | 15 | 'build a list' :- 16 | length(Xs, 3), 17 | Xs = [_,_,_]. 18 | -------------------------------------------------------------------------------- /t/var.pl: -------------------------------------------------------------------------------- 1 | % Tests for var/1 2 | 3 | :- use_module(library(tap)). 4 | 5 | % Tests derived from Prolog: The Standard p. 181 6 | simple_var :- 7 | var(_). 8 | 9 | entangled_var :- 10 | X = _Y, 11 | var(X). 12 | 13 | term_with_vars(fail) :- 14 | X = f(_), 15 | var(X). 16 | 17 | not_a_var(fail) :- 18 | var(hi). 19 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "fmt" 4 | import "os" 5 | 6 | func MaybePanic(err error) { 7 | if err != nil { 8 | panic(err) 9 | } 10 | } 11 | 12 | func Debugging() bool { 13 | return os.Getenv("GOLOG_DEBUG") != "" 14 | } 15 | 16 | func Debugf(format string, args ...interface{}) { 17 | if Debugging() { 18 | fmt.Fprintf(os.Stderr, format, args...) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /t/sort.pl: -------------------------------------------------------------------------------- 1 | % Tests for sort/2 2 | % 3 | % Part of the defacto standard. 4 | :- use_module(library(tap)). 5 | 6 | typical :- 7 | sort([a,d,c,e], [a,c,d,e]). 8 | 9 | 'already in order' :- 10 | sort([c,d,e,f,g], [c,d,e,f,g]). 11 | 12 | 'input has duplicates' :- 13 | sort([a,b,a,c,d], [a,b,c,d]). 14 | 15 | 'empty list' :- 16 | sort([], []). 17 | 18 | 'complex terms' :- 19 | sort([sue, hello(world), 9, 42.95], [9, 42.95, sue, hello(world)]). 20 | -------------------------------------------------------------------------------- /t/msort.pl: -------------------------------------------------------------------------------- 1 | % Tests for msort/2 2 | % 3 | % This isn't defined in ISO but many Prologs have it. 4 | :- use_module(library(tap)). 5 | 6 | empty :- 7 | msort([], L), 8 | L = []. 9 | single :- 10 | msort([a], L), 11 | L = [a]. 12 | duplicates :- 13 | msort([a,a], L), 14 | L = [a,a]. 15 | realistic :- 16 | msort([a,9,hi(world),4.32,a(0)], L), 17 | L = [4.32, 9, a, a(0), hi(world)]. 18 | wrong(fail) :- 19 | msort([3,2,1], [3,2,1]). 20 | -------------------------------------------------------------------------------- /t/downcase_atom.pl: -------------------------------------------------------------------------------- 1 | % Tests for downcase_atom/2 2 | % 3 | % downcase_atom/2 is an SWI-Prolog extension for converting an 4 | % atom to lowercase. Other Prolog systems have similar 5 | % predicates, but the semantics seem less useful or more confusing. 6 | :- use_module(library(tap)). 7 | 8 | already_lowercase :- 9 | downcase_atom(foo, foo), 10 | downcase_atom(foo, Foo), 11 | Foo = foo. 12 | all_uppercase :- 13 | downcase_atom('YELL', A), 14 | A = yell, 15 | downcase_atom('YELL', yell). 16 | mixed_case :- 17 | downcase_atom('Once upon a time...', A), 18 | A = 'once upon a time...'. 19 | -------------------------------------------------------------------------------- /t/memberchk.pl: -------------------------------------------------------------------------------- 1 | % Tests for memberchk/1 2 | % 3 | % This isn't defined in ISO but many Prologs have it. 4 | :- use_module(library(tap)). 5 | 6 | 'empty list'(fail) :- 7 | memberchk(a, []). 8 | 9 | 'singleton list w/o match'(fail) :- 10 | memberchk(a, [b]). 11 | 12 | 'list w/o match'(fail) :- 13 | memberchk(a, [b, d, c, e]). 14 | 15 | 'only element' :- 16 | memberchk(a, [a]). 17 | 18 | 'first element' :- 19 | memberchk(c, [c, a, j, k]). 20 | 21 | 'final element' :- 22 | memberchk(c, [a, j, k, c]). 23 | 24 | typical :- 25 | memberchk(c, [a, j, c, k]). 26 | 27 | 'with duplicates' :- 28 | memberchk(c, [a, b, a, c, d, c]). 29 | -------------------------------------------------------------------------------- /t/unify.pl: -------------------------------------------------------------------------------- 1 | % Tests for unification 2 | % 3 | % These tests are only those cases which have caused Golog trouble 4 | % in the past. Eventually I hope to have many more unification 5 | % tests here. 6 | :- use_module(library(tap)). 7 | 8 | % This exact unification failure arose in a production language model 9 | 'product/3 with many anonymous variables' :- 10 | product(_,'wham-shell', _) = product(_,_,[]). 11 | 12 | % If _ creates a distinct variable, this unification succeeds. If 13 | % all _ variables are the same, it fails. This failure was the 14 | % root cause of the 'product/3 with many anonymous variables' failure. 15 | 'anonymous variables are distinct' :- 16 | _ = 3, 17 | _ = 2. 18 | -------------------------------------------------------------------------------- /t/atom_number.pl: -------------------------------------------------------------------------------- 1 | % Tests for atom_number/2 2 | % 3 | % atom_number/2 is an SWI-Prolog extension. We follow its semantics. 4 | :- use_module(library(tap)). 5 | 6 | empty_atom(fail) :- 7 | atom_number('', _). 8 | nil(fail) :- 9 | atom_number([], _). 10 | 11 | atom_to_int :- 12 | atom_number('13', N), 13 | N = 13. 14 | int_to_atom :- 15 | atom_number(A, 7), 16 | A = '7'. 17 | 18 | atom_to_float :- 19 | atom_number('7.23', N), 20 | N = 7.23. 21 | float_to_atom :- 22 | atom_number(A, 3.1415), 23 | A = '3.1415'. 24 | 25 | hex :- 26 | atom_number('0xa9', N), 27 | 169 = N. 28 | 29 | bound_variables :- 30 | A = '4', 31 | atom_number(A, N), 32 | 4 = N. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | golog 2 | ===== 3 | 4 | Prolog interpreter in Go with aspirations to be ISO compatible. See the [full package documentation](http://godoc.org/github.com/mndrix/golog) for usage details. 5 | 6 | Install with 7 | 8 | go get github.com/mndrix/golog 9 | 10 | Status 11 | ------ 12 | 13 | The long term goal is to make Golog fully compliant with ISO Prolog. It currently supports a subset of ISO Prolog. Any deviations from the standard are bugs. Please report [issues](https://github.com/mndrix/golog/issues) to help Golog improve. 14 | 15 | Golog is used in production systems, but should be considered experimental software. It has bugs and sharp edges. 16 | 17 | Development is sponsored by [PriceCharting](https://www.pricecharting.com). 18 | -------------------------------------------------------------------------------- /t/not.pl: -------------------------------------------------------------------------------- 1 | % Tests for (\+)/1 2 | 3 | hello(A, inverted) :- 4 | \+ A = world. 5 | hello(_, normal). 6 | 7 | :- use_module(library(tap)). 8 | 9 | % Tests derived from Prolog: The Standard p. 115 10 | simple_unify :- 11 | X = 3, 12 | \+((X=1;X=2)). 13 | 14 | failing :- 15 | \+ fail. 16 | 17 | /* 18 | % should bind X to 1 19 | cutting :- 20 | \+(!); X=1. 21 | */ 22 | 23 | disjunction_then_unify(fail) :- 24 | \+((X=1;X=2)), 3=X. 25 | 26 | unify_then_disjunction(fail) :- 27 | X = 1, \+((X=1;X=2)). 28 | 29 | 30 | 'existing choicepoints, not fails' :- 31 | hello(world, X), 32 | X == normal. 33 | 34 | 'existing choicepoints, not succeeds' :- 35 | findall(X, hello(foo, X), Xs), 36 | Xs == [inverted, normal]. 37 | -------------------------------------------------------------------------------- /t/ground.pl: -------------------------------------------------------------------------------- 1 | % Tests for ground/1 2 | % 3 | % This isn't defined in ISO but many Prologs have it. 4 | :- use_module(library(tap)). 5 | 6 | atom :- 7 | ground(here_is_an_atom). 8 | 9 | integer :- 10 | ground(19). 11 | 12 | float :- 13 | ground(7.321). 14 | 15 | 'compound term' :- 16 | ground(hi(one, two, three)). 17 | 18 | nil :- 19 | ground([]). 20 | 21 | 'complete list' :- 22 | ground([a, b, c]). 23 | 24 | 'bound variable' :- 25 | X = something, 26 | ground(X). 27 | 28 | 'unbound variable'(fail) :- 29 | ground(_). 30 | 31 | 'improper list'(fail) :- 32 | ground([a,b|_]). 33 | 34 | 'list pattern'(fail) :- 35 | ground([_|_]). 36 | 37 | 'compound term with variables'(fail) :- 38 | ground(a_term(one, X, three, X)). 39 | -------------------------------------------------------------------------------- /t/if-then.pl: -------------------------------------------------------------------------------- 1 | % Tests for ->/2 2 | % 3 | % ->/2 is defined in ISO §7.8.7 4 | :- use_module(library(tap)). 5 | 6 | % Tests derived from examples in ISO §7.8.7.4 7 | true_true :- 8 | (true -> true). 9 | true_fail(fail) :- 10 | (true -> fail). 11 | fail_true(fail) :- 12 | (fail -> true). 13 | true_bind :- 14 | (true -> X=1), 15 | 1 = X. 16 | true_bind_before_fail(fail) :- 17 | (true -> X=1), 18 | 1 = X, 19 | fail. 20 | cut_works :- 21 | ((X=1;X=2) -> true), 22 | 1=X. 23 | cut_if_clause :- 24 | findall(X, ((X=1;X=2) -> true), Xs), 25 | [1] = Xs. 26 | dont_cut_then_clause :- 27 | findall(X, (true -> (X=1;X=2)), Xs), 28 | [1,2] = Xs. 29 | 30 | % Tests derived from Prolog: The Standard p. 107 31 | failing_if_clause(fail) :- 32 | (fail -> (true;true)). 33 | -------------------------------------------------------------------------------- /clauses_test.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import "testing" 4 | 5 | import "github.com/mndrix/golog/read" 6 | 7 | func TestClauses(t *testing.T) { 8 | rt := read.Term_ // convenience 9 | 10 | cs0 := newClauses() 11 | if cs0.count() != 0 { 12 | t.Errorf("Initial clauses are not empty") 13 | } 14 | 15 | // add some terms 16 | cs1 := cs0. 17 | cons(rt(`hi(two).`)). 18 | cons(rt(`hi(one).`)). 19 | snoc(rt(`hi(three).`)). 20 | snoc(rt(`hi(four).`)) 21 | if n := cs1.count(); n != 4 { 22 | t.Errorf("Incorrect term count: %d vs 4", n) 23 | } 24 | expected := []string{ 25 | `hi(one)`, 26 | `hi(two)`, 27 | `hi(three)`, 28 | `hi(four)`, 29 | } 30 | for i, got := range cs1.all() { 31 | if got.String() != expected[i] { 32 | t.Errorf("Clause %d wrong: %s vs %s", i, got, expected[i]) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /foreign_test.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | // Tests for foreign predicates 4 | 5 | import "testing" 6 | import . "github.com/mndrix/golog/term" 7 | 8 | func TestDeterministic(t *testing.T) { 9 | x := 0 10 | m := NewMachine().RegisterForeign(map[string]ForeignPredicate{ 11 | "furrener/0": func(m Machine, args []Term) ForeignReturn { 12 | x++ 13 | return ForeignTrue() 14 | }, 15 | }) 16 | 17 | // run the foreign predicate once 18 | if !m.CanProve(`furrener.`) { 19 | t.Errorf("Can't prove furrener/0 the first time") 20 | } 21 | if x != 1 { 22 | t.Errorf("x has the wrong value: %d vs 1", x) 23 | } 24 | 25 | // make sure it works when called again 26 | if !m.CanProve(`furrener.`) { 27 | t.Errorf("Can't prove furrener/0 the second time") 28 | } 29 | if x != 2 { 30 | t.Errorf("x has the wrong value: %d vs 2", x) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /t/numeric_comparison.pl: -------------------------------------------------------------------------------- 1 | % Tests for =:=/2 and other numeric comparison operators 2 | % 3 | % As defined in ISO §8.7 4 | :- use_module(library(tap)). 5 | 6 | 'integer equality' :- 7 | 7 =:= 7. 8 | 9 | 'integer equality failing'(fail) :- 10 | 97 =:= 2. 11 | 12 | 'float equality' :- 13 | 1.0 =:= 1.0000. 14 | 15 | 'float equality failing'(fail) :- 16 | 1.7 =:= 2.0. 17 | 18 | 'mixed equality' :- 19 | 9 =:= 9.00. 20 | 21 | 'arithmetic equality' :- 22 | X = 1 + 2, 23 | X + 6 =:= X * 3. 24 | 25 | 'more arithmetic equality' :- 26 | 3*2 =:= 7-1. 27 | 28 | 'integer quotient equality' :- 29 | 1/3 =:= 2/6. 30 | 31 | 'zero is not one'(fail) :- 32 | 0 =:= 1. 33 | 34 | is :- 35 | X is 9 * 3, 36 | X =:= 27. 37 | 'fully bound is' :- 38 | X = 10, 39 | X is 6 + 4. 40 | 'repeated is' :- 41 | X is 9, 42 | X is 3*3. 43 | -------------------------------------------------------------------------------- /t/phrase.pl: -------------------------------------------------------------------------------- 1 | % Tests for phrase/2 and phrase/3 2 | % 3 | % This isn't defined in ISO but most Prologs have it. 4 | 5 | % helper predicates 6 | alphabet([a,b,c,d|X], X). 7 | 8 | triplets([X,X,X|Xs], Xs). 9 | 10 | greeting(Whom, [hello,Whom|Xs], Xs). 11 | 12 | :- use_module(library(tap)). 13 | 14 | 'phrase/2 alphabet' :- 15 | phrase(alphabet, [a,b,c,d]). 16 | 17 | 'phrase/3 alphabet' :- 18 | phrase(alphabet, [a,b,c,d,e,f], Rest), 19 | Rest = [e,f]. 20 | 21 | 'phrase/2 triplets' :- 22 | phrase(triplets, [j,j,j]). 23 | 24 | 'phrase/3 triplets' :- 25 | phrase(triplets, [j,j,j,k], Rest), 26 | Rest = [k]. 27 | 28 | 'phrase/2 greeting' :- 29 | phrase(greeting(Whom), [hello,michael]), 30 | Whom = michael. 31 | 32 | 'phrase/3 greeting' :- 33 | phrase(greeting(Whom), [hello,john,doe], Rest), 34 | Whom = john, 35 | Rest = [doe]. 36 | -------------------------------------------------------------------------------- /lex/list_test.go: -------------------------------------------------------------------------------- 1 | package lex 2 | 3 | import "testing" 4 | 5 | func TestLLBasic(t *testing.T) { 6 | ch := make(chan *Eme) 7 | go func() { 8 | ch <- &Eme{Atom, "1", nil} 9 | ch <- &Eme{Atom, "2", nil} 10 | ch <- &Eme{Atom, "3", nil} 11 | close(ch) 12 | }() 13 | 14 | l := NewList(ch) 15 | if l.Value.Type != Atom || l.Value.Content != "1" { 16 | t.Errorf("Wrong lexeme: %s", l.Value.Content) 17 | } 18 | 19 | l = l.Next() 20 | if l.Value.Type != Atom || l.Value.Content != "2" { 21 | t.Errorf("Wrong lexeme: %s", l.Value.Content) 22 | } 23 | 24 | l = l.Next() 25 | if l.Value.Type != Atom || l.Value.Content != "3" { 26 | t.Errorf("Wrong lexeme: %s", l.Value.Content) 27 | } 28 | 29 | l = l.Next() 30 | if l.Value.Type != EOF { 31 | t.Errorf("Backing channel not closed") 32 | } 33 | 34 | l = l.Next() 35 | if l.Value.Type != EOF { 36 | t.Errorf("Backing channel still not closed") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lex/list.go: -------------------------------------------------------------------------------- 1 | package lex 2 | 3 | // An immutable list of lexemes which populates its tail by reading 4 | // lexemes from a channel, such as that provided by Scan() 5 | type List struct { 6 | Value *Eme 7 | next *List 8 | src <-chan *Eme 9 | } 10 | 11 | // NewLexemList returns a new lexeme list which pulls lexemes from 12 | // the given source channel. Creating a new list consumes one lexeme 13 | // from the source channel. 14 | func NewList(src <-chan *Eme) *List { 15 | lexeme, ok := <-src 16 | if !ok { 17 | lexeme = &Eme{Type: EOF} 18 | } 19 | return &List{ 20 | Value: lexeme, 21 | next: nil, 22 | src: src, 23 | } 24 | } 25 | 26 | // Next returns the next element in the lexeme list, pulling a lexeme 27 | // from the source channel, if necessary 28 | func (self *List) Next() *List { 29 | if self.next == nil { 30 | next := NewList(self.src) 31 | self.next = next 32 | } 33 | return self.next 34 | } 35 | -------------------------------------------------------------------------------- /t/term_comparison.pl: -------------------------------------------------------------------------------- 1 | % Tests for ==/2 and other term comparison operators 2 | % 3 | % As defined in ISO §8.4 4 | :- use_module(library(tap)). 5 | 6 | % examples taken from ISO §8.4.1.4 7 | 1.0 @=< 1. 8 | 1.0 @< 1. 9 | '1 \\== 1'(fail) :- 10 | 1 \== 1. 11 | aardvark @=< zebra. 12 | short @=< short. 13 | short @=< shorter. 14 | 'short @>= shorter'(fail) :- 15 | short @>= shorter. 16 | 'foo(a,b) @< north(a).'(fail) :- 17 | foo(a,b) @< north(a). 18 | foo(b) @> foo(a). 19 | foo(a,_X) @< foo(b,_Y). 20 | 'foo(X,a) @< foo(Y,b)'(todo('implementation dependent')) :- 21 | foo(_X,a) @< foo(_Y,b). 22 | X @=< X. 23 | X == X. 24 | 'X @=< Y'(todo('implementation dependent')) :- 25 | _X @=< _Y. 26 | 'X == Y'(fail) :- 27 | _X == _Y. 28 | _ \== _. 29 | '_ == _'(fail) :- 30 | _ == _. 31 | '_ @=< _'(todo('implementation dependent')) :- 32 | _ @=< _. 33 | 'foo(X,a) @=< foo(Y,b)'(todo('implementation dependent')) :- 34 | foo(_X,a) @=< foo(_Y,b). 35 | -------------------------------------------------------------------------------- /t/if-then-else.pl: -------------------------------------------------------------------------------- 1 | % Tests for ;/2 (if-then-else) 2 | % 3 | % ;/2 is defined in ISO §7.8.8 4 | :- use_module(library(tap)). 5 | 6 | % Tests derived from examples in ISO §7.8.8.4 7 | true_true_fail :- 8 | (true -> true; fail). 9 | fail_true_true :- 10 | (fail -> true; true). 11 | true_fail_fail(fail) :- 12 | (true -> fail; fail). 13 | fail_true_fail(fail) :- 14 | (fail -> true; fail). 15 | then :- 16 | findall(X, (true -> X=1; X=2), Xs), 17 | Xs = [1]. 18 | else :- 19 | findall(X, (fail -> X=1; X=2), Xs), 20 | Xs = [2]. 21 | disjunction_in_then :- 22 | findall(X, (true -> (X=1;X=2); true), Xs), 23 | Xs = [1,2]. 24 | disjunction_in_if :- 25 | findall(X, ((X=1;X=2) -> true; true), Xs), 26 | Xs = [1]. 27 | cut_if :- % cut inside if shouldn't trim else clause 28 | ( !,fail -> true; true ). 29 | 30 | % Tests derived from Prolog: The Standard p. 107 31 | cut_opaque :- 32 | (((!,X=1,fail) -> true; fail); X=2), 33 | 2 = X. 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Michael Hendricks 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /golog.komodoproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0 6 | smart 7 | 1 8 | 1 9 | 10 | *.*~;*.bak;*.tmp;CVS;.#*;*.pyo;*.pyc;.svn;.git;.hg;.bzr;*%25*;tmp*.html;.DS_Store;*.swp;*.kpf;*.komodoproject;.komodotools;__pycache__;blib;_darcs;golog 11 | 12 | 0 13 | 4 14 | 1 15 | 4 16 | 0 17 | 1 18 | 19 | 20 | -------------------------------------------------------------------------------- /doc/features.md: -------------------------------------------------------------------------------- 1 | # Common Features 2 | 3 | This is a list of features that are common to Prolog implementations. 4 | Golog supports them all. 5 | 6 | TODO 7 | 8 | # Rare Features 9 | 10 | This is a list of features which are found in some Prolog 11 | implementations, but they're not commonly available. 12 | 13 | ## Automatic, arbitrary precision numbers 14 | 15 | Most programming languages treat the text "1.23" as a floating point 16 | number. Calculations with that value are subject to approximation and 17 | rounding errors. When Golog encounters a number, it uses the most 18 | accurate representation possible. In this case, it's stored as the 19 | rational number 123/100. 20 | 21 | Arithmetic calculations try to maintain the most accurate 22 | representation at all times. Numbers are only converted to floating 23 | point representation as a last resort. 24 | 25 | All this behavior is strictly an implementation detail. One can use 26 | Golog as if it only had integer and float numbers. However, the extra 27 | precision is especially helpful for working with currency amounts, 28 | etc. 29 | -------------------------------------------------------------------------------- /term/error.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import . "fmt" 4 | import "github.com/mndrix/golog/lex" 5 | 6 | type Error struct { 7 | message string 8 | eme *lex.Eme 9 | } 10 | 11 | // Returns an error term. These are used internally for error handling and 12 | // may disappear in the future. 13 | func NewError(message string, eme *lex.Eme) Term { 14 | return &Error{ 15 | message: message, 16 | eme: eme, 17 | } 18 | } 19 | 20 | func (self *Error) Functor() string { 21 | panic("Errors have no Functor()") 22 | } 23 | 24 | func (self *Error) Arity() int { 25 | panic("Errors have no Arity()") 26 | } 27 | 28 | func (self *Error) Arguments() []Term { 29 | panic("Errors have no Arguments()") 30 | } 31 | 32 | func (self *Error) String() string { 33 | return Sprintf("%s at %s", self.message, self.eme.Pos) 34 | } 35 | 36 | func (self *Error) Type() int { 37 | return ErrorType 38 | } 39 | 40 | func (self *Error) Indicator() string { 41 | panic("Errors have no Indicator()") 42 | } 43 | 44 | func (self *Error) ReplaceVariables(env Bindings) Term { 45 | return self 46 | } 47 | 48 | func (a *Error) Unify(e Bindings, b Term) (Bindings, error) { 49 | panic("Errors can't Unify()") 50 | } 51 | -------------------------------------------------------------------------------- /t/findall.pl: -------------------------------------------------------------------------------- 1 | % Tests for findall/3 2 | % 3 | % findall/3 is defined in ISO §8.10.1 4 | :- use_module(library(tap)). 5 | 6 | % Tests derived from examples in ISO §8.10.1.4 7 | disj :- 8 | findall(X, (X=1;X=2), S), 9 | S = [1,2]. 10 | template :- 11 | findall(X+_, (X=1), S), 12 | S = [1+_]. 13 | empty :- 14 | findall(_, fail, L), 15 | L = []. 16 | duplicate :- 17 | findall(X, (X=1;X=1), S), 18 | S = [1, 1]. 19 | order(fail) :- 20 | findall(X, (X=2; X=1), [1,2]). 21 | 22 | 'variable instances (same names)' :- 23 | findall(X, (X=1; X=2), [X,Y]), 24 | X = 1, Y = 2 . 25 | 26 | 'variables instances (different names)' :- 27 | findall(X, (X=1; X=2), [A,B]), 28 | A = 1, B = 2 . 29 | 30 | %all_variables(throws(instantiation_error)) :- 31 | % findall(X, Goal, S). 32 | %type_error(throws(type_error(callable, 4)) :- 33 | % findall(X, 4, S). 34 | 35 | 36 | % Tests derived from Prolog: The Standard p. 89 37 | variables_in_goal :- 38 | findall(X, (X=Y; X=Y), S), 39 | S = [_,_]. 40 | 41 | % choice points before findall/3 don't change the results 42 | 'choicepoints before findall/3' :- 43 | ( findall(x,fail,Result), ! 44 | ; true 45 | ), 46 | Result = []. 47 | -------------------------------------------------------------------------------- /t/atom_codes.pl: -------------------------------------------------------------------------------- 1 | % Tests for atom_codes/2 2 | % 3 | % atom_codes/2 is defined in ISO §8.6.5 4 | :- use_module(library(tap)). 5 | 6 | % Tests derived from examples in ISO §8.6.5.4 7 | empty_atom :- 8 | atom_codes('', L), 9 | L = []. 10 | nil :- 11 | atom_codes([], L), 12 | L = [0'[, 0']]. 13 | %single_quote :- 14 | % atom_codes('''', L), 15 | % L = [0''']. % '] for syntax 16 | ant :- 17 | atom_codes(ant, L), 18 | L = [0'a, 0'n, 0't]. % ' for syntax 19 | sop :- 20 | atom_codes(Str, [0's, 0'o, 0'p]), % ' for syntax 21 | Str = sop. 22 | partial_list :- 23 | atom_codes('North', [0'N | X]), % ' for syntax 24 | X = [0'o, 0'r, 0't, 0'h]. 25 | missing_a_code(fail) :- 26 | atom_codes(soap, [0's, 0'o, 0'p]). % ' for syntax 27 | %all_variables(throws(instantiation_error)) :- 28 | % atom_codes(_,_). 29 | 30 | 31 | % Tests derived from Prolog: The Standard p. 53 32 | anna_var :- 33 | atom_codes(X, [0'a, 0'n, 0'n, 0'a]), 34 | X = anna. 35 | anna_ground :- 36 | atom_codes(anna, [0'a, 0'n, 0'n, 0'a]). 37 | %var_and_list_with_var(throws(instantiation_error)) :- 38 | % atom_codes(_, [0'a | _]). % ' for syntax 39 | 40 | 41 | % Tests covering edge cases I've encountered 42 | bound_variables :- 43 | L = "mario", 44 | atom_codes(A, L), 45 | A = mario. 46 | -------------------------------------------------------------------------------- /term/float.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import "fmt" 4 | import "strconv" 5 | import "math/big" 6 | 7 | type Float float64 8 | 9 | func NewFloat(text string) Number { 10 | r, ok := NewRational(text) 11 | if ok { 12 | return r 13 | } 14 | f, err := strconv.ParseFloat(text, 64) 15 | maybePanic(err) 16 | return (*Float)(&f) 17 | } 18 | 19 | func NewFloat64(f float64) *Float { 20 | return (*Float)(&f) 21 | } 22 | 23 | func (self *Float) Value() float64 { 24 | return float64(*self) 25 | } 26 | 27 | func (self *Float) String() string { 28 | return fmt.Sprintf("%g", self.Value()) 29 | } 30 | func (self *Float) Type() int { 31 | return FloatType 32 | } 33 | func (self *Float) Indicator() string { 34 | return self.String() 35 | } 36 | 37 | func (a *Float) Unify(e Bindings, b Term) (Bindings, error) { 38 | if IsVariable(b) { 39 | return b.Unify(e, a) 40 | } 41 | if IsFloat(b) { 42 | if a.Value() == b.(*Float).Value() { 43 | return e, nil 44 | } 45 | } 46 | 47 | return e, CantUnify 48 | } 49 | 50 | func (self *Float) ReplaceVariables(env Bindings) Term { 51 | return self 52 | } 53 | 54 | // implement Number interface 55 | func (self *Float) Float64() float64 { 56 | return self.Value() 57 | } 58 | 59 | func (self *Float) LosslessInt() (*big.Int, bool) { 60 | return nil, false 61 | } 62 | 63 | func (self *Float) LosslessRat() (*big.Rat, bool) { 64 | return nil, false 65 | } 66 | -------------------------------------------------------------------------------- /lex/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | Copyright (c) 2013 Michael Hendricks. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /foreignreturn.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | // simulate an algebraic datatype representing the return value 4 | // of foreign predicates 5 | 6 | import "github.com/mndrix/golog/term" 7 | 8 | // ForeignReturn represents the return type of ForeignPredicate functions. 9 | // Values of ForeignReturn indicate certain success or failure conditions 10 | // to the Golog machine. If you're writing a foreign predicate, see 11 | // functions named Foreign* for possible return values. 12 | type ForeignReturn interface { 13 | IsaForeignReturn() // useless method to restrict implementations 14 | } 15 | 16 | // ForeignTrue indicates a foreign predicate that succeeds deterministically 17 | func ForeignTrue() ForeignReturn { 18 | t := true 19 | return (*foreignTrue)(&t) 20 | } 21 | 22 | type foreignTrue bool 23 | 24 | func (*foreignTrue) IsaForeignReturn() {} 25 | 26 | // ForeignFail indicates a foreign predicate that failed 27 | func ForeignFail() ForeignReturn { 28 | f := false 29 | return (*foreignFail)(&f) 30 | } 31 | 32 | type foreignFail bool 33 | 34 | func (*foreignFail) IsaForeignReturn() {} 35 | 36 | // ForeignUnify indicates a predicate that succeeds or fails depending 37 | // on the results of a unification. The list of arguments must have 38 | // an even number of elements. Each pair is unified in turn. Variables 39 | // bound during unification become part of the Golog machines's bindings. 40 | func ForeignUnify(ts ...term.Term) ForeignReturn { 41 | if len(ts)%2 != 0 { 42 | panic("Uneven number of arguments to ForeignUnify") 43 | } 44 | return (*foreignUnify)(&ts) 45 | } 46 | 47 | type foreignUnify []term.Term 48 | 49 | func (*foreignUnify) IsaForeignReturn() {} 50 | -------------------------------------------------------------------------------- /prelude/prelude.go: -------------------------------------------------------------------------------- 1 | // Defines the Prolog standard library. All prelude predicates are 2 | // implemented in pure Prolog. Each variable in this package is a predicate 3 | // definition. At init time, they're combined into a single string 4 | // in the Prelude var. 5 | package prelude 6 | 7 | import "strings" 8 | 9 | // After init(), Prelude contains all prelude predicates combined into 10 | // a single large string. One rarely addresses this variable directly 11 | // because golog.NewMachine() does it for you. 12 | var Prelude string 13 | 14 | func init() { 15 | Prelude = strings.Join([]string{ 16 | Ignore1, 17 | Length2, 18 | Memberchk2, 19 | Phrase2, 20 | Phrase3, 21 | Sort2, 22 | }, "\n\n") 23 | } 24 | 25 | var Ignore1 = ` 26 | ignore(A) :- 27 | call(A), 28 | !. 29 | ignore(_). 30 | ` 31 | 32 | var Length2 = ` 33 | length(Xs, N) :- 34 | length(Xs, 0, N). 35 | 36 | length([], N, N) :- !. 37 | length([_|T], N0, N) :- 38 | % N0 \= N 39 | succ(N0, N1), 40 | length(T, N1, N). 41 | ` 42 | 43 | var Memberchk2 = ` 44 | memberchk(X,[X|_]) :- !. 45 | memberchk(X,[_|T]) :- 46 | memberchk(X,T). 47 | ` 48 | 49 | // phrase(:DCGBody, ?List, ?Rest) is nondet. 50 | // 51 | // True when DCGBody applies to the difference List/Rest. 52 | var Phrase3 = ` 53 | phrase(Dcg, Head, Tail) :- 54 | call(Dcg, Head, Tail). 55 | ` 56 | 57 | // phrase(:DCGBody, ?List) is nondet. 58 | // 59 | // Like phrase(DCG,List,[]). 60 | var Phrase2 = ` 61 | phrase(Dcg, List) :- 62 | call(Dcg, List, []). 63 | ` 64 | 65 | // sort(+List, -Sorted) is det. 66 | // 67 | // Like msort/2 but removes duplicates. 68 | var Sort2 = ` 69 | sort(List, Sorted) :- 70 | msort(List, Duplicates), 71 | '$consolidate'(Duplicates, Sorted). 72 | 73 | % helper predicate that removes adjacent duplicates from a list 74 | '$consolidate'([], []). 75 | '$consolidate'([X], [X]). 76 | '$consolidate'([X,Y|Rest], Result) :- 77 | ( X = Y -> 78 | '$consolidate'([Y|Rest], Result) 79 | ; % otherwise -> 80 | '$consolidate'([Y|Rest], Tail), 81 | Result = [X|Tail] 82 | ). 83 | ` 84 | -------------------------------------------------------------------------------- /term/atom.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import . "fmt" 4 | 5 | type Atom string 6 | 7 | // NewAtom creates a new atom with the given name. 8 | // This is just a 0-arity compound term, for now. Eventually, it will 9 | // have an optimized implementation. 10 | func NewAtom(name string) Callable { 11 | return (*Atom)(&name) 12 | } 13 | 14 | // Unlikely to be useful outside of the parser 15 | func NewAtomFromLexeme(possiblyQuotedName string) Callable { 16 | if len(possiblyQuotedName) == 0 { 17 | panic("Atoms must have some content") 18 | } 19 | name := possiblyQuotedName 20 | 21 | // remove quote characters, if they exist 22 | runes := []rune(possiblyQuotedName) 23 | if runes[0] == '\'' { 24 | if runes[len(runes)-1] == '\'' { 25 | raw := runes[1 : len(runes)-1] 26 | unescaped := make([]rune, len(raw)) 27 | var i, j int 28 | for i < len(raw) { 29 | if raw[i] == '\\' && i < len(raw)-1 && raw[i+1] == '\'' { 30 | unescaped[j] = '\'' 31 | i += 2 32 | } else { 33 | unescaped[j] = raw[i] 34 | i++ 35 | } 36 | j++ 37 | } 38 | name = string(unescaped[0:j]) 39 | } else { 40 | msg := Sprintf("Atom needs closing quote: %s", possiblyQuotedName) 41 | panic(msg) 42 | } 43 | } 44 | 45 | return NewAtom(name) 46 | } 47 | 48 | func (self *Atom) Name() string { 49 | return string(*self) 50 | } 51 | func (self *Atom) Arity() int { 52 | return 0 53 | } 54 | func (self *Atom) Arguments() []Term { 55 | return make([]Term, 0) 56 | } 57 | func (self *Atom) String() string { 58 | return QuoteFunctor(self.Name()) 59 | } 60 | func (self *Atom) Type() int { 61 | return AtomType 62 | } 63 | func (self *Atom) Indicator() string { 64 | return Sprintf("%s/0", self.Name()) 65 | } 66 | 67 | func (self *Atom) ReplaceVariables(env Bindings) Term { 68 | return self 69 | } 70 | 71 | func (a *Atom) Unify(e Bindings, b Term) (Bindings, error) { 72 | switch t := b.(type) { 73 | case *Variable: 74 | return b.Unify(e, a) 75 | case *Atom: 76 | if *a == *t { 77 | return e, nil 78 | } 79 | return e, CantUnify 80 | default: 81 | return e, CantUnify 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /database_test.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import . "github.com/mndrix/golog/term" 4 | 5 | import "github.com/mndrix/golog/read" 6 | 7 | import "testing" 8 | 9 | func TestAsserta(t *testing.T) { 10 | db0 := NewDatabase() 11 | db1 := db0.Asserta(NewAtom("alpha")) 12 | db2 := db1.Asserta(NewAtom("beta")) 13 | 14 | db3 := db2.Asserta(read.Term_(`foo(one,two) :- alpha.`)) 15 | t.Logf(db3.String()) // helpful for debugging 16 | 17 | // do we have the right number of clauses? 18 | if db0.ClauseCount() != 0 { 19 | t.Errorf("db0: wrong number of clauses: %d", db0.ClauseCount()) 20 | } 21 | if db1.ClauseCount() != 1 { 22 | t.Errorf("db0: wrong number of clauses: %d", db0.ClauseCount()) 23 | } 24 | if db2.ClauseCount() != 2 { 25 | t.Errorf("db0: wrong number of clauses: %d", db0.ClauseCount()) 26 | } 27 | if db3.ClauseCount() != 3 { 28 | t.Errorf("db0: wrong number of clauses: %d", db0.ClauseCount()) 29 | } 30 | 31 | // is alpha/0 present where it should be? 32 | if cs := db1.Candidates_(NewAtom("alpha")); len(cs) != 1 { 33 | t.Errorf("db1: can't find alpha/0") 34 | } 35 | if cs := db2.Candidates_(NewAtom("alpha")); len(cs) != 1 { 36 | t.Errorf("db2: can't find alpha/0") 37 | } 38 | if cs := db3.Candidates_(NewAtom("alpha")); len(cs) != 1 { 39 | t.Errorf("db3: can't find alpha/0") 40 | } 41 | 42 | // is beta/0 present where it should be? 43 | if _, err := db1.Candidates(NewAtom("beta")); err == nil { 44 | t.Errorf("db1: shouldn't have found beta/0") 45 | } 46 | if cs := db2.Candidates_(NewAtom("beta")); len(cs) != 1 { 47 | t.Errorf("db2: can't find beta/0") 48 | } 49 | if cs := db3.Candidates_(NewAtom("beta")); len(cs) != 1 { 50 | t.Errorf("db3: can't find beta/0") 51 | } 52 | 53 | // is foo/2 present where it should be? 54 | term := read.Term_(`foo(one,two).`) 55 | if _, err := db1.Candidates(term); err == nil { 56 | t.Errorf("db1: shouldn't have found foo/2") 57 | } 58 | if _, err := db2.Candidates(term); err == nil { 59 | t.Errorf("db2: shouldn't have found foo/2") 60 | } 61 | if cs := db3.Candidates_(term); len(cs) != 1 { 62 | t.Errorf("db3: can't find foo/2") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /clauses.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/mndrix/golog/term" 7 | "github.com/mndrix/ps" 8 | ) 9 | 10 | // clauses represents an ordered list of terms with 11 | // cheap insertion at the front 12 | // and back and deletion from anywhere. 13 | // Each clause has a unique identifier 14 | // which can be used for deletions 15 | type clauses struct { 16 | n int64 // number of terms in collection 17 | lowestId int64 18 | highestId int64 19 | terms ps.Map // maps int64 => Term 20 | } 21 | 22 | // newClauses returns a new, empty list of clauses 23 | func newClauses() *clauses { 24 | var cs clauses 25 | cs.terms = ps.NewMap() 26 | // n, lowestId, highestId correctly default to 0 27 | return &cs 28 | } 29 | 30 | // returns the number of terms stored in the list 31 | func (self *clauses) count() int64 { 32 | return self.n 33 | } 34 | 35 | // cons adds a term to the list's front 36 | func (self *clauses) cons(t term.Term) *clauses { 37 | cs := self.clone() 38 | cs.n++ 39 | cs.lowestId-- 40 | key := strconv.FormatInt(cs.lowestId, 10) 41 | cs.terms = self.terms.Set(key, t) 42 | return cs 43 | } 44 | 45 | // cons adds a term to the list's back 46 | func (self *clauses) snoc(t term.Term) *clauses { 47 | cs := self.clone() 48 | cs.n++ 49 | cs.highestId++ 50 | key := strconv.FormatInt(cs.highestId, 10) 51 | cs.terms = self.terms.Set(key, t) 52 | return cs 53 | } 54 | 55 | // all returns a slice of all terms, in order 56 | func (self *clauses) all() []term.Term { 57 | terms := make([]term.Term, 0) 58 | if self.count() == 0 { 59 | return terms 60 | } 61 | 62 | for i := self.lowestId; i <= self.highestId; i++ { 63 | key := strconv.FormatInt(i, 10) 64 | t, ok := self.terms.Lookup(key) 65 | if ok { 66 | terms = append(terms, t.(term.Term)) 67 | } 68 | } 69 | return terms 70 | } 71 | 72 | // invoke a callback on each clause 73 | func (self *clauses) forEach(f func(term.Term)) { 74 | for _, t := range self.all() { 75 | f(t) 76 | } 77 | } 78 | 79 | // returns a copy of this clause list 80 | func (self *clauses) clone() *clauses { 81 | cs := *self 82 | return &cs 83 | } 84 | -------------------------------------------------------------------------------- /prolog_test.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | // This file runs Go tests for Prolog test files under a 't' directory. 4 | // This gives us an easy way to write many Prolog tests without writing 5 | // a bunch of manual Go tests. Because the tests are written in Prolog 6 | // they can be reused by other Prolog implementations. 7 | 8 | import "os" 9 | import "testing" 10 | import "github.com/mndrix/golog/read" 11 | import "github.com/mndrix/golog/term" 12 | import . "github.com/mndrix/golog/util" 13 | 14 | func TestPureProlog(t *testing.T) { 15 | // find all t/*.pl files 16 | file, err := os.Open("t") 17 | MaybePanic(err) 18 | names, err := file.Readdirnames(-1) 19 | 20 | useModule := read.Term_(`:- use_module(library(tap)).`) 21 | env := term.NewBindings() 22 | 23 | // run tests found in each file 24 | for _, name := range names { 25 | if name[0] == '.' { 26 | continue // skip hidden files 27 | } 28 | //t.Logf("-------------- %s", name) 29 | openTest := func() *os.File { 30 | f, err := os.Open("t/" + name) 31 | MaybePanic(err) 32 | return f 33 | } 34 | 35 | // which tests does the file have? 36 | pastUseModule := false 37 | tests := make([]term.Term, 0) 38 | terms := read.TermAll_(openTest()) 39 | for _, s := range terms { 40 | x := s.(term.Callable) 41 | if pastUseModule { 42 | if x.Arity() == 2 && x.Name() == ":-" { 43 | tests = append(tests, x.Arguments()[0]) 44 | } else { 45 | tests = append(tests, x) 46 | } 47 | } else { 48 | // look for use_module(library(tap)) declaration 49 | _, err := s.Unify(env, useModule) 50 | if err == nil { 51 | pastUseModule = true 52 | //t.Logf("found use_module directive") 53 | } 54 | } 55 | } 56 | 57 | // run each test in this file 58 | m := NewMachine().Consult(openTest()) 59 | for _, test := range tests { 60 | x := test.(term.Callable) 61 | //t.Logf("proving: %s", test) 62 | canProve := m.CanProve(test) 63 | if x.Arity() > 0 && x.Arguments()[0].String() == "fail" { 64 | if canProve { 65 | t.Errorf("%s: %s should fail", name, test) 66 | } 67 | } else { 68 | if !canProve { 69 | t.Errorf("%s: %s failed", name, test) 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /term/rational.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import "fmt" 4 | import "math/big" 5 | 6 | // Rational is a specialized, internal representation of floats. 7 | // The goal is to facilitate more accurate numeric computations than 8 | // floats allow. This isn't always possible, but it's a helpful optimization 9 | // in many practical circumstances. 10 | type Rational big.Rat 11 | 12 | // NewRational parses a rational's string representation to create a new 13 | // rational value. Panics if the string's is not a valid rational 14 | func NewRational(text string) (*Rational, bool) { 15 | r, ok := new(big.Rat).SetString(text) 16 | return (*Rational)(r), ok 17 | } 18 | 19 | // Constructs a new Rational value from a big.Rat value 20 | func NewBigRat(r *big.Rat) *Rational { 21 | return (*Rational)(r) 22 | } 23 | 24 | func (self *Rational) Value() *big.Rat { 25 | return (*big.Rat)(self) 26 | } 27 | 28 | func (self *Rational) String() string { 29 | val := self.Value() 30 | if val.IsInt() { 31 | return val.RatString() 32 | } 33 | f, _ := val.Float64() 34 | return fmt.Sprintf("%g", f) 35 | } 36 | 37 | func (self *Rational) Type() int { 38 | return FloatType 39 | } 40 | 41 | func (self *Rational) Indicator() string { 42 | return self.String() 43 | } 44 | 45 | func (a *Rational) Unify(e Bindings, b Term) (Bindings, error) { 46 | if IsVariable(b) { 47 | return b.Unify(e, a) 48 | } 49 | if IsRational(b) { 50 | if a.Value().Cmp(b.(*Rational).Value()) == 0 { 51 | return e, nil 52 | } 53 | return e, CantUnify 54 | } 55 | 56 | x := a.Value() 57 | switch b.Type() { 58 | case IntegerType: 59 | y := b.(*Integer) 60 | if !x.IsInt() { 61 | return e, CantUnify 62 | } 63 | if x.Num().Cmp(y.Value()) == 0 { 64 | return e, nil 65 | } 66 | case FloatType: 67 | f, _ := x.Float64() 68 | if f == b.(*Float).Value() { 69 | return e, nil 70 | } 71 | } 72 | 73 | return e, CantUnify 74 | } 75 | 76 | func (self *Rational) ReplaceVariables(env Bindings) Term { 77 | return self 78 | } 79 | 80 | // implement Number interface 81 | func (self *Rational) Float64() float64 { 82 | f, _ := self.Value().Float64() 83 | return f 84 | } 85 | 86 | func (self *Rational) LosslessInt() (*big.Int, bool) { 87 | if self.Value().IsInt() { 88 | return self.Value().Num(), true 89 | } 90 | return nil, false 91 | } 92 | 93 | func (self *Rational) LosslessRat() (*big.Rat, bool) { 94 | return self.Value(), true 95 | } 96 | -------------------------------------------------------------------------------- /cmd/golog/main.go: -------------------------------------------------------------------------------- 1 | // A primitive Prolog top level for Golog. See golog.sh in the repository 2 | // for recommended usage. 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "fmt" 8 | "io" 9 | "os" 10 | "strings" 11 | 12 | "github.com/mndrix/golog" 13 | "github.com/mndrix/golog/read" 14 | "github.com/mndrix/golog/term" 15 | ) 16 | 17 | func main() { 18 | // create a Golog machine 19 | m := initMachine() 20 | 21 | // ?- do(stuff). 22 | in := bufio.NewReader(os.Stdin) 23 | for { 24 | warnf("?- ") 25 | 26 | // process the user's query 27 | query, err := in.ReadString('\n') 28 | if err == io.EOF { 29 | warnf("\n") 30 | os.Exit(0) 31 | } 32 | if err != nil { 33 | warnf("Trouble reading from stdin: %s\n", err) 34 | os.Exit(1) 35 | } 36 | goal, err := read.Term(query) 37 | if err != nil { 38 | warnf("Problem parsing the query: %s\n", err) 39 | continue 40 | } 41 | 42 | // execute user's query 43 | variables := term.Variables(goal) 44 | answers := m.ProveAll(goal) 45 | 46 | // showing 0 results is easy and fun! 47 | if len(answers) == 0 { 48 | warnf("no.\n\n") 49 | continue 50 | } 51 | 52 | // show each answer in turn 53 | if variables.Size() == 0 { 54 | warnf("yes.\n") 55 | continue 56 | } 57 | for i, answer := range answers { 58 | lines := make([]string, 0) 59 | variables.ForEach(func(name string, variable interface{}) { 60 | v := variable.(*term.Variable) 61 | val := answer.Resolve_(v) 62 | line := fmt.Sprintf("%s = %s", name, val) 63 | lines = append(lines, line) 64 | }) 65 | 66 | warnf("%s", strings.Join(lines, "\n")) 67 | if i == len(answers)-1 { 68 | warnf("\t.\n\n") 69 | } else { 70 | warnf("\t;\n") 71 | } 72 | } 73 | } 74 | } 75 | 76 | // warnf generates formatted output on stderr 77 | func warnf(format string, args ...interface{}) { 78 | fmt.Fprintf(os.Stderr, format, args...) 79 | os.Stderr.Sync() 80 | } 81 | 82 | // initMachine creates a new Golog machine based on command line arguments 83 | func initMachine() golog.Machine { 84 | m := golog.NewInteractiveMachine() 85 | 86 | // are we supposed to load some code into the machine? 87 | if len(os.Args) > 1 { 88 | filename := os.Args[1] 89 | warnf("Opening %s ...\n", filename) 90 | file, err := os.Open(filename) 91 | if err != nil { 92 | warnf("Can't open file: %s\n", err) 93 | os.Exit(1) 94 | } 95 | m = m.Consult(file) 96 | } 97 | 98 | return m 99 | } 100 | -------------------------------------------------------------------------------- /term/pretty.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | ) 7 | 8 | func IsEmptyList(t Term) bool { 9 | return IsAtom(t) && t.(*Atom).Name() == "[]" 10 | } 11 | 12 | func IsString(t Term) bool { 13 | if IsEmptyList(t) { 14 | return true 15 | } 16 | if !IsCompound(t) { 17 | return false 18 | } 19 | // TODO(olegs): Handle circular structures 20 | c := t.(*Compound) 21 | for { 22 | if c.Arity() != 2 { 23 | return false 24 | } 25 | if c.Func != "." { 26 | return false 27 | } 28 | args := c.Arguments() 29 | if !IsInteger(args[0]) { 30 | return false 31 | } 32 | if IsCompound(args[1]) { 33 | c = args[1].(*Compound) 34 | } else if IsEmptyList(args[1]) { 35 | break 36 | } else { 37 | return false 38 | } 39 | } 40 | return true 41 | } 42 | 43 | func IsList(t Term) bool { 44 | if IsEmptyList(t) { 45 | return true 46 | } 47 | if !IsCompound(t) { 48 | return false 49 | } 50 | // TODO(olegs): Handle circular structures 51 | c := t.(*Compound) 52 | for { 53 | if c.Arity() != 2 { 54 | return false 55 | } 56 | if c.Func != "." { 57 | return false 58 | } 59 | args := c.Arguments() 60 | if IsCompound(args[1]) { 61 | c = args[1].(*Compound) 62 | } else if IsEmptyList(args[1]) { 63 | break 64 | } else { 65 | return false 66 | } 67 | } 68 | return true 69 | } 70 | 71 | func PrettyList(t Term) string { 72 | b := bytes.NewBuffer([]byte{}) 73 | _ = b.WriteByte('[') 74 | for !IsEmptyList(t) { 75 | c := t.(*Compound) 76 | _, _ = b.WriteString(c.Arguments()[0].String()) 77 | _ = b.WriteByte(',') 78 | t = c.Arguments()[1] 79 | } 80 | return string(b.Bytes()[:b.Len()-1]) + "]" 81 | } 82 | 83 | func PrettyString(t Term) string { 84 | return "\"" + strings.Replace(RawString(t), "\"", "\\\"", -1) + "\"" 85 | } 86 | 87 | func RawString(t Term) string { 88 | var chars []rune 89 | for !IsEmptyList(t) { 90 | c := t.(*Compound) 91 | code := c.Arguments()[0].(*Integer).Code() 92 | chars = append(chars, code) 93 | t = c.Arguments()[1] 94 | } 95 | return string(chars) 96 | } 97 | 98 | func ListToSlice(t Term) (result []Term) { 99 | for !IsEmptyList(t) { 100 | c := t.(Callable) 101 | result = append(result, c.Arguments()[0]) 102 | t = c.Arguments()[1] 103 | } 104 | return result 105 | } 106 | 107 | func SliceToList(ts []Term) (result Term) { 108 | result = NewAtom("[]") 109 | if len(ts) == 0 { 110 | return result 111 | } 112 | result = NewCallable(".", ts[len(ts)-1], result) 113 | for i := len(ts) - 2; i >= 0; i-- { 114 | result = NewCallable(".", ts[i], result) 115 | } 116 | return result 117 | } 118 | -------------------------------------------------------------------------------- /term/variable.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import . "fmt" 4 | import . "regexp" 5 | import . "github.com/mndrix/golog/util" 6 | 7 | var anonCounter <-chan int64 8 | 9 | func init() { 10 | // goroutine providing a counter for anonymous variables 11 | c := make(chan int64) 12 | var i int64 = 1000 13 | go func() { 14 | for { 15 | c <- i 16 | i++ 17 | } 18 | }() 19 | anonCounter = c 20 | } 21 | 22 | // A Prolog logic variable. See ISO §6.1.2(a) 23 | type Variable struct { 24 | Name string 25 | id int64 // uniquely identifiers this variable 26 | } 27 | 28 | // Creates a new logic variable with the given name. 29 | func NewVar(name string) *Variable { 30 | // sanity check the variable name's syntax 31 | isCapitalized, err := MatchString(`^[A-Z_]`, name) 32 | maybePanic(err) 33 | if !isCapitalized { 34 | panic("Variable names must start with a capital letter or underscore") 35 | } 36 | 37 | // make sure anonymous variables are unique 38 | var i int64 39 | if name == "_" { 40 | i = <-anonCounter 41 | } 42 | return &Variable{ 43 | Name: name, 44 | id: i, 45 | } 46 | } 47 | 48 | // Id returns a unique identifier for this variable 49 | func (self *Variable) Id() int64 { 50 | return self.id 51 | } 52 | 53 | func (self *Variable) Functor() string { 54 | panic("Variables have no Functor()") 55 | } 56 | 57 | func (self *Variable) Arity() int { 58 | panic("Variables have no Arity()") 59 | } 60 | 61 | func (self *Variable) Arguments() []Term { 62 | panic("Variables have no Arguments()") 63 | } 64 | 65 | func (self *Variable) String() string { 66 | if Debugging() && self.Name == "_" { 67 | return self.Indicator() 68 | } 69 | return self.Name 70 | } 71 | 72 | func (self *Variable) Type() int { 73 | return VariableType 74 | } 75 | 76 | func (self *Variable) Indicator() string { 77 | return Sprintf("_V%d", self.id) 78 | } 79 | 80 | func (a *Variable) Unify(e Bindings, b Term) (Bindings, error) { 81 | var aTerm, bTerm Term 82 | 83 | // a variable always unifies with itself 84 | if IsVariable(b) { 85 | if a.Indicator() == b.Indicator() { 86 | return e, nil 87 | } 88 | bTerm = e.Resolve_(b.(*Variable)) 89 | } else { 90 | bTerm = b 91 | } 92 | 93 | // resolve any previous bindings 94 | aTerm = e.Resolve_(a) 95 | 96 | // bind unbound variables 97 | if IsVariable(aTerm) { 98 | return e.Bind(aTerm.(*Variable), b) 99 | } 100 | if IsVariable(bTerm) { 101 | return e.Bind(bTerm.(*Variable), a) 102 | } 103 | 104 | // otherwise, punt 105 | return aTerm.Unify(e, bTerm) 106 | } 107 | 108 | func (self *Variable) ReplaceVariables(env Bindings) Term { 109 | return env.Resolve_(self) 110 | } 111 | 112 | func (self *Variable) WithNewId() *Variable { 113 | return &Variable{ 114 | Name: self.Name, 115 | id: <-anonCounter, 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /read/read_test.go: -------------------------------------------------------------------------------- 1 | package read 2 | 3 | import "testing" 4 | 5 | func TestBasic(t *testing.T) { 6 | 7 | // read single terms 8 | single := make(map[string]string) 9 | single[`hello. world.`] = `hello` // read only the first term 10 | single[`a + b.`] = `+(a, b)` 11 | single[`first, second.`] = `','(first, second)` 12 | single[`\+ j.`] = `\+(j)` 13 | single[`a + b*c.`] = `+(a, *(b, c))` // test precedence 14 | single[`a + b + c.`] = `+(+(a, b), c)` // test left associativity 15 | single[`a^b^c.`] = `^(a, ^(b, c))` // test right associativity 16 | single[`x(a).`] = `x(a)` 17 | single[`x(a,b,c).`] = `x(a, b, c)` 18 | single[`x((a,b)).`] = `x(','(a, b))` 19 | single[`x(A).`] = `x(A)` 20 | single[`amen :- true.`] = `:-(amen, true)` 21 | single[`bee(X) :- X=b.`] = `:-(bee(X), =(X, b))` 22 | single[`zero(X) :- 0 =:= X.`] = `:-(zero(X), =:=(0, X))` 23 | single[`succ(0,1) :- true.`] = `:-(succ(0, 1), true)` 24 | single[`pi(3.14159).`] = `pi(3.14159)` 25 | single[`etc(_,_).`] = `etc(_, _)` 26 | single[`'one two'(three) :- four.`] = `:-('one two'(three), four)` 27 | single[`[].`] = `[]` // based on examples in §6.3.5.1 28 | single[`[a].`] = `[a]` 29 | single[`[a,b].`] = `[a,b]` 30 | single[`[a|b].`] = `'.'(a, b)` 31 | single[`"".`] = `[]` 32 | single[`"hi".`] = `"hi"` 33 | single[`"✓".`] = `"✓"` // 0x2713 Unicode 34 | single[`''.`] = `''` // empty atom 35 | single[`'\''.`] = `'\''` // single quote (escaped) 36 | single[`'hi'.`] = `hi` 37 | single[`'collector\'s'.`] = `'collector\'s'` 38 | single[`(true->true).`] = `->(true, true)` 39 | single[`(true->(true)).`] = `->(true, true)` 40 | single[`(if->then;else).`] = `;(->(if, then), else)` 41 | single[`A = 3.`] = `=(A, 3)` 42 | for test, wanted := range single { 43 | got, err := Term(test) 44 | maybePanic(err) 45 | if got.String() != wanted { 46 | t.Errorf("Reading `%s` gave `%s` instead of `%s`", test, got, wanted) 47 | } 48 | } 49 | 50 | // read single terms (with user-defined operators) 51 | user := make(map[string]string) 52 | user[`a x b.`] = `x(a, b)` 53 | user[`a x b x c.`] = `x(x(a, b), c)` 54 | user[`two weeks.`] = `weeks(two)` 55 | for test, wanted := range user { 56 | r, err := NewTermReader(test) 57 | maybePanic(err) 58 | r.Op(400, yfx, "x") 59 | r.Op(200, yf, "weeks") 60 | 61 | got, err := r.Next() 62 | maybePanic(err) 63 | if got.String() != wanted { 64 | t.Errorf("Reading `%s` gave `%s` instead of `%s`", test, got, wanted) 65 | } 66 | } 67 | 68 | // reading a couple simple terms 69 | oneTwoStr := `one. two.` 70 | oneTwo, err := TermAll(oneTwoStr) 71 | maybePanic(err) 72 | if oneTwo[0].String() != "one" { 73 | t.Errorf("Expected `one` in %#v", oneTwo) 74 | } 75 | if oneTwo[1].String() != "two" { 76 | t.Errorf("Expected `two` in %#v", oneTwo) 77 | } 78 | } 79 | 80 | func TestEolComment(t *testing.T) { 81 | terms := TermAll_(` 82 | one. % shouldn't hide following term 83 | two. 84 | `) 85 | if len(terms) != 2 { 86 | t.Errorf("Wrong number of terms: %d vs 2", len(terms)) 87 | } 88 | if terms[0].String() != "one" { 89 | t.Errorf("Expected `one` in %#v", terms) 90 | } 91 | if terms[1].String() != "two" { 92 | t.Errorf("Expected `two` in %#v", terms) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /term/bindings.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import "fmt" 4 | import "github.com/mndrix/ps" 5 | 6 | // Returned by Bind() if the variable in question has already 7 | // been bound to a value. 8 | var AlreadyBound = fmt.Errorf("Variable was already bound") 9 | 10 | // Returned by Value() and ByName() if the variable in question has 11 | // no bindings yet. 12 | var NotBound = fmt.Errorf("Variable is not bound") 13 | 14 | type Bindings interface { 15 | // Bind returns a new Environment, like the old one, but with the variable 16 | // bound to its new value; error is AlreadyBound if the variable had a 17 | // value previously. 18 | Bind(*Variable, Term) (Bindings, error) 19 | 20 | // ByName is similar to Value() but it searches for a variable 21 | // binding by using that variable's name. Names can be ambiguous so 22 | // use with caution. 23 | ByName(string) (Term, error) 24 | 25 | // ByName_ is like ByName() but panics on error. 26 | ByName_(string) Term 27 | 28 | // Resolve follows bindings recursively until a term is found for 29 | // which no binding exists. If you want to know the value of a 30 | // variable, this is your best bet. 31 | Resolve(*Variable) (Term, error) 32 | 33 | // Resolve_ is like Resolve() but panics on error. 34 | Resolve_(*Variable) Term 35 | 36 | // Size returns the number of variable bindings in this environment. 37 | Size() int 38 | 39 | // Value returns the value of a bound variable; error is NotBound if 40 | // the variable is free. 41 | Value(*Variable) (Term, error) 42 | 43 | // WithNames returns a new bindings with human-readable names attached 44 | // for convenient lookup. Panics if names have already been attached. 45 | WithNames(ps.Map) Bindings 46 | } 47 | 48 | // NewBindings returns a new, empty bindings value. 49 | func NewBindings() Bindings { 50 | var newEnv envMap 51 | newEnv.bindings = ps.NewMap() 52 | newEnv.names = ps.NewMap() 53 | return &newEnv 54 | } 55 | 56 | type envMap struct { 57 | bindings ps.Map // v.Indicator() => Term 58 | names ps.Map // v.Name => *Variable 59 | } 60 | 61 | func (self *envMap) Bind(v *Variable, val Term) (Bindings, error) { 62 | _, ok := self.bindings.Lookup(v.Indicator()) 63 | if ok { 64 | // binding already exists for this variable 65 | return self, AlreadyBound 66 | } 67 | 68 | // at this point, we know that v is a free variable 69 | 70 | // create a new environment with the binding in place 71 | newEnv := self.clone() 72 | newEnv.bindings = self.bindings.Set(v.Indicator(), val) 73 | 74 | // error slot in return is for attributed variables someday 75 | return newEnv, nil 76 | } 77 | func (self *envMap) Resolve_(v *Variable) Term { 78 | r, err := self.Resolve(v) 79 | maybePanic(err) 80 | return r 81 | } 82 | 83 | func (self *envMap) Resolve(v *Variable) (Term, error) { 84 | for { 85 | t, err := self.Value(v) 86 | if err == NotBound { 87 | return v, nil 88 | } 89 | if err != nil { 90 | return nil, err 91 | } 92 | if IsVariable(t) { 93 | v = t.(*Variable) 94 | } else { 95 | return t.ReplaceVariables(self), nil 96 | } 97 | } 98 | } 99 | func (self *envMap) Size() int { 100 | return self.bindings.Size() 101 | } 102 | func (self *envMap) Value(v *Variable) (Term, error) { 103 | name := v.Indicator() 104 | value, ok := self.bindings.Lookup(name) 105 | if !ok { 106 | return nil, NotBound 107 | } 108 | return value.(Term), nil 109 | } 110 | func (self *envMap) clone() *envMap { 111 | newEnv := *self 112 | return &newEnv 113 | } 114 | 115 | func (self *envMap) ByName(name string) (Term, error) { 116 | v, ok := self.names.Lookup(name) 117 | if !ok { 118 | return nil, NotBound 119 | } 120 | return self.Resolve(v.(*Variable)) 121 | } 122 | 123 | func (self *envMap) ByName_(name string) Term { 124 | x, err := self.ByName(name) 125 | maybePanic(err) 126 | return x 127 | } 128 | 129 | func (self *envMap) String() string { 130 | return self.bindings.String() 131 | } 132 | 133 | func (self *envMap) WithNames(names ps.Map) Bindings { 134 | if !self.names.IsNil() { 135 | panic("Can't set names when names have already been set") 136 | } 137 | 138 | b := self.clone() 139 | b.names = names 140 | return b 141 | } 142 | -------------------------------------------------------------------------------- /doc/architecture.md: -------------------------------------------------------------------------------- 1 | Architecture 2 | ============ 3 | 4 | This document describes how the Golog interpreter is designed. Understanding this document is not necessary for running Prolog code. It's intended to help those who want to hack on the interpreter. 5 | 6 | A Golog machine tracks the following state: 7 | 8 | * database 9 | * foreign predicates 10 | * environment 11 | * disjunctions (called disjs) 12 | * conjunctions (called conjs) 13 | 14 | Database 15 | -------- 16 | 17 | The database holds all predicates defined in Prolog. It's conceptually a map from predicate indicators (foo/2) to a list of terms. Those terms define the predicate's clauses. A database may support indexing. It may represent clauses internally using whatever means seems reasonble. The database is encouraged to inspect all clauses, their shape and number when deciding how to represent clauses internally. 18 | 19 | Eventually, a Golog machine will map atoms (module names) to databases. In this scenario each database represents a module. Databases might also become first class values that are garbage collected like other values. 20 | 21 | Foreign Predicates 22 | ------------------ 23 | 24 | This is similar to a database and may eventually be merged with it. Conceptually, it's a map from predicate indicators to native Go functions. These Go functions implement the associated predicates. 25 | 26 | Environment 27 | ----------- 28 | 29 | An environment encapsulates variable bindings. Unification occurs in the presence of this environment. At the moment, unification doesn't replace variables in terms, it just adds more bindings to the environment. It might be reasonable to "compact" the environment occassionally by discarding unneeded bindings. 30 | 31 | Disjunctions 32 | ------------ 33 | 34 | This is a stack of choice points. Each time Golog encounters nondeterminism, it pushes some choice points onto the disjunction stack. Special choice points, called cut barriers, act as sentinels on this stack. A cut removes all choice points stacked on top of one of these barriers. Backtracking pops a choice point off this stack and follows it to produce a new machine. That machine is typically a snapshot of the Golog machine as it existed when we first encountered the choice point. Following the choice point replaces the current Golog machine with that one. A side effect is discarding all other state that has accumulated since. 35 | 36 | This design makes backtracking very easy to understand. We just revert back to the state of the machine as it existed before. Go's garbage collector takes care of any state that's no longer needed. 37 | 38 | The disjunction stack can be thought of as "computations we haven't tried yet." Each of those computations is represented as a choice point. A choice point is just a function which returns a machine. That machine could come from anywhere. It could be a snapshot of a machine we saw earlier. It could be the result of executing a machine in parallel. It could be the result of executing a machine on several servers, etc. 39 | 40 | Conjunctions 41 | ------------ 42 | 43 | This is a stack of goals yet to be proven. It's the machine's continuation. A new goal is pushed onto this empty stack. Executing the top goal of this stack replaces the goal with its corresponding clause body. 44 | 45 | This design makes it easy to add call-with-current-continuation to Golog at some point. 46 | 47 | Execution 48 | --------- 49 | 50 | Take a goal off the conjunction stack. If the goal matches a clause head, push the clause's body onto the conjunction stack. If the goal might match other clause heads, push those other clauses onto the disjunction stack. If the goal fails, take a choice point off the disjunction stack and follow it to produce a new machine. Continue execution on this new machine. 51 | 52 | 53 | Immutability 54 | ============ 55 | 56 | All data structures in a Golog machine are immutable. Operations on a Golog machine produce a new machine, leaving the old one completely intact. I initially chose this approach because it makes backtracking trivial. It also makes it easy to build a Golog machine during Go's init() and then use that machine in many different web requests without affecting the original machine. 57 | 58 | It looks like this design might also make or-parallel and distributed execution easy to implement. Time and experimentation will tell. 59 | -------------------------------------------------------------------------------- /term/integer.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import "fmt" 4 | import "math/big" 5 | 6 | // Integer represents an unbounded, signed integer value 7 | type Integer big.Int 8 | 9 | // NewInt parses an integer's string representation to create a new 10 | // integer value. Panics if the string's is not a valid integer 11 | func NewInt(text string) Number { 12 | if len(text) == 0 { 13 | panic("Empty string is not a valid integer") 14 | } 15 | 16 | // see §6.4.4 for syntax details 17 | if text[0] == '0' && len(text) >= 3 { 18 | switch text[1] { 19 | case '\'': 20 | return parseEscape(text[2:]) 21 | case 'b': 22 | return parseInteger("%b", text[2:]) 23 | case 'o': 24 | return parseInteger("%o", text[2:]) 25 | case 'x': 26 | return parseInteger("%x", text[2:]) 27 | default: 28 | return parseInteger("%d", text) 29 | } 30 | } 31 | return parseInteger("%d", text) 32 | } 33 | 34 | // helper for when an int64 is already available 35 | func NewInt64(i int64) Number { 36 | return (*Integer)(big.NewInt(i)) 37 | } 38 | 39 | // helper for when a big.Int is already available 40 | func NewBigInt(val *big.Int) Number { 41 | return (*Integer)(val) 42 | } 43 | 44 | // NewCode returns an integer whose value is the character code if 45 | // the given rune. 46 | func NewCode(c rune) *Integer { 47 | return (*Integer)(big.NewInt(int64(c))) 48 | } 49 | 50 | func parseInteger(format, text string) *Integer { 51 | i := new(big.Int) 52 | n, err := fmt.Sscanf(text, format, i) 53 | maybePanic(err) 54 | if n == 0 { 55 | panic("Parsed no integers") 56 | } 57 | 58 | return (*Integer)(i) 59 | } 60 | 61 | // see "single quoted character" - §6.4.2.1 62 | func parseEscape(text string) *Integer { 63 | var r rune 64 | if text[0] == '\\' { 65 | if len(text) < 2 { 66 | msg := fmt.Sprintf("Invalid integer character constant: %s", text) 67 | panic(msg) 68 | } 69 | switch text[1] { 70 | // "meta escape sequence" - §6.4.2.1 71 | case '\\': 72 | r = '\\' 73 | case '\'': 74 | r = '\'' 75 | case '"': 76 | r = '"' 77 | case '`': 78 | r = '`' 79 | 80 | // "control escape char" - §6.4.2.1 81 | case 'a': 82 | r = '\a' 83 | case 'b': 84 | r = '\b' 85 | case 'f': 86 | r = '\f' 87 | case 'n': 88 | r = '\n' 89 | case 'r': 90 | r = '\r' 91 | case 's': 92 | r = ' ' // SWI-Prolog extension 93 | case 't': 94 | r = '\t' 95 | case 'v': 96 | r = '\v' 97 | 98 | // "hex escape char" - §6.4.2.1 99 | case 'x': 100 | return parseInteger("%x", text[2:len(text)-1]) 101 | 102 | // "octal escape char" - §6.4.2.1 103 | case '0', '1', '2', '3', '4', '5', '6', '7': 104 | return parseInteger("%o", text[1:len(text)-1]) 105 | 106 | // unexpected escape sequence 107 | default: 108 | msg := fmt.Sprintf("Invalid character escape sequence: %s", text) 109 | panic(msg) 110 | } 111 | } else { 112 | // "non quote char" - §6.4.2.1 113 | runes := []rune(text) 114 | r = runes[0] 115 | } 116 | code := int64(r) 117 | return (*Integer)(big.NewInt(code)) 118 | } 119 | 120 | func (self *Integer) Value() *big.Int { 121 | return (*big.Int)(self) 122 | } 123 | 124 | // treat this integer as a character code. should be a method on 125 | // a Code interface someday 126 | func (self *Integer) Code() rune { 127 | i := (*big.Int)(self) 128 | return rune(i.Int64()) 129 | } 130 | 131 | func (self *Integer) String() string { 132 | return self.Value().String() 133 | } 134 | 135 | func (self *Integer) Type() int { 136 | return IntegerType 137 | } 138 | 139 | func (self *Integer) Indicator() string { 140 | return self.String() 141 | } 142 | 143 | func (a *Integer) Unify(e Bindings, b Term) (Bindings, error) { 144 | if IsVariable(b) { 145 | return b.Unify(e, a) 146 | } 147 | if IsInteger(b) { 148 | if a.Value().Cmp(b.(*Integer).Value()) == 0 { 149 | return e, nil 150 | } 151 | } 152 | 153 | return e, CantUnify 154 | } 155 | 156 | func (self *Integer) ReplaceVariables(env Bindings) Term { 157 | return self 158 | } 159 | 160 | // implement Number interface 161 | func (self *Integer) Float64() float64 { 162 | return float64(self.Value().Int64()) 163 | } 164 | 165 | func (self *Integer) LosslessInt() (*big.Int, bool) { 166 | return self.Value(), true 167 | } 168 | 169 | func (self *Integer) LosslessRat() (*big.Rat, bool) { 170 | r := new(big.Rat).SetFrac(self.Value(), big.NewInt(1)) 171 | return r, true 172 | } 173 | -------------------------------------------------------------------------------- /database.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import . "fmt" 4 | import . "github.com/mndrix/golog/term" 5 | import . "github.com/mndrix/golog/util" 6 | 7 | import ( 8 | "bytes" 9 | 10 | "github.com/mndrix/ps" 11 | ) 12 | 13 | // Database is an immutable Prolog database. All write operations on the 14 | // database produce a new database without affecting the previous one. 15 | // A database is a mapping from predicate indicators (foo/3) to clauses. 16 | // The database may or may not implement indexing. It's unusual to 17 | // interact with databases directly. One usually calls methods on Machine 18 | // instead. 19 | type Database interface { 20 | // Asserta adds a term to the database at the start of any existing 21 | // terms with the same name and arity. 22 | Asserta(Term) Database 23 | 24 | // Assertz adds a term to the database at the end of any existing 25 | // terms with the same name and arity. 26 | Assertz(Term) Database 27 | 28 | // Candidates() returns a list of clauses that might unify with a term. 29 | // Returns error if no predicate with appropriate 30 | // name and arity has been defined. 31 | Candidates(Term) ([]Term, error) 32 | 33 | // Candidates_() is like Candidates() but panics on error. 34 | Candidates_(Term) []Term 35 | 36 | // ClauseCount returns the number of clauses in the database 37 | ClauseCount() int 38 | 39 | // String returns a string representation of the entire database 40 | String() string 41 | } 42 | 43 | // NewDatabase returns a new, empty database 44 | func NewDatabase() Database { 45 | var db mapDb 46 | db.clauseCount = 0 47 | db.predicates = ps.NewMap() 48 | return &db 49 | } 50 | 51 | type mapDb struct { 52 | clauseCount int // number of clauses in the database 53 | predicates ps.Map // term indicator => *clauses 54 | } 55 | 56 | func (self *mapDb) Asserta(term Term) Database { 57 | return self.assert('a', term) 58 | } 59 | 60 | func (self *mapDb) Assertz(term Term) Database { 61 | return self.assert('z', term) 62 | } 63 | 64 | func (self *mapDb) assert(side rune, term Term) Database { 65 | var newMapDb mapDb 66 | var cs *clauses 67 | 68 | // find the indicator under which this term is classified 69 | indicator := term.Indicator() 70 | if IsClause(term) { 71 | // ':-' uses the indicator of its head term 72 | indicator = Head(term).Indicator() 73 | } 74 | 75 | oldClauses, ok := self.predicates.Lookup(indicator) 76 | if ok { // clauses exist for this predicate 77 | switch side { 78 | case 'a': 79 | cs = oldClauses.(*clauses).cons(term) 80 | case 'z': 81 | cs = oldClauses.(*clauses).snoc(term) 82 | } 83 | } else { // brand new predicate 84 | cs = newClauses().snoc(term) 85 | } 86 | 87 | newMapDb.clauseCount = self.clauseCount + 1 88 | newMapDb.predicates = self.predicates.Set(indicator, cs) 89 | return &newMapDb 90 | } 91 | 92 | func (self *mapDb) Candidates_(t Term) []Term { 93 | ts, err := self.Candidates(t) 94 | if err != nil { 95 | panic(err) 96 | } 97 | return ts 98 | } 99 | 100 | func (self *mapDb) Candidates(t Term) ([]Term, error) { 101 | indicator := t.Indicator() 102 | cs, ok := self.predicates.Lookup(indicator) 103 | if !ok { // this predicate hasn't been defined 104 | return nil, Errorf("Undefined predicate: %s", indicator) 105 | } 106 | 107 | // quick return for an atom term 108 | if !IsCompound(t) { 109 | return cs.(*clauses).all(), nil 110 | } 111 | 112 | // ignore clauses that can't possibly unify with our term 113 | candidates := make([]Term, 0) 114 | cs.(*clauses).forEach(func(clause Term) { 115 | if !IsCompound(clause) { 116 | Debugf(" ... discarding. Not compound term\n") 117 | return 118 | } 119 | head := clause 120 | if IsClause(clause) { 121 | head = Head(clause) 122 | } 123 | if t.(*Compound).MightUnify(head.(*Compound)) { 124 | Debugf(" ... adding to candidates: %s\n", clause) 125 | candidates = append(candidates, clause) 126 | } 127 | }) 128 | Debugf(" final candidates = %s\n", candidates) 129 | return candidates, nil 130 | } 131 | 132 | func (self *mapDb) ClauseCount() int { 133 | return self.clauseCount 134 | } 135 | 136 | func (self *mapDb) String() string { 137 | var buf bytes.Buffer 138 | 139 | keys := self.predicates.Keys() 140 | for _, key := range keys { 141 | cs, _ := self.predicates.Lookup(key) 142 | cs.(*clauses).forEach(func(t Term) { Fprintf(&buf, "%s.\n", t) }) 143 | } 144 | return buf.String() 145 | } 146 | -------------------------------------------------------------------------------- /term/term_test.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import . "regexp" 4 | import "testing" 5 | 6 | func TestAtom(t *testing.T) { 7 | atom := NewAtom("prolog") 8 | if atom.Arity() != 0 { 9 | t.Errorf("atom's arity wasn't 0 it was %d", atom.Arity()) 10 | } 11 | 12 | if atom.Name() != "prolog" { 13 | t.Errorf("atom's has the wrong functor: %s", atom.Name()) 14 | } 15 | 16 | if atom.Indicator() != "prolog/0" { 17 | t.Errorf("atom's indicator is wrong: %s", atom.Indicator()) 18 | } 19 | 20 | if atom.String() != "prolog" { 21 | t.Errorf("wrong string representationt: %s", atom.String()) 22 | } 23 | } 24 | 25 | func TestVariable(t *testing.T) { 26 | v := NewVar("X") 27 | if n := v.String(); n != "X" { 28 | t.Errorf("variable X has the wrong name %s", n) 29 | } 30 | 31 | a0 := NewVar("_") 32 | ok, err := MatchString(`^_`, a0.Indicator()) 33 | maybePanic(err) 34 | if !ok { 35 | t.Errorf("a0 has the wrong indicator") 36 | } 37 | t.Logf("a0: %s", a0) 38 | 39 | a1 := NewVar("_") 40 | ok, err = MatchString(`^_`, a1.Indicator()) 41 | maybePanic(err) 42 | if !ok { 43 | t.Errorf("a1 has the wrong indicator") 44 | } 45 | t.Logf("a1: %s", a1) 46 | 47 | if a0.Indicator() == a1.Indicator() { 48 | t.Errorf("anonymous variables are accidentally sharing names") 49 | } 50 | } 51 | 52 | func TestShallowTerm(t *testing.T) { 53 | shallow := NewCallable("prolog", NewAtom("in"), NewAtom("go")) 54 | 55 | if shallow.Arity() != 2 { 56 | t.Errorf("wrong arity: %d", shallow.Arity()) 57 | } 58 | 59 | if shallow.Name() != "prolog" { 60 | t.Errorf("wrong functor: %s", shallow.Name()) 61 | } 62 | 63 | if shallow.Indicator() != "prolog/2" { 64 | t.Errorf("indicator is wrong: %s", shallow.Indicator()) 65 | } 66 | 67 | if shallow.String() != "prolog(in, go)" { 68 | t.Errorf("wrong string representation: %s", shallow.String()) 69 | } 70 | } 71 | 72 | func TestQuoting(t *testing.T) { 73 | // functors entirely out of punctuation don't need quotes 74 | x := NewCallable(":-", NewCallable("foo"), NewCallable("bar")) 75 | if x.String() != ":-(foo, bar)" { 76 | t.Errorf("Clause has wrong quoting: %s", x.String()) 77 | } 78 | 79 | // functors with punctuation and letters need quoting 80 | x = NewCallable("/a", NewCallable("foo"), NewCallable("bar")) 81 | if x.String() != "'/a'(foo, bar)" { 82 | t.Errorf("Clause has wrong quoting: %s", x.String()) 83 | } 84 | 85 | // initial capital letters must be quoted 86 | x = NewCallable("Caps") 87 | if x.String() != "'Caps'" { 88 | t.Errorf("Capitalized atom has wrong quoting: %s", x.String()) 89 | } 90 | 91 | // all lowercase atoms don't need quotes 92 | x = NewCallable("lower") 93 | if x.String() != "lower" { 94 | t.Errorf("Atom shouldn't be quoted: %s", x.String()) 95 | } 96 | 97 | // initial lowercase atoms don't need quotes 98 | x = NewCallable("lower_Then_Caps") 99 | if x.String() != "lower_Then_Caps" { 100 | t.Errorf("Mixed case atom shouldn't be quoted: %s", x.String()) 101 | } 102 | 103 | // empty list atom doesn't need quoting, but cons does 104 | x = NewCallable("[]") 105 | if x.String() != "[]" { 106 | t.Errorf("empty list atom shouldn't be quoted: %s", x.String()) 107 | } 108 | x = NewCallable(".") 109 | if x.String() != "'.'" { 110 | t.Errorf("cons must be quoted: %s", x.String()) 111 | } 112 | 113 | // cut doesn't need quoting 114 | x = NewCallable("!") 115 | if x.String() != "!" { 116 | t.Errorf("cut shouldn't be quoted: %s", x.String()) 117 | } 118 | } 119 | 120 | func TestInteger(t *testing.T) { 121 | tests := make(map[string]Number) 122 | tests[`123`] = NewInt64(123) 123 | tests[`0xf`] = NewInt64(15) 124 | tests[`0o10`] = NewInt64(8) 125 | tests[`0b10`] = NewInt64(2) 126 | tests[`0' `] = NewInt64(32) 127 | tests[`0'\s`] = NewInt64(32) // SWI-Prolog extension 128 | tests[`0',`] = NewInt64(44) 129 | tests["0'\\x2218\\"] = NewInt64(0x2218) 130 | tests["0'\\21030\\"] = NewInt64(0x2218) 131 | 132 | for text, expected := range tests { 133 | x := NewInt(text) 134 | if NumberCmp(x, expected) != 0 { 135 | t.Errorf("Integer `%s` parsed as `%s` wanted `%s`", text, x, expected) 136 | } 137 | } 138 | 139 | large := NewInt(`989050597012214992552592926549`) 140 | if large.String() != `989050597012214992552592926549` { 141 | t.Errorf("Can't handle large integers") 142 | } 143 | } 144 | 145 | func TestFloat(t *testing.T) { 146 | tests := make(map[string]float64) 147 | tests[`3.14159`] = 3.14159 148 | tests[`2.0e2`] = 200.0 149 | tests[`2.5e-2`] = 0.025 150 | tests[`0.9E4`] = 9000.0 151 | 152 | for text, expected := range tests { 153 | x := NewFloat(text) 154 | if x.Float64() != expected { 155 | t.Errorf("Float `%s` parsed as `%f` wanted `%f`", text, x.Float64(), expected) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /choice_point.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mndrix/golog/term" 7 | ) 8 | import . "github.com/mndrix/golog/util" 9 | 10 | // ChoicePoint represents a place in the execution where we had a 11 | // choice between multiple alternatives. Following a choice point 12 | // is like making one of those choices. 13 | // 14 | // The simplest choice point simply tries to prove some goal; usually 15 | // the body of a clause. We can imagine more complex choice points. Perhaps 16 | // when the choice point was created, we spawned a goroutine to investigate 17 | // that portion of the execution tree. If that "clone" finds something 18 | // interesting, it can send itself down a channel. In this case, the 19 | // ChoicePoint's Follow() method would just return that speculative, cloned 20 | // machine. In effect it's saying, "If you want to pursue this execution 21 | // path, don't bother with the computation. I've already done it" 22 | // 23 | // One can imagine a ChoicePoint implementation which clones the Golog 24 | // machine onto a separate server in a cluster. Following that choice point 25 | // waits for the other server to finish evaluating its execution branch. 26 | type ChoicePoint interface { 27 | // Follow produces a new machine which sets out to explore a 28 | // choice point. 29 | Follow() (Machine, error) 30 | } 31 | 32 | // a choice point which follows Head :- Body clauses 33 | type headbodyCP struct { 34 | machine Machine 35 | goal term.Callable 36 | clause term.Callable 37 | } 38 | 39 | // A head-body choice point is one which, when followed, unifies a 40 | // goal g with the head of a term t. If unification fails, the choice point 41 | // fails. If unification succeeds, the machine tries to prove t's body. 42 | func NewHeadBodyChoicePoint(m Machine, g, t term.Term) ChoicePoint { 43 | return &headbodyCP{machine: m, goal: g.(term.Callable), clause: t.(term.Callable)} 44 | } 45 | func (cp *headbodyCP) Follow() (Machine, error) { 46 | // rename variables so recursive clauses work 47 | clause := term.RenameVariables(cp.clause).(term.Callable) 48 | 49 | // does the machine's current goal unify with our head? 50 | head := clause 51 | if clause.Arity() == 2 && clause.Name() == ":-" { 52 | head = term.Head(clause) 53 | } 54 | env, err := cp.goal.Unify(cp.machine.Bindings(), head) 55 | if err == term.CantUnify { 56 | return nil, err 57 | } 58 | MaybePanic(err) 59 | 60 | // yup, update the environment and top goal 61 | if clause.Arity() == 2 && clause.Name() == ":-" { 62 | return cp.machine.SetBindings(env).PushConj(term.Body(clause)), nil 63 | } 64 | return cp.machine.SetBindings(env), nil // don't need to push "true" 65 | } 66 | func (cp *headbodyCP) String() string { 67 | return fmt.Sprintf("prove goal `%s` against rule `%s`", cp.goal, cp.clause) 68 | } 69 | 70 | // a choice point that just pushes a term onto conjunctions 71 | type simpleCP struct { 72 | machine Machine 73 | goal term.Callable 74 | } 75 | 76 | // Following a simple choice point makes the machine start proving 77 | // goal g. If g is true/0, this choice point can be used to revert 78 | // back to a previous version of a machine. It can be useful for 79 | // building certain control constructs. 80 | func NewSimpleChoicePoint(m Machine, g term.Term) ChoicePoint { 81 | return &simpleCP{machine: m, goal: g.(term.Callable)} 82 | } 83 | func (cp *simpleCP) Follow() (Machine, error) { 84 | return cp.machine.PushConj(cp.goal), nil 85 | } 86 | func (cp *simpleCP) String() string { 87 | return fmt.Sprintf("push conj %s", cp.goal) 88 | } 89 | 90 | // a noop choice point that represents a cut barrier 91 | var barrierID int64 = 0 // thread unsafe counter variable. fix when needed 92 | type barrierCP struct { 93 | machine Machine 94 | id int64 95 | } 96 | 97 | // NewCutBarrier creates a special choice point which acts as a sentinel 98 | // value in the Golog machine's disjunction stack. Attempting to follow 99 | // a cut barrier choice point panics. 100 | func NewCutBarrier(m Machine) ChoicePoint { 101 | barrierID++ 102 | return &barrierCP{machine: m, id: barrierID} 103 | } 104 | 105 | var CutBarrierFails error = fmt.Errorf("Cut barriers never succeed") 106 | 107 | func (cp *barrierCP) Follow() (Machine, error) { 108 | return nil, CutBarrierFails 109 | } 110 | func (cp *barrierCP) String() string { 111 | return fmt.Sprintf("cut barrier %d", cp.id) 112 | } 113 | 114 | // If cp is a cut barrier choice point, BarrierId returns an identifier 115 | // unique to this cut barrier and true. If cp is not a cut barrier, 116 | // the second return value is false. BarrierId is mostly useful for 117 | // those hacking on the interpreter or doing strange control constructs 118 | // with foreign predicates. You probably don't need this. Incidentally, 119 | // !/0 is implemented in terms of this. 120 | func BarrierId(cp ChoicePoint) (int64, bool) { 121 | switch b := cp.(type) { 122 | case *barrierCP: 123 | return b.id, true 124 | } 125 | return -1, false 126 | } 127 | -------------------------------------------------------------------------------- /term/compound.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import . "fmt" 4 | 5 | import "bytes" 6 | 7 | // NewCallable creates a new term (or atom) with the given functor and 8 | // optional arguments 9 | func NewCallable(functor string, arguments ...Term) Callable { 10 | if len(arguments) == 0 { 11 | return NewAtom(functor) 12 | } 13 | return &Compound{ 14 | Func: functor, 15 | Args: arguments, 16 | ucache: &unificationCache{}, 17 | } 18 | } 19 | 20 | // Unlikely to be useful outside of the parser 21 | func NewTermFromLexeme(possiblyQuotedName string, arguments ...Term) Callable { 22 | a := NewAtomFromLexeme(possiblyQuotedName) 23 | return NewCallable(a.Name(), arguments...) 24 | } 25 | 26 | // NewCodeList returns a compound term consisting of the character codes 27 | // of the given string. The internal representation may eventually optimize 28 | // for storing character codes. 29 | func NewCodeList(s string) Term { 30 | runes := []rune(s) 31 | list := NewAtom("[]") 32 | for i := len(runes) - 1; i >= 0; i-- { 33 | list = NewCallable(".", NewCode(runes[i]), list) 34 | } 35 | return list 36 | } 37 | 38 | // NewTermList returns a list term consisting of each term from the slice. 39 | // A future implementation may optimize the data structure that's returned. 40 | func NewTermList(terms []Term) Term { 41 | list := NewAtom("[]") 42 | for i := len(terms) - 1; i >= 0; i-- { 43 | list = NewCallable(".", terms[i], list) 44 | } 45 | return list 46 | } 47 | 48 | // ISO calls this a "compound term" see §6.1.2(e) 49 | // We currently use this type to cover atoms defined in §6.1.2(b) 50 | // by treating atoms as compound terms with 0 arity. 51 | type Compound struct { 52 | Func string 53 | Args []Term 54 | ucache *unificationCache 55 | } 56 | type unificationCache struct { 57 | // 0 means UnificationHash hasn't been calculated yet 58 | phash uint64 // prepared hash 59 | qhash uint64 // query hash 60 | } 61 | 62 | func (self *Compound) Name() string { 63 | return self.Func 64 | } 65 | func (self *Compound) Arity() int { 66 | return len(self.Args) 67 | } 68 | func (self *Compound) Arguments() []Term { 69 | return self.Args 70 | } 71 | func (self *Compound) String() string { 72 | if IsString(self) { 73 | return PrettyString(self) 74 | } 75 | if IsList(self) { 76 | return PrettyList(self) 77 | } 78 | quotedFunctor := QuoteFunctor(self.Name()) 79 | 80 | var buf bytes.Buffer 81 | Fprintf(&buf, "%s(", quotedFunctor) 82 | arity := self.Arity() 83 | for i := 0; i < arity; i++ { 84 | if i > 0 { 85 | Fprintf(&buf, ", ") 86 | } 87 | Fprintf(&buf, "%s", self.Arguments()[i]) 88 | } 89 | Fprintf(&buf, ")") 90 | return buf.String() 91 | } 92 | 93 | func (self *Compound) Type() int { 94 | return CompoundType 95 | } 96 | 97 | func (self *Compound) Indicator() string { 98 | return Sprintf("%s/%d", self.Name(), self.Arity()) 99 | } 100 | 101 | func (self *Compound) ReplaceVariables(env Bindings) Term { 102 | args := self.Arguments() 103 | for i, arg := range args { 104 | newArg := arg.ReplaceVariables(env) 105 | if arg != newArg { // argument changed. build a new compound term 106 | newArgs := make([]Term, self.Arity()) 107 | for j, arg := range args { 108 | if j < i { 109 | newArgs[j] = arg 110 | } else { 111 | if j == i { 112 | newArgs[j] = newArg 113 | } else { 114 | newArgs[j] = arg.ReplaceVariables(env) 115 | } 116 | } 117 | } 118 | newTerm := NewCallable(self.Name(), newArgs...) 119 | return newTerm 120 | } 121 | } 122 | 123 | // no variables were replaced. reuse the same compound term 124 | return self 125 | } 126 | 127 | func (a *Compound) Unify(e Bindings, x Term) (Bindings, error) { 128 | if IsVariable(x) { 129 | return x.Unify(e, a) 130 | } 131 | if !IsCompound(x) { 132 | return e, CantUnify 133 | } 134 | b := x.(*Compound) 135 | 136 | // functor and arity must match for unification to work 137 | arity := a.Arity() 138 | if arity != b.Arity() { 139 | return e, CantUnify 140 | } 141 | if a.Name() != b.Name() { 142 | return e, CantUnify 143 | } 144 | 145 | // try unifying each subterm 146 | var err error 147 | env := e 148 | aArgs := a.Arguments() 149 | bArgs := b.Arguments() 150 | for i := 0; i < arity; i++ { 151 | env, err = aArgs[i].Unify(env, bArgs[i]) 152 | if err != nil { 153 | return e, err // return original environment along with error 154 | } 155 | } 156 | 157 | // unification succeeded 158 | return env, nil 159 | } 160 | 161 | // Univ is just like =../2 in ISO Prolog 162 | func (self *Compound) Univ() []Term { 163 | ts := make([]Term, 0) 164 | ts = append(ts, NewAtom(self.Name())) 165 | ts = append(ts, self.Arguments()...) 166 | return ts 167 | } 168 | 169 | // Returns true if a and b might unify. This is an optimization 170 | // for times when a and b are frequently unified with other 171 | // compound terms. For example, goals and clause heads. 172 | func (a *Compound) MightUnify(b *Compound) bool { 173 | if a.ucache.qhash == 0 { 174 | a.ucache.qhash = UnificationHash([]Term{a}, 64, false) 175 | } 176 | if b.ucache.phash == 0 { 177 | b.ucache.phash = UnificationHash([]Term{b}, 64, true) 178 | } 179 | 180 | return (a.ucache.qhash & b.ucache.phash) == a.ucache.qhash 181 | } 182 | -------------------------------------------------------------------------------- /interactive.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | 9 | "github.com/mndrix/golog/term" 10 | ) 11 | 12 | func RegisterHelp(m Machine, h map[string]string) { 13 | rh := m.(*machine).help 14 | for k, v := range h { 15 | rh[k] = v 16 | } 17 | } 18 | 19 | func NewInteractiveMachine() Machine { 20 | res := NewMachine() 21 | res.(*machine).help = builtInHelp() 22 | return res.RegisterForeign(map[string]ForeignPredicate{ 23 | "help/0": InteractiveHelp0, 24 | "help/1": InteractiveHelp1, 25 | "apropos/1": InteractiveApropos1, 26 | }) 27 | } 28 | 29 | func apropos(m Machine, pattern string) string { 30 | var keys []string 31 | pat := regexp.MustCompile(pattern) 32 | buf := bytes.NewBuffer([]byte{}) 33 | if np, ok := m.(*machine); ok { 34 | for i := 0; i < smallThreshold; i++ { 35 | for _, k := range np.smallForeign[i].Keys() { 36 | keys = append(keys, fmt.Sprintf("%s/%d", k, i)) 37 | } 38 | } 39 | keys = append(keys, np.largeForeign.Keys()...) 40 | 41 | for _, k := range keys { 42 | if pat.MatchString(k) { 43 | _, _ = buf.WriteString(k) 44 | _, _ = buf.WriteRune('\n') 45 | } 46 | } 47 | return string(buf.Bytes()) 48 | } 49 | return "% no results." 50 | } 51 | 52 | func builtInHelp() map[string]string { 53 | return map[string]string{ 54 | "help/0": `Prints help usage.`, 55 | "help/1": `Prints the usage of the given predicate.`, 56 | "apropos/1": `Looks up the database for the predicates 57 | matching regexp and prints them.`, 58 | "!/0": `Cut operator, prevents backtracking beyond this point.`, 59 | ",/2": `Conjunction operator.`, 60 | "->/2": `Implication operator.`, 61 | ";/2": `Disjunction operator.`, 62 | "=/2": `Unification operator.`, 63 | "=:=/2": `Numeric equality operator.`, 64 | "==/2": `Equality operator.`, 65 | "\\==/2": `Equality negation operator.`, 66 | "@/2": `Greater than operator.`, 69 | "@>=/2": `Greater than or equal operator.`, 70 | `\+/1`: `Negation operator.`, 71 | "atom_codes/2": `Second argument is the list containing the character 72 | codes of the name of the first argument.`, 73 | "atom_number/2": `Second argument is the number represented by the name 74 | of the first argument.`, 75 | "call/1": `Evaluates its argument.`, 76 | "call/2": `Constructs term from its arguments and evaluates it.`, 77 | "call/3": `Constructs term from its arguments and evaluates it.`, 78 | "call/4": `Constructs term from its arguments and evaluates it.`, 79 | "call/5": `Constructs term from its arguments and evaluates it.`, 80 | "call/6": `Constructs term from its arguments and evaluates it.`, 81 | "downcase_atom/2": `Second argument is the atom with the name made up of 82 | all the same characters of the first atom, just in lower case`, 83 | "fail/0": `Fail unconditionaly.`, 84 | "findall/3": `Generate variables from template (first argument), 85 | bind them in the second argument, then collect the bindings in the third argument.`, 86 | "ground/1": `Succeeds if the argument is ground.`, 87 | "is/2": `Succeeds if the numerical expressions on both sides 88 | evaluate to the same number.`, 89 | "listing/0": `Prints all predicates known to this interpreter.`, 90 | "msort/2": `Sorts list.`, 91 | "printf/1": `Prints its first argument.`, 92 | "printf/2": `Populates the template in the first argument with 93 | the printable representations of its second argument (which must be a list) 94 | and prints it.`, 95 | "printf/3": `Same as printf/2, but prints into a stream given 96 | in the first argument.`, 97 | "succ/2": `True if its second argument is one greater than its 98 | first argument.`, 99 | "var/1": `True if its argument is a variable.`, 100 | } 101 | } 102 | 103 | func InteractiveHelp0(m Machine, args []term.Term) ForeignReturn { 104 | _, _ = fmt.Fprintf(os.Stderr, ` 105 | Use: 106 | ?- help(predicate). 107 | to print documentation of the predicate. 108 | ?- apropos("regexp"). 109 | to look for predicates matching regexp. 110 | `) 111 | return ForeignTrue() 112 | } 113 | 114 | func InteractiveHelp1(m Machine, args []term.Term) ForeignReturn { 115 | var subj string 116 | if term.IsAtom(args[0]) { 117 | subj = args[0].(*term.Atom).Name() 118 | } else if term.IsString(args[0]) { 119 | subj = term.RawString(args[0]) 120 | } else { 121 | panic(fmt.Sprintf("Illegal argument to help/1: %s", args[0])) 122 | } 123 | rh := m.(*machine).help 124 | help := rh[subj] 125 | 126 | if help == "" { 127 | _, _ = fmt.Fprintf(os.Stderr, "No help on %s\n", subj) 128 | help = apropos(m, subj) 129 | if help != "" { 130 | _, _ = fmt.Fprintf(os.Stderr, "Maybe you meant\n%s\n", help) 131 | } 132 | } else { 133 | _, _ = fmt.Fprintf(os.Stderr, help+"\n") 134 | } 135 | return ForeignTrue() 136 | } 137 | 138 | func InteractiveApropos1(m Machine, args []term.Term) ForeignReturn { 139 | var subj string 140 | if term.IsAtom(args[0]) { 141 | subj = args[0].(*term.Atom).Name() 142 | } else if term.IsString(args[0]) { 143 | subj = term.RawString(args[0]) 144 | } else { 145 | panic(fmt.Sprintf("Illegal argument to apropos/1: %s", args[0])) 146 | } 147 | _, _ = fmt.Fprintf(os.Stderr, apropos(m, subj)) 148 | return ForeignTrue() 149 | } 150 | -------------------------------------------------------------------------------- /term/unify_test.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import "testing" 4 | 5 | // convenience for below 6 | func unify(env Bindings, a, b Term) (Bindings, error) { 7 | return a.Unify(env, b) 8 | } 9 | 10 | func TestUnifyConstants(t *testing.T) { 11 | env := NewBindings() 12 | 13 | // atoms 14 | _, err := NewAtom("hi").Unify(env, NewAtom("hi")) 15 | if err != nil { 16 | t.Errorf("hi/0 and hi/0 don't unify") 17 | } 18 | _, err = NewAtom("n").Unify(env, NewCode('n')) 19 | if err == nil { 20 | t.Errorf("n and 0'n unified") 21 | } 22 | 23 | // shallow terms 24 | _, err = unify(env, 25 | NewCallable("hi", NewCallable("you")), 26 | NewCallable("hi", NewCallable("you")), 27 | ) 28 | if err != nil { 29 | t.Errorf("hi(you) and hi(you) don't unify") 30 | } 31 | 32 | // atom and deeper term don't unify 33 | _, err = unify(env, 34 | NewCallable("foo"), 35 | NewCallable("bar", NewCallable("baz")), 36 | ) 37 | if err == nil { 38 | t.Errorf("foo and bar(baz) should not unify") 39 | } 40 | 41 | // integers and floats 42 | _, err = unify(env, NewInt("1234"), NewInt("1234")) 43 | if err != nil { 44 | t.Errorf("1234 and 1234 don't unify") 45 | } 46 | _, err = unify(env, NewInt("1234"), NewInt("1235")) 47 | if err == nil { 48 | t.Errorf("1234 and 1235 unify") 49 | } 50 | _, err = unify(env, NewFloat("99.2"), NewFloat("99.2")) 51 | if err != nil { 52 | t.Errorf("99.2 and 99.2 don't unify") 53 | } 54 | _, err = unify(env, NewFloat("8.2"), NewFloat("8.1")) 55 | if err == nil { 56 | t.Errorf("8.2 and 8.1 unify") 57 | } 58 | 59 | /* ISO standard is ambiguous (as best I can tell) to how these should 60 | behave. SWI-Prolog and YAP both behave as the tests describe below. 61 | It seems reasonable to me that 6 and 6.0 are both representations of 62 | the same term: the number 6. 63 | I may change this behavior later to conform with de facto standard 64 | if it makes sense. 65 | 66 | _, err = unify(env, NewInt("6"), NewFloat("6.0")) 67 | if err == nil { 68 | t.Errorf("6 and 6.0 unify") 69 | } 70 | _, err = unify(env, NewFloat("5.0"), NewInt("5")) 71 | if err == nil { 72 | t.Errorf("5.0 and 5 unify") 73 | } 74 | */ 75 | } 76 | 77 | func nv(name string) *Variable { 78 | return NewVar(name) 79 | } 80 | 81 | func TestUnifyAtomWithUnboundVariable(t *testing.T) { 82 | env0 := NewBindings() 83 | 84 | env1, err := unify(env0, 85 | NewCallable("x"), 86 | nv("X"), 87 | ) 88 | if err != nil { 89 | t.Errorf("Couldn't unify `x=X`") 90 | } 91 | x0, err := env1.Value(nv("X")) 92 | if err != nil { 93 | t.Errorf("No binding produced for X") 94 | } 95 | if x0.String() != "x" { 96 | t.Errorf("X has the wrong value: %s", x0) 97 | } 98 | } 99 | 100 | func TestUnifyUnboundVariableWithStructure(t *testing.T) { 101 | env1, err := unify(NewBindings(), 102 | NewVar("X"), 103 | NewCallable("alpha", NewCallable("beta")), 104 | ) 105 | if err != nil { 106 | t.Errorf("Couldn't unify `X=alpha(beta)`") 107 | } 108 | x0, err := env1.Value(nv("X")) 109 | if err != nil { 110 | t.Errorf("No binding produced for X") 111 | } 112 | if x0.String() != "alpha(beta)" { 113 | t.Errorf("X has the wrong value: %s", x0) 114 | } 115 | } 116 | 117 | func TestUnifyNestedVariable(t *testing.T) { 118 | env0 := NewBindings() 119 | env1, err := unify(env0, 120 | NewCallable("etc", NewCallable("stuff")), 121 | NewCallable("etc", nv("A")), 122 | ) 123 | if err != nil { 124 | t.Errorf("Couldn't unify `etc(stuff)=etc(A)`") 125 | } 126 | x0, err := env1.Value(nv("A")) 127 | if err != nil { 128 | t.Errorf("No binding produced for A") 129 | } 130 | if x0.String() != "stuff" { 131 | t.Errorf("A has the wrong value: %s", x0) 132 | } 133 | 134 | // A shouldn't be bound in the original, empty environment 135 | _, err = env0.Value(nv("A")) 136 | if err != NotBound { 137 | t.Errorf("Unification changed the original environment") 138 | } 139 | } 140 | 141 | func TestUnifySameVariable(t *testing.T) { 142 | env0 := NewBindings() 143 | env1, err := unify(env0, NewVar("X"), NewVar("X")) 144 | maybePanic(err) 145 | 146 | if env0.Size() != 0 { 147 | t.Errorf("env0 has bindings") 148 | } 149 | if env1.Size() != 0 { 150 | t.Errorf("env1 has bindings") 151 | } 152 | } 153 | 154 | func TestUnifyVariableAliases(t *testing.T) { 155 | env0 := NewBindings() 156 | 157 | // make two variables aliases for each other 158 | env1, err := unify(env0, NewVar("X0"), NewVar("X1")) 159 | maybePanic(err) 160 | 161 | // unify one of the aliased variables with a term 162 | env2, err := unify(env1, NewCallable("hello"), NewVar("X0")) 163 | maybePanic(err) 164 | 165 | // does X0 have the right value? 166 | x0 := env2.Resolve_(nv("X0")) 167 | if x0.String() != "hello" { 168 | t.Errorf("X0 has the wrong value: %s", x0) 169 | } 170 | 171 | // does X1 have the right value? 172 | x1 := env2.Resolve_(nv("X1")) 173 | maybePanic(err) 174 | if x1.String() != "hello" { 175 | t.Errorf("X1 has the wrong value: %s", x1) 176 | } 177 | } 178 | 179 | // same as TestUnifyVariableAliases but with first unification order switched 180 | func TestUnifyVariableAliases2(t *testing.T) { 181 | env0 := NewBindings() 182 | 183 | // make two variables aliases for each other 184 | env1, err := unify(env0, nv("X1"), nv("X0")) 185 | maybePanic(err) 186 | 187 | // unify one of the aliased variables with a term 188 | env2, err := unify(env1, NewCallable("hello"), nv("X0")) 189 | maybePanic(err) 190 | 191 | // does X0 have the right value? 192 | x0 := env2.Resolve_(nv("X0")) 193 | if x0.String() != "hello" { 194 | t.Errorf("X0 has the wrong value: %s", x0) 195 | } 196 | 197 | // does X1 have the right value? 198 | x1 := env2.Resolve_(nv("X1")) 199 | if x1.String() != "hello" { 200 | t.Errorf("X1 has the wrong value: %s", x1) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /term/number.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import "fmt" 4 | import "math/big" 5 | import . "github.com/mndrix/golog/util" 6 | 7 | // Number represents either an integer or a floating point number. This 8 | // interface is convenient when working with arithmetic 9 | type Number interface { 10 | Term 11 | 12 | // Float64 gives a floating point representation of this number. The 13 | // representation inherits all weaknesses of the float64 data type. 14 | Float64() float64 15 | 16 | // LosslessInt returns a big.Int representation of this number, if 17 | // possible. If int64 can't represent the number perfectly, returns 18 | // false. 19 | LosslessInt() (*big.Int, bool) 20 | 21 | // LosslessRat returns a big.Rational representation of this number, 22 | // if possible. If a big.Rational can't represent the number perfectly, 23 | // returns false. Floats never convert to rationals. 24 | LosslessRat() (*big.Rat, bool) 25 | } 26 | 27 | // Returns true if term t is an integer 28 | func IsInteger(t Term) bool { 29 | return t.Type() == IntegerType 30 | } 31 | 32 | // Returns true if term t is an floating point number 33 | func IsFloat(t Term) bool { 34 | return t.Type() == FloatType 35 | } 36 | 37 | // Returns true if term t is a rational number 38 | func IsRational(t Term) bool { 39 | switch t.(type) { 40 | case *Rational: 41 | return true 42 | default: 43 | return false 44 | } 45 | } 46 | 47 | // Returns true if term is a number (integer, float, etc) 48 | func IsNumber(t Term) bool { 49 | return IsInteger(t) || IsFloat(t) || IsRational(t) 50 | } 51 | 52 | // Evaluate an arithmetic expression to produce a number. This is 53 | // conceptually similar to Prolog: X is Expression. Returns false if 54 | // the expression cannot be evaluated (unbound variables, in) 55 | func ArithmeticEval(t0 Term) (Number, error) { 56 | Debugf("arith eval: %s\n", t0) 57 | 58 | // number terms require no additional evaluation 59 | if IsNumber(t0) { 60 | return t0.(Number), nil 61 | } 62 | var t Callable 63 | if IsCallable(t0) { 64 | t = t0.(Callable) 65 | } else { 66 | msg := fmt.Sprintf("Unexpected arithmetic term: %s", t0) 67 | panic(msg) 68 | } 69 | 70 | // evaluate arithmetic expressions 71 | if t.Arity() == 2 { 72 | // evaluate arguments recursively 73 | args := t.Arguments() 74 | a, b, err := ArithmeticEval2(args[0], args[1]) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | switch t.Name() { 80 | case "*": // multiplication 81 | return ArithmeticMultiply(a, b) 82 | case "+": // addition 83 | return ArithmeticAdd(a, b) 84 | case "-": // subtraction 85 | return ArithmeticMinus(a, b) 86 | case "/": // division 87 | return ArithmeticDivide(a, b) 88 | } 89 | 90 | } 91 | 92 | // this term doesn't look like an expression 93 | msg := fmt.Errorf("Not an expression: %s", t) 94 | return nil, msg 95 | } 96 | 97 | func ArithmeticEval2(first, second Term) (Number, Number, error) { 98 | // recursively evaluate each argument 99 | a, err := ArithmeticEval(first) 100 | if err != nil { 101 | return nil, nil, err 102 | } 103 | b, err := ArithmeticEval(second) 104 | if err != nil { 105 | return nil, nil, err 106 | } 107 | return a, b, nil 108 | } 109 | 110 | // Add two Golog numbers returning the result as a new Golog number 111 | func ArithmeticAdd(a, b Number) (Number, error) { 112 | 113 | // as integers? 114 | if xi, ok := a.LosslessInt(); ok { 115 | if yi, ok := b.LosslessInt(); ok { 116 | r := new(big.Int).Add(xi, yi) 117 | return NewBigInt(r), nil 118 | } 119 | } 120 | 121 | // as rationals? 122 | if xr, ok := a.LosslessRat(); ok { 123 | if yr, ok := b.LosslessRat(); ok { 124 | r := new(big.Rat).Add(xr, yr) 125 | return NewBigRat(r), nil 126 | } 127 | } 128 | 129 | // as floats? 130 | r := a.Float64() + b.Float64() 131 | return NewFloat64(r), nil 132 | } 133 | 134 | // Divide two Golog numbers returning the result as a new Golog number. 135 | // The return value uses the most precise internal type possible. 136 | func ArithmeticDivide(a, b Number) (Number, error) { 137 | 138 | // as integers? 139 | if xi, ok := a.LosslessInt(); ok { 140 | if yi, ok := b.LosslessInt(); ok { 141 | r := new(big.Rat).SetFrac(xi, yi) 142 | return NewBigRat(r), nil 143 | } 144 | } 145 | 146 | // as rationals? 147 | if xr, ok := a.LosslessRat(); ok { 148 | if yr, ok := b.LosslessRat(); ok { 149 | r := new(big.Rat).Quo(xr, yr) 150 | return NewBigRat(r), nil 151 | } 152 | } 153 | 154 | // as floats? 155 | r := a.Float64() / b.Float64() 156 | return NewFloat64(r), nil 157 | } 158 | 159 | // Subtract two Golog numbers returning the result as a new Golog number 160 | func ArithmeticMinus(a, b Number) (Number, error) { 161 | 162 | // as integers? 163 | if xi, ok := a.LosslessInt(); ok { 164 | if yi, ok := b.LosslessInt(); ok { 165 | r := new(big.Int).Sub(xi, yi) 166 | return NewBigInt(r), nil 167 | } 168 | } 169 | 170 | // as rationals? 171 | if xr, ok := a.LosslessRat(); ok { 172 | if yr, ok := b.LosslessRat(); ok { 173 | r := new(big.Rat).Sub(xr, yr) 174 | return NewBigRat(r), nil 175 | } 176 | } 177 | 178 | // as floats? 179 | r := a.Float64() - b.Float64() 180 | return NewFloat64(r), nil 181 | } 182 | 183 | // Multiply two Golog numbers returning the result as a new Golog number 184 | func ArithmeticMultiply(a, b Number) (Number, error) { 185 | 186 | // as integers? 187 | if xi, ok := a.LosslessInt(); ok { 188 | if yi, ok := b.LosslessInt(); ok { 189 | r := new(big.Int).Mul(xi, yi) 190 | return NewBigInt(r), nil 191 | } 192 | } 193 | 194 | // as rationals? 195 | if xr, ok := a.LosslessRat(); ok { 196 | if yr, ok := b.LosslessRat(); ok { 197 | r := new(big.Rat).Mul(xr, yr) 198 | return NewBigRat(r), nil 199 | } 200 | } 201 | 202 | // as floats? 203 | r := a.Float64() * b.Float64() 204 | return NewFloat64(r), nil 205 | } 206 | 207 | // Compare two Golog numbers. Returns 208 | // 209 | // -1 if a < b 210 | // 0 if a == b 211 | // +1 if a > b 212 | func NumberCmp(a, b Number) int { 213 | // compare as integers? 214 | if xi, ok := a.LosslessInt(); ok { 215 | if yi, ok := b.LosslessInt(); ok { 216 | return xi.Cmp(yi) 217 | } 218 | } 219 | 220 | // compare as rationals? 221 | if xr, ok := a.LosslessRat(); ok { 222 | if yr, ok := b.LosslessRat(); ok { 223 | return xr.Cmp(yr) 224 | } 225 | } 226 | 227 | // compare as floats? 228 | diff := a.Float64() - b.Float64() 229 | if diff < 0 { 230 | return -1 231 | } 232 | if diff > 0 { 233 | return 1 234 | } 235 | return 0 236 | } 237 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import "fmt" 4 | import "strconv" 5 | import "testing" 6 | import "github.com/mndrix/golog/read" 7 | import "github.com/mndrix/golog/term" 8 | 9 | func BenchmarkTrue(b *testing.B) { 10 | m := NewMachine() 11 | g := read.Term_(`true.`) 12 | for i := 0; i < b.N; i++ { 13 | _ = m.ProveAll(g) 14 | } 15 | } 16 | 17 | func BenchmarkAppend(b *testing.B) { 18 | m := NewMachine().Consult(` 19 | append([], A, A). % test same variable name as other clauses 20 | append([A|B], C, [A|D]) :- 21 | append(B, C, D). 22 | `) 23 | g := read.Term_(`append([a,b,c], [d,e], List).`) 24 | 25 | for i := 0; i < b.N; i++ { 26 | _ = m.ProveAll(g) 27 | } 28 | } 29 | 30 | // unify two compounds terms with deep structure. unification succeeds 31 | func BenchmarkUnifyDeep(b *testing.B) { 32 | x := read.Term_(`a(b(c(d(e(f(g(h(i(j))))))))).`) 33 | y := read.Term_(`a(b(c(d(e(f(g(h(i(X))))))))).`) 34 | 35 | env := term.NewBindings() 36 | for i := 0; i < b.N; i++ { 37 | _, _ = x.Unify(env, y) 38 | } 39 | } 40 | 41 | // unify two compounds terms with deep structure. unification fails 42 | func BenchmarkUnifyDeepFail(b *testing.B) { 43 | x := read.Term_(`a(b(c(d(e(f(g(h(i(j))))))))).`) 44 | y := read.Term_(`a(b(c(d(e(f(g(h(i(x))))))))).`) 45 | 46 | env := term.NewBindings() 47 | for i := 0; i < b.N; i++ { 48 | _, _ = x.Unify(env, y) 49 | } 50 | } 51 | 52 | func BenchmarkUnificationHash(b *testing.B) { 53 | x := read.Term_(`a(b(c(d(e(f(g(h(i(j))))))))).`) 54 | for i := 0; i < b.N; i++ { 55 | _ = term.UnificationHash([]term.Term{x}, 64, true) 56 | } 57 | } 58 | 59 | // test performance of a standard maplist implementation 60 | func BenchmarkMaplist(b *testing.B) { 61 | m := NewMachine().Consult(` 62 | always_a(_, a). 63 | 64 | maplist(C, A, B) :- 65 | maplist_(A, B, C). 66 | 67 | maplist_([], [], _). 68 | maplist_([B|D], [C|E], A) :- 69 | call(A, B, C), 70 | maplist_(D, E, A). 71 | `) 72 | g := read.Term_(`maplist(always_a, [1,2,3,4,5], As).`) 73 | 74 | for i := 0; i < b.N; i++ { 75 | _ = m.ProveAll(g) 76 | } 77 | } 78 | 79 | // traditional, naive reverse benchmark 80 | // The Art of Prolog by Sterling, etal says that reversing a 30 element 81 | // list using this technique does 496 reductions. From this we can 82 | // calculate a rough measure of Golog's LIPS. 83 | func BenchmarkNaiveReverse(b *testing.B) { 84 | m := NewMachine().Consult(` 85 | append([], A, A). 86 | append([A|B], C, [A|D]) :- 87 | append(B, C, D). 88 | 89 | reverse([],[]). 90 | reverse([X|Xs], Zs) :- 91 | reverse(Xs, Ys), 92 | append(Ys, [X], Zs). 93 | `) 94 | g := read.Term_(`reverse([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30], As).`) 95 | 96 | for i := 0; i < b.N; i++ { 97 | _ = m.ProveAll(g) 98 | } 99 | } 100 | 101 | func BenchmarkDCGish(b *testing.B) { 102 | m := NewMachine().Consult(` 103 | name([alice |X], X). 104 | name([bob |X], X). 105 | name([charles |X], X). 106 | name([david |X], X). 107 | name([eric |X], X). 108 | name([francis |X], X). 109 | name([george |X], X). 110 | name([harry |X], X). 111 | name([ignatius|X], X). 112 | name([john |X], X). 113 | name([katie |X], X). 114 | name([larry |X], X). 115 | name([michael |X], X). 116 | name([nancy |X], X). 117 | name([oliver |X], X). 118 | `) 119 | g := read.Term_(`name([george,the,third], Rest).`) 120 | 121 | for i := 0; i < b.N; i++ { 122 | _ = m.ProveAll(g) 123 | } 124 | } 125 | 126 | func BenchmarkRead(b *testing.B) { 127 | for i := 0; i < b.N; i++ { 128 | _ = read.Term_(`reverse([1,2,3,4,5,6,7], Xs).`) 129 | } 130 | } 131 | 132 | // Low level benchmarks to test Go's implementation 133 | func init() { // avoid import errors when low level benchmarks comment out 134 | _ = fmt.Sprintf("") 135 | _ = strconv.Itoa(1) 136 | } 137 | 138 | /* 139 | func BenchmarkLowLevelCompareUint64(b *testing.B) { 140 | var nintendo uint64 = 282429536481 141 | var other uint64 = 387429489 142 | for i := 0; i < b.N; i++ { 143 | if nintendo == other { 144 | // do nothing 145 | } 146 | } 147 | } 148 | 149 | func BenchmarkLowLevelCompareString(b *testing.B) { 150 | nintendo := "nintendo" 151 | other := "other" 152 | for i := 0; i < b.N; i++ { 153 | if nintendo == other { 154 | // do nothing 155 | } 156 | } 157 | } 158 | func BenchmarkLowLevelBitwise(b *testing.B) { 159 | var nintendo uint64 = 282429536481 160 | var other uint64 = 387429489 161 | for i := 0; i < b.N; i++ { 162 | if nintendo&other == nintendo { 163 | // do nothing 164 | } 165 | } 166 | } 167 | func BenchmarkLowLevelFloatBinaryExponent(b *testing.B) { 168 | f := 3.1415 169 | for i := 0; i < b.N; i++ { 170 | _ = strconv.FormatFloat(f, 'b', 0, 64) 171 | } 172 | } 173 | func BenchmarkLowLevelFloatDecimalExponent(b *testing.B) { 174 | f := 3.1415 175 | for i := 0; i < b.N; i++ { 176 | _ = strconv.FormatFloat(f, 'e', 64, 64) 177 | } 178 | } 179 | func BenchmarkLowLevelIntDecimal(b *testing.B) { 180 | var x uint64 = 1967 181 | for i := 0; i < b.N; i++ { 182 | _ = fmt.Sprintf("%d", x) 183 | } 184 | } 185 | func BenchmarkLowLevelIntHex(b *testing.B) { 186 | var x uint64 = 1967 187 | for i := 0; i < b.N; i++ { 188 | _ = fmt.Sprintf("%x", x) 189 | } 190 | } 191 | 192 | // benchmarks to compare performance on interface-related code 193 | type AnInterface interface { 194 | AMethod() int 195 | } 196 | type ImplementationOne int 197 | 198 | func (*ImplementationOne) AMethod() int { return 1 } 199 | 200 | type ImplementationTwo int 201 | 202 | func (*ImplementationTwo) AMethod() int { return 2 } 203 | 204 | func NotAMethod(x AnInterface) int { 205 | switch x.(type) { 206 | case *ImplementationOne: 207 | return 1 208 | case *ImplementationTwo: 209 | return 2 210 | } 211 | panic("impossible") 212 | } 213 | 214 | func NotAMethodManual(x AnInterface) int { 215 | kind := x.AMethod() 216 | switch kind { 217 | case 1: 218 | return 1 219 | case 2: 220 | return 2 221 | } 222 | panic("impossible") 223 | } 224 | 225 | // how expensive is it to call a method? 226 | func BenchmarkInterfaceMethod(b *testing.B) { 227 | var x AnInterface 228 | num := 100 229 | x = (*ImplementationOne)(&num) 230 | 231 | for i := 0; i < b.N; i++ { 232 | _ = x.AMethod() 233 | } 234 | } 235 | 236 | // how expensive is it to call a function that acts like a method? 237 | func BenchmarkInterfaceFunctionTypeSwitch(b *testing.B) { 238 | var x AnInterface 239 | num := 100 240 | x = (*ImplementationOne)(&num) 241 | 242 | for i := 0; i < b.N; i++ { 243 | _ = NotAMethod(x) 244 | } 245 | } 246 | 247 | // how expensive is it to call a function that acts like a method? 248 | func BenchmarkInterfaceFunctionManualTypeSwitch(b *testing.B) { 249 | var x AnInterface 250 | num := 100 251 | x = (*ImplementationOne)(&num) 252 | 253 | for i := 0; i < b.N; i++ { 254 | _ = NotAMethodManual(x) 255 | } 256 | } 257 | 258 | // how expensive is it to inline a type switch that acts like a method? 259 | func BenchmarkInterfaceInlineTypeSwitch(b *testing.B) { 260 | var x AnInterface 261 | num := 100 262 | x = (*ImplementationOne)(&num) 263 | 264 | for i := 0; i < b.N; i++ { 265 | var y int 266 | switch x.(type) { 267 | case *ImplementationOne: 268 | y = 1 269 | case *ImplementationTwo: 270 | y = 2 271 | } 272 | _ = y 273 | } 274 | } 275 | 276 | // how expensive is a manually-implemented type switch? 277 | func BenchmarkInterfaceManualTypeSwitch(b *testing.B) { 278 | var x AnInterface 279 | num := 100 280 | x = (*ImplementationOne)(&num) 281 | 282 | for i := 0; i < b.N; i++ { 283 | var y int 284 | kind := x.AMethod() 285 | switch kind { 286 | case 1: 287 | y = 1 288 | case 2: 289 | y = 2 290 | } 291 | _ = y 292 | } 293 | } 294 | 295 | */ 296 | -------------------------------------------------------------------------------- /prove_test.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mndrix/golog/term" 7 | ) 8 | 9 | func TestFacts(t *testing.T) { 10 | m := NewMachine().Consult(` 11 | father(michael). 12 | father(marc). 13 | 14 | mother(gail). 15 | 16 | parent(X) :- 17 | father(X). 18 | parent(X) :- 19 | mother(X). 20 | `) 21 | 22 | // these should be provably true 23 | if !m.CanProve(`father(michael).`) { 24 | t.Errorf("Couldn't prove father(michael)") 25 | } 26 | if !m.CanProve(`father(marc).`) { 27 | t.Errorf("Couldn't prove father(marc)") 28 | } 29 | if !m.CanProve(`parent(michael).`) { 30 | t.Errorf("Couldn't prove parent(michael)") 31 | } 32 | if !m.CanProve(`parent(marc).`) { 33 | t.Errorf("Couldn't prove parent(marc)") 34 | } 35 | 36 | // these should not be provable 37 | if m.CanProve(`father(sue).`) { 38 | t.Errorf("Proved father(sue)") 39 | } 40 | if m.CanProve(`mother(michael).`) { 41 | t.Errorf("Proved mother(michael)") 42 | } 43 | if m.CanProve(`parent(sue).`) { 44 | t.Errorf("Proved parent(sue)") 45 | } 46 | 47 | // trivial predicate with multiple solutions 48 | solutions := m.ProveAll(`father(X).`) 49 | if len(solutions) != 2 { 50 | t.Errorf("Wrong number of solutions: %d vs 2", len(solutions)) 51 | } 52 | if x := solutions[0].ByName_("X").String(); x != "michael" { 53 | t.Errorf("Wrong first solution: %s", x) 54 | } 55 | if x := solutions[1].ByName_("X").String(); x != "marc" { 56 | t.Errorf("Wrong second solution: %s", x) 57 | } 58 | 59 | // simple predicate with multiple solutions 60 | solutions = m.ProveAll(`parent(Name).`) 61 | if len(solutions) != 3 { 62 | t.Errorf("Wrong number of solutions: %d vs 2", len(solutions)) 63 | } 64 | if x := solutions[0].ByName_("Name").String(); x != "michael" { 65 | t.Errorf("Wrong first solution: %s", x) 66 | } 67 | if x := solutions[1].ByName_("Name").String(); x != "marc" { 68 | t.Errorf("Wrong second solution: %s", x) 69 | } 70 | if x := solutions[2].ByName_("Name").String(); x != "gail" { 71 | t.Errorf("Wrong third solution: %s", x) 72 | } 73 | 74 | // cut in the top level query 75 | solutions = m.ProveAll(`parent(Name), !.`) 76 | if len(solutions) != 1 { 77 | t.Errorf("Wrong number of solutions: %d vs 1", len(solutions)) 78 | } 79 | if x := solutions[0].ByName_("Name").String(); x != "michael" { 80 | t.Errorf("Wrong first solution: %s", x) 81 | } 82 | } 83 | 84 | func TestConjunction(t *testing.T) { 85 | m := NewMachine().Consult(` 86 | floor_wax(briwax). 87 | floor_wax(shimmer). 88 | floor_wax(minwax). 89 | 90 | dessert(shimmer). 91 | dessert(cake). 92 | dessert(pie). 93 | 94 | verb(glimmer). 95 | verb(shimmer). 96 | 97 | snl(Item) :- 98 | floor_wax(Item), 99 | dessert(Item). 100 | 101 | three(Item) :- 102 | verb(Item), 103 | dessert(Item), 104 | floor_wax(Item). 105 | `) 106 | 107 | skits := m.ProveAll(`snl(X).`) 108 | if len(skits) != 1 { 109 | t.Errorf("Wrong number of solutions: %d vs 1", len(skits)) 110 | } 111 | if x := skits[0].ByName_("X").String(); x != "shimmer" { 112 | t.Errorf("Wrong solution: %s vs shimmer", x) 113 | } 114 | 115 | skits = m.ProveAll(`three(W).`) 116 | if len(skits) != 1 { 117 | t.Errorf("Wrong number of solutions: %d vs 1", len(skits)) 118 | } 119 | if x := skits[0].ByName_("W").String(); x != "shimmer" { 120 | t.Errorf("Wrong solution: %s vs shimmer", x) 121 | } 122 | } 123 | 124 | func TestCut(t *testing.T) { 125 | m := NewMachine().Consult(` 126 | single(foo) :- 127 | !. 128 | single(bar). 129 | 130 | twice(X) :- 131 | single(X). % cut inside here doesn't cut twice/1 132 | twice(bar). 133 | `) 134 | 135 | proofs := m.ProveAll(`single(X).`) 136 | if len(proofs) != 1 { 137 | t.Errorf("Wrong number of solutions: %d vs 1", len(proofs)) 138 | } 139 | if x := proofs[0].ByName_("X").String(); x != "foo" { 140 | t.Errorf("Wrong solution: %s vs foo", x) 141 | } 142 | 143 | proofs = m.ProveAll(`twice(X).`) 144 | if len(proofs) != 2 { 145 | t.Errorf("Wrong number of solutions: %d vs 2", len(proofs)) 146 | } 147 | if x := proofs[0].ByName_("X").String(); x != "foo" { 148 | t.Errorf("Wrong solution: %s vs foo", x) 149 | } 150 | if x := proofs[1].ByName_("X").String(); x != "bar" { 151 | t.Errorf("Wrong solution: %s vs bar", x) 152 | } 153 | } 154 | 155 | func TestAppend(t *testing.T) { 156 | m := NewMachine().Consult(` 157 | append([], A, A). % test same variable name as other clauses 158 | append([A|B], C, [A|D]) :- 159 | append(B, C, D). 160 | `) 161 | 162 | proofs := m.ProveAll(`append([a], [b], List).`) 163 | if len(proofs) != 1 { 164 | t.Errorf("Wrong number of answers: %d vs 1", len(proofs)) 165 | } 166 | if x := proofs[0].ByName_("List").String(); x != "[a,b]" { 167 | t.Errorf("Wrong solution: %s vs [a, b]", x) 168 | } 169 | 170 | proofs = m.ProveAll(`append([a,b,c], [d,e], List).`) 171 | if len(proofs) != 1 { 172 | t.Errorf("Wrong number of answers: %d vs 1", len(proofs)) 173 | } 174 | if x := proofs[0].ByName_("List").String(); x != "[a,b,c,d,e]" { 175 | t.Errorf("Wrong solution: %s", x) 176 | } 177 | } 178 | 179 | func TestCall(t *testing.T) { 180 | m := NewMachine().Consult(` 181 | bug(spider). 182 | bug(fly). 183 | 184 | squash(Animal, Class) :- 185 | call(Class, Animal). 186 | `) 187 | 188 | proofs := m.ProveAll(`squash(It, bug).`) 189 | if len(proofs) != 2 { 190 | t.Errorf("Wrong number of answers: %d vs 2", len(proofs)) 191 | } 192 | if x := proofs[0].ByName_("It").String(); x != "spider" { 193 | t.Errorf("Wrong solution: %s vs spider", x) 194 | } 195 | if x := proofs[1].ByName_("It").String(); x != "fly" { 196 | t.Errorf("Wrong solution: %s vs fly", x) 197 | } 198 | } 199 | 200 | func TestUnify(t *testing.T) { 201 | m := NewMachine().Consult(` 202 | thing(Z) :- 203 | Z = whatever. 204 | two(X, Y) :- 205 | X = a, 206 | Y = b. 207 | `) 208 | 209 | proofs := m.ProveAll(`thing(It).`) 210 | if len(proofs) != 1 { 211 | t.Errorf("Wrong number of answers: %d vs 1", len(proofs)) 212 | } 213 | if x := proofs[0].ByName_("It").String(); x != "whatever" { 214 | t.Errorf("Wrong solution: %s vs whatever", x) 215 | } 216 | 217 | proofs = m.ProveAll(`two(First, Second).`) 218 | if len(proofs) != 1 { 219 | t.Errorf("Wrong number of answers: %d vs 1", len(proofs)) 220 | } 221 | if x := proofs[0].ByName_("First").String(); x != "a" { 222 | t.Errorf("Wrong solution: %s vs a", x) 223 | } 224 | if x := proofs[0].ByName_("Second").String(); x != "b" { 225 | t.Errorf("Wrong solution: %s vs b", x) 226 | } 227 | 228 | proofs = m.ProveAll(`two(j, k).`) 229 | if len(proofs) != 0 { 230 | t.Errorf("Proved the impossible") 231 | } 232 | } 233 | 234 | func TestDisjunction(t *testing.T) { 235 | m := NewMachine().Consult(` 236 | insect(fly). 237 | arachnid(spider). 238 | squash(Critter) :- 239 | arachnid(Critter) ; insect(Critter). 240 | `) 241 | 242 | proofs := m.ProveAll(`squash(It).`) 243 | if len(proofs) != 2 { 244 | t.Errorf("Wrong number of answers: %d vs 2", len(proofs)) 245 | } 246 | if x := proofs[0].ByName_("It").String(); x != "spider" { 247 | t.Errorf("Wrong solution: %s vs spider", x) 248 | } 249 | if x := proofs[1].ByName_("It").String(); x != "fly" { 250 | t.Errorf("Wrong solution: %s vs fly", x) 251 | } 252 | } 253 | 254 | func TestIfThenElse(t *testing.T) { 255 | m := NewMachine().Consult(` 256 | succeeds(yes). 257 | succeeds(yup). 258 | alpha(X) :- succeeds(yes) -> X = ok. 259 | beta(X) :- succeeds(no) -> X = ok. 260 | `) 261 | 262 | proofs := m.ProveAll(`alpha(Y).`) 263 | if len(proofs) != 1 { 264 | t.Errorf("Wrong number of answers: %d vs 1", len(proofs)) 265 | } 266 | if x := proofs[0].ByName_("Y").String(); x != "ok" { 267 | t.Errorf("Wrong solution: %s vs ok", x) 268 | } 269 | 270 | proofs = m.ProveAll(`beta(Y).`) 271 | if len(proofs) != 0 { 272 | t.Errorf("Wrong number of answers: %d vs 0", len(proofs)) 273 | } 274 | } 275 | 276 | // make sure the effect of !/0 are localized to (\+)/1 277 | func TestNotWithCut(t *testing.T) { 278 | m := NewMachine() 279 | 280 | proofs := m.ProveAll(`\+(!); X=ok.`) 281 | if len(proofs) != 1 { 282 | t.Errorf("Wrong number of answers: %d vs 1", len(proofs)) 283 | } 284 | if x := proofs[0].ByName_("X").String(); x != "ok" { 285 | t.Errorf("Wrong solution: %s vs ok", x) 286 | } 287 | } 288 | 289 | // make sure that CanProve only finds the first solution 290 | func TestCanProveOnce(t *testing.T) { 291 | counter := 0 292 | f := func(m Machine, args []term.Term) ForeignReturn { 293 | counter++ 294 | return ForeignTrue() 295 | } 296 | m := NewMachine().RegisterForeign(map[string]ForeignPredicate{ 297 | "increment_counter/0": f, 298 | }) 299 | m = m.Consult(` 300 | go :- increment_counter. 301 | go :- increment_counter. % increment again on backtrack 302 | `) 303 | 304 | if !m.CanProve(`go.`) { 305 | t.Errorf("Couldn't prove go/0") 306 | } 307 | if counter != 1 { 308 | t.Errorf("CanProve found multiple solutions") 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /builtin.go: -------------------------------------------------------------------------------- 1 | package golog 2 | 3 | // All of Golog's builtin, foreign-implemented predicates 4 | // are defined here. 5 | 6 | import ( 7 | "fmt" 8 | "math/big" 9 | "sort" 10 | "strings" 11 | 12 | "github.com/mndrix/golog/term" 13 | ) 14 | import . "github.com/mndrix/golog/util" 15 | 16 | // !/0 17 | func BuiltinCut(m Machine, args []term.Term) ForeignReturn { 18 | // if were anything to cut, !/0 would have already been 19 | // replaced with '$cut_to/1' Since this goal wasn't there 20 | // must be nothing cut, so treat it as an alias for "true/0" 21 | return ForeignTrue() 22 | } 23 | 24 | // $cut_to/1 25 | // 26 | // An internal system predicate which might be removed at any time 27 | // in the future. It cuts all disjunctions on top of a specific cut 28 | // barrier. 29 | func BuiltinCutTo(m Machine, args []term.Term) ForeignReturn { 30 | barrierId := args[0].(*term.Integer).Value().Int64() 31 | return m.CutTo(barrierId) 32 | } 33 | 34 | // ,/2 35 | func BuiltinComma(m Machine, args []term.Term) ForeignReturn { 36 | return m.PushConj(args[1].(term.Callable)).PushConj(args[0].(term.Callable)) 37 | } 38 | 39 | // ground/1 40 | func BuiltinGround(m Machine, args []term.Term) ForeignReturn { 41 | switch args[0].Type() { 42 | case term.VariableType: 43 | return ForeignFail() 44 | case term.AtomType, 45 | term.IntegerType, 46 | term.FloatType, 47 | term.ErrorType: 48 | return ForeignTrue() 49 | case term.CompoundType: 50 | // recursively evaluate compound term's arguments 51 | x := args[0].(*term.Compound) 52 | for _, arg := range x.Arguments() { 53 | f := BuiltinGround(m, []term.Term{arg}) 54 | switch f.(type) { 55 | case *foreignFail: 56 | return ForeignFail() 57 | } 58 | } 59 | return ForeignTrue() 60 | } 61 | msg := fmt.Sprintf("Unexpected term type: %#v", args[0]) 62 | panic(msg) 63 | } 64 | 65 | // is/2 66 | func BuiltinIs(m Machine, args []term.Term) ForeignReturn { 67 | value := args[0] 68 | expression := args[1] 69 | num, err := term.ArithmeticEval(expression) 70 | MaybePanic(err) 71 | return ForeignUnify(value, num) 72 | } 73 | 74 | // ->/2 75 | func BuiltinIfThen(m Machine, args []term.Term) ForeignReturn { 76 | cond := args[0] 77 | then := args[1] 78 | 79 | // CUT_BARRIER, (cond, !, then) 80 | cut := term.NewCallable("!") 81 | goal := term.NewCallable(",", cond, term.NewCallable(",", cut, then)) 82 | return m.DemandCutBarrier().PushConj(goal) 83 | } 84 | 85 | // ;/2 86 | // 87 | // Implements disjunction and if-then-else. 88 | func BuiltinSemicolon(m Machine, args []term.Term) ForeignReturn { 89 | if term.IsCompound(args[0]) { 90 | ct := args[0].(*term.Compound) 91 | if ct.Arity() == 2 && ct.Name() == "->" { // §7.8.8 92 | return ifThenElse(m, args) 93 | } 94 | } 95 | 96 | cp := NewSimpleChoicePoint(m, args[1]) 97 | return m.PushDisj(cp).PushConj(args[0].(term.Callable)) 98 | } 99 | func ifThenElse(m Machine, args []term.Term) ForeignReturn { 100 | semicolon := args[0].(*term.Compound) 101 | cond := semicolon.Arguments()[0] 102 | then := semicolon.Arguments()[1] 103 | els := args[1] 104 | 105 | // CUT_BARRIER, (call(cond), !, then; else) 106 | cut := term.NewCallable("!") 107 | cond = term.NewCallable("call", cond) 108 | goal := term.NewCallable(",", cond, term.NewCallable(",", cut, then)) 109 | goal = term.NewCallable(";", goal, els) 110 | return m.DemandCutBarrier().PushConj(goal) 111 | } 112 | 113 | // =/2 114 | func BuiltinUnify(m Machine, args []term.Term) ForeignReturn { 115 | return ForeignUnify(args[0], args[1]) 116 | } 117 | 118 | // =:=/2 119 | func BuiltinNumericEquals(m Machine, args []term.Term) ForeignReturn { 120 | // evaluate each arithmetic argument 121 | a, err := term.ArithmeticEval(args[0]) 122 | MaybePanic(err) 123 | b, err := term.ArithmeticEval(args[1]) 124 | MaybePanic(err) 125 | 126 | // perform the actual comparison 127 | if term.NumberCmp(a, b) == 0 { 128 | return ForeignTrue() 129 | } 130 | return ForeignFail() 131 | } 132 | 133 | // ==/2 134 | func BuiltinTermEquals(m Machine, args []term.Term) ForeignReturn { 135 | a := args[0] 136 | b := args[1] 137 | if !term.Precedes(a, b) && !term.Precedes(b, a) { 138 | return ForeignTrue() 139 | } 140 | return ForeignFail() 141 | } 142 | 143 | // \==/2 144 | func BuiltinTermNotEquals(m Machine, args []term.Term) ForeignReturn { 145 | a := args[0] 146 | b := args[1] 147 | if !term.Precedes(a, b) && !term.Precedes(b, a) { 148 | return ForeignFail() 149 | } 150 | return ForeignTrue() 151 | } 152 | 153 | // @/2 174 | func BuiltinTermGreater(m Machine, args []term.Term) ForeignReturn { 175 | a := args[0] 176 | b := args[1] 177 | if term.Precedes(b, a) { 178 | return ForeignTrue() 179 | } 180 | return ForeignFail() 181 | } 182 | 183 | // @>=/2 184 | func BuiltinTermGreaterEquals(m Machine, args []term.Term) ForeignReturn { 185 | a := args[0] 186 | b := args[1] 187 | if term.Precedes(b, a) { 188 | return ForeignTrue() 189 | } 190 | return BuiltinTermEquals(m, args) 191 | } 192 | 193 | // (\+)/1 194 | func BuiltinNot(m Machine, args []term.Term) ForeignReturn { 195 | var answer term.Bindings 196 | var err error 197 | m = m.ClearConjs().ClearDisjs().PushConj(args[0].(term.Callable)) 198 | 199 | for { 200 | m, answer, err = m.Step() 201 | if err == MachineDone { 202 | return ForeignTrue() 203 | } 204 | MaybePanic(err) 205 | if answer != nil { 206 | return ForeignFail() 207 | } 208 | } 209 | } 210 | 211 | // atom_codes/2 see ISO §8.16.5 212 | func BuiltinAtomCodes2(m Machine, args []term.Term) ForeignReturn { 213 | 214 | if !term.IsVariable(args[0]) { 215 | atom := args[0].(*term.Atom) 216 | list := term.NewCodeList(atom.Name()) 217 | return ForeignUnify(args[1], list) 218 | } else if !term.IsVariable(args[1]) { 219 | runes := make([]rune, 0) 220 | list := args[1].(term.Callable) 221 | for { 222 | switch list.Arity() { 223 | case 2: 224 | if list.Name() == "." { 225 | args := list.Arguments() 226 | code := args[0].(*term.Integer) 227 | runes = append(runes, code.Code()) 228 | list = args[1].(term.Callable) 229 | } 230 | case 0: 231 | if list.Name() == "[]" { 232 | atom := term.NewAtom(string(runes)) 233 | return ForeignUnify(args[0], atom) 234 | } 235 | default: 236 | msg := fmt.Sprintf("unexpected code list %s", args[1]) 237 | panic(msg) 238 | } 239 | } 240 | } 241 | 242 | msg := fmt.Sprintf("atom_codes/2: error with the arguments %s and %s", args[0], args[1]) 243 | panic(msg) 244 | } 245 | 246 | // atom_number/2 as defined in SWI-Prolog 247 | func BuiltinAtomNumber2(m Machine, args []term.Term) (ret ForeignReturn) { 248 | number := args[1] 249 | 250 | if !term.IsVariable(args[0]) { 251 | atom := args[0].(term.Callable) 252 | defer func() { // convert parsing panics into fail 253 | if x := recover(); x != nil { 254 | ret = ForeignFail() 255 | } 256 | }() 257 | if strings.Contains(atom.Name(), ".") { 258 | number = term.NewFloat(atom.Name()) 259 | } else { 260 | number = term.NewInt(atom.Name()) 261 | } 262 | return ForeignUnify(args[1], number) 263 | } else if !term.IsVariable(number) { 264 | atom := term.NewAtom(number.String()) 265 | return ForeignUnify(args[0], atom) 266 | } 267 | 268 | msg := fmt.Sprintf("atom_number/2: Arguments are not sufficiently instantiated: %s and %s", args[0], args[1]) 269 | panic(msg) 270 | } 271 | 272 | // call/* 273 | func BuiltinCall(m Machine, args []term.Term) ForeignReturn { 274 | 275 | // build a new goal with extra arguments attached 276 | bodyTerm := args[0].(term.Callable) 277 | functor := bodyTerm.Name() 278 | newArgs := make([]term.Term, 0) 279 | newArgs = append(newArgs, bodyTerm.Arguments()...) 280 | newArgs = append(newArgs, args[1:]...) 281 | goal := term.NewCallable(functor, newArgs...) 282 | 283 | // construct a machine that will prove this goal next 284 | return m.DemandCutBarrier().PushConj(goal) 285 | } 286 | 287 | // downcase_atom(+AnyCase, -LowerCase) 288 | // 289 | // Converts the characters of AnyCase into lowercase and unifies the 290 | // lowercase atom with LowerCase. 291 | func BuiltinDowncaseAtom2(m Machine, args []term.Term) ForeignReturn { 292 | if term.IsVariable(args[0]) { 293 | panic("downcase_atom/2: instantiation_error") 294 | } 295 | anycase := args[0].(term.Callable) 296 | if anycase.Arity() != 0 { 297 | msg := fmt.Sprintf("downcase_atom/2: type_error(atom, %s)", anycase) 298 | panic(msg) 299 | } 300 | 301 | lowercase := term.NewAtom(strings.ToLower(anycase.Name())) 302 | return ForeignUnify(args[1], lowercase) 303 | } 304 | 305 | // fail/0 306 | func BuiltinFail(m Machine, args []term.Term) ForeignReturn { 307 | return ForeignFail() 308 | } 309 | 310 | // findall/3 311 | func BuiltinFindall3(m Machine, args []term.Term) ForeignReturn { 312 | template := args[0] 313 | goal := args[1] 314 | 315 | // call(Goal), X=Template 316 | x := term.NewVar("_") 317 | call := term.NewCallable("call", goal) 318 | unify := term.NewCallable("=", x, template) 319 | prove := term.NewCallable(",", call, unify) 320 | proofs := m.ClearConjs().ClearDisjs().ProveAll(prove) 321 | 322 | // build a list from the results 323 | instances := make([]term.Term, 0) 324 | for _, proof := range proofs { 325 | t, err := proof.Resolve(x) 326 | MaybePanic(err) 327 | instances = append(instances, t) 328 | } 329 | 330 | return ForeignUnify(args[2], term.NewTermList(instances)) 331 | } 332 | 333 | // listing/0 334 | // This should be implemented in pure Prolog, but for debugging purposes, 335 | // I'm doing it for now as a foreign predicate. This will go away. 336 | func BuiltinListing0(m Machine, args []term.Term) ForeignReturn { 337 | fmt.Println(m.String()) 338 | return ForeignTrue() 339 | } 340 | 341 | // msort(+Unsorted:list, -Sorted:list) is det. 342 | // 343 | // True if Sorted is a sorted version of Unsorted. Duplicates are 344 | // not removed. 345 | // 346 | // This is currently implemented using Go's sort.Sort. 347 | // The exact implementation is subject to change. I make no 348 | // guarantees about sort stability. 349 | func BuiltinMsort2(m Machine, args []term.Term) ForeignReturn { 350 | terms := term.ProperListToTermSlice(args[0]) 351 | sort.Sort((*term.TermSlice)(&terms)) 352 | list := term.NewTermList(terms) 353 | return ForeignUnify(args[1], list) 354 | } 355 | 356 | // A temporary hack for debugging. This will disappear once Golog has 357 | // proper support for format/2 358 | func BuiltinPrintf(m Machine, args []term.Term) ForeignReturn { 359 | template := args[0].(*term.Atom).Name() 360 | template = strings.Replace(template, "~n", "\n", -1) 361 | if len(args) == 1 { 362 | fmt.Printf(template) 363 | } else if len(args) == 2 { 364 | fmt.Printf(template, args[1]) 365 | } 366 | return ForeignTrue() 367 | } 368 | 369 | // succ(?A:integer, ?B:integer) is det. 370 | // 371 | // True if B is one greater than A and A >= 0. 372 | func BuiltinSucc2(m Machine, args []term.Term) ForeignReturn { 373 | x := args[0] 374 | y := args[1] 375 | zero := big.NewInt(0) 376 | 377 | if term.IsInteger(x) { 378 | a := x.(*term.Integer) 379 | if a.Value().Cmp(zero) < 0 { 380 | panic("succ/2: first argument must be 0 or greater") 381 | } 382 | result := new(big.Int).Add(a.Value(), big.NewInt(1)) 383 | return ForeignUnify(y, term.NewBigInt(result)) 384 | } else if term.IsInteger(y) { 385 | b := y.(*term.Integer) 386 | result := new(big.Int).Add(b.Value(), big.NewInt(-1)) 387 | if result.Cmp(zero) < 0 { 388 | panic("succ/2: first argument must be 0 or greater") 389 | } 390 | return ForeignUnify(x, term.NewBigInt(result)) 391 | } 392 | 393 | panic("succ/2: one argument must be an integer") 394 | } 395 | 396 | // var(?X) is semidet. 397 | // 398 | // True if X is a variable. 399 | func BuiltinVar1(m Machine, args []term.Term) ForeignReturn { 400 | x := args[0] 401 | 402 | if term.IsVariable(x) { 403 | return ForeignTrue() 404 | } 405 | return ForeignFail() 406 | } 407 | -------------------------------------------------------------------------------- /read/read.go: -------------------------------------------------------------------------------- 1 | // Read Prolog terms. Typical usage is like 2 | // 3 | // terms, err := read.TermAll(`some(prolog,term). another(one).`) 4 | // 5 | // Functions with a trailing underscore panic on error. 6 | package read 7 | 8 | import ( 9 | "fmt" 10 | "io" 11 | "strings" 12 | 13 | "github.com/mndrix/golog/lex" 14 | "github.com/mndrix/golog/term" 15 | ) 16 | 17 | // NoMoreTerms is returned by Next() when it can read no more terms 18 | // from its source. 19 | var NoMoreTerms = fmt.Errorf("No more terms available") 20 | 21 | // ISO operator specifiers per §6.3.4, table 4 22 | type specifier int // xf, yf, xfy, etc. 23 | const ( 24 | fx specifier = iota 25 | fy 26 | xfx 27 | xfy 28 | yfx 29 | xf 30 | yf 31 | ) 32 | 33 | // ISO operator priorities per §6.3.4 34 | type priority int // between 1 and 1200, inclusive 35 | 36 | // Term reads a single term from a term source. A term source can 37 | // be any of the following: 38 | // 39 | // * type that implements io.Reader 40 | // * string 41 | // 42 | // Reading a term may consume more content from the source than is strictly 43 | // necessary. 44 | func Term(src interface{}) (term.Term, error) { 45 | r, err := NewTermReader(src) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return r.Next() 51 | } 52 | 53 | // Term_ is like Term but panics instead of returning an error. 54 | // (Too bad Go doesn't allow ! as an identifier character) 55 | func Term_(src interface{}) term.Term { 56 | t, err := Term(src) 57 | maybePanic(err) 58 | return t 59 | } 60 | 61 | // TermAll reads all available terms from the source 62 | func TermAll(src interface{}) ([]term.Term, error) { 63 | r, err := NewTermReader(src) 64 | if err != nil { 65 | return nil, err 66 | } 67 | return r.all() 68 | } 69 | 70 | // TermAll_ is like TermAll but panics instead of returning an error. 71 | func TermAll_(src interface{}) []term.Term { 72 | ts, err := TermAll(src) 73 | maybePanic(err) 74 | return ts 75 | } 76 | 77 | func toReader(src interface{}) (io.Reader, error) { 78 | if r, ok := src.(io.Reader); ok { 79 | return r, nil 80 | } 81 | switch x := src.(type) { 82 | case string: 83 | return strings.NewReader(x), nil 84 | } 85 | 86 | return nil, fmt.Errorf("Can't convert %#v into io.Reader\n", src) 87 | } 88 | 89 | type TermReader struct { 90 | operators map[string]*[7]priority 91 | ll *lex.List 92 | } 93 | 94 | func NewTermReader(src interface{}) (*TermReader, error) { 95 | ioReader, err := toReader(src) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | tokens := lex.Scan(ioReader) 101 | r := TermReader{ll: lex.NewList(tokens)} 102 | r.ResetOperatorTable() 103 | return &r, nil 104 | } 105 | 106 | // Next returns the next term available from this reader. 107 | // Returns error NoMoreTerms if the reader can't find any more terms. 108 | func (r *TermReader) Next() (term.Term, error) { 109 | var t term.Term 110 | var ll *lex.List 111 | if r.readTerm(1200, r.ll, &ll, &t) { 112 | if term.IsError(t) { 113 | return nil, fmt.Errorf("%s", t.String()) 114 | } 115 | r.ll = ll 116 | return term.RenameVariables(t), nil 117 | } 118 | 119 | return nil, NoMoreTerms 120 | } 121 | 122 | // all returns a slice of all terms available from this reader 123 | func (r *TermReader) all() ([]term.Term, error) { 124 | terms := make([]term.Term, 0) 125 | 126 | t, err := r.Next() 127 | for err == nil { 128 | terms = append(terms, t) 129 | t, err = r.Next() 130 | } 131 | 132 | if err == NoMoreTerms { 133 | err = nil 134 | } 135 | return terms, err 136 | } 137 | 138 | // ResetOperatorTable replaces the reader's current operator table 139 | // with the default table specified in ISO Prolog §6.3.4.4, table 7 140 | func (r *TermReader) ResetOperatorTable() { 141 | r.operators = make(map[string]*[7]priority) 142 | r.Op(1200, xfx, `:-`, `-->`) 143 | r.Op(1200, fx, `:-`, `?-`) 144 | r.Op(1150, fx, `meta_predicate`) // SWI, YAP, etc. extension 145 | r.Op(1100, xfy, `;`) 146 | r.Op(1050, xfy, `->`) 147 | r.Op(1000, xfy, `,`) 148 | r.Op(900, fy, `\+`) 149 | r.Op(700, xfx, `=`, `\=`) 150 | r.Op(700, xfx, `==`, `\==`, `@<`, `@=<`, `@>`, `@>=`) 151 | r.Op(700, xfx, `=..`) 152 | r.Op(700, xfx, `is`, `=:=`, `=\=`, `<`, `=<`, `>`, `>=`) 153 | r.Op(500, yfx, `+`, `-`, `/\`, `\/`) // syntax highlighter ` 154 | r.Op(400, yfx, `*`, `/`, `//`, `rem`, `mod`, `<<`, `<<`) 155 | r.Op(200, xfx, `**`) 156 | r.Op(200, xfy, `^`) 157 | r.Op(200, fy, `-`, `\`) // syntax highlighter ` 158 | } 159 | 160 | // Op creates or changes the parsing behavior of a Prolog operator. 161 | // It's equivalent to op/3 162 | func (r *TermReader) Op(p priority, s specifier, os ...string) { 163 | for _, o := range os { 164 | priorities, ok := r.operators[o] 165 | if !ok { 166 | priorities = new([7]priority) 167 | r.operators[o] = priorities 168 | } 169 | priorities[s] = p 170 | } 171 | } 172 | 173 | // parse a single functor 174 | func (r *TermReader) functor(in *lex.List, out **lex.List, f *string) bool { 175 | if in.Value.Type == lex.Functor { 176 | *f = in.Value.Content 177 | *out = in.Next() // skip functor we just processed 178 | return true 179 | } 180 | 181 | return false 182 | } 183 | 184 | // parse all list items after the first one 185 | func (r *TermReader) listItems(i *lex.List, o **lex.List, t *term.Term) bool { 186 | var arg, rest term.Term 187 | if r.tok(',', i, o) && r.term(999, *o, o, &arg) && r.listItems(*o, o, &rest) { 188 | *t = term.NewCallable(".", arg, rest) 189 | return true 190 | } 191 | if r.tok('|', i, o) && r.term(999, *o, o, &arg) && r.tok(']', *o, o) { 192 | *t = arg 193 | return true 194 | } 195 | if r.tok(']', i, o) { 196 | *t = term.NewAtom("[]") 197 | return true 198 | } 199 | return false 200 | } 201 | 202 | // consume a single character token 203 | func (r *TermReader) tok(c rune, in *lex.List, out **lex.List) bool { 204 | if in.Value.Type == c { 205 | *out = in.Next() 206 | return true 207 | } 208 | return false 209 | } 210 | 211 | // readTerm returns true if it was able to read a term or if there was an 212 | // error reading a term (the error is in a term.Error value). If the stream 213 | // is empty, it returns false. 214 | func (r *TermReader) readTerm(p priority, i *lex.List, o **lex.List, t *term.Term) bool { 215 | // fmt.Printf("\nreading term\n") 216 | if r.tok(lex.EOF, i, o) { 217 | // fmt.Printf("hit EOF\n") 218 | return false 219 | } 220 | 221 | if r.term(p, i, o, t) { 222 | if r.tok(lex.FullStop, *o, o) { 223 | return true 224 | } else { 225 | msg := fmt.Sprintf("expected full stop after `%s` but got `%s`", *t, (*o).Value.Content) 226 | *t = term.NewError(msg, (*o).Value) 227 | return true 228 | } 229 | } 230 | 231 | msg := fmt.Sprintf("expected term but got `%s`", i.Value.Content) 232 | *t = term.NewError(msg, i.Value) 233 | return false 234 | } 235 | 236 | // parse a single term 237 | func (r *TermReader) term(p priority, i *lex.List, o **lex.List, t *term.Term) bool { 238 | var op, f string 239 | var t0, t1 term.Term 240 | var opP, argP priority 241 | // fmt.Printf("seeking term with %s\n", i.Value.Content) 242 | 243 | // prefix operator 244 | if r.prefix(&op, &opP, &argP, i, o) && opP <= p && r.term(argP, *o, o, &t0) { 245 | opT := term.NewCallable(op, t0) 246 | return r.restTerm(opP, p, *o, o, opT, t) 247 | } 248 | 249 | // list notation for compound terms §6.3.5 250 | if r.tok('[', i, o) && r.term(999, *o, o, &t0) && r.listItems(*o, o, &t1) { 251 | list := term.NewCallable(".", t0, t1) 252 | return r.restTerm(0, p, *o, o, list, t) 253 | } 254 | if r.tok('[', i, o) && r.tok(']', *o, o) { 255 | list := term.NewAtom("[]") 256 | return r.restTerm(0, p, *o, o, list, t) 257 | } 258 | 259 | // parenthesized terms 260 | if r.tok('(', i, o) && r.term(1200, *o, o, &t0) && r.tok(')', *o, o) { 261 | // fmt.Printf("open paren %s close paren\n", t0) 262 | return r.restTerm(0, p, *o, o, t0, t) 263 | } 264 | 265 | switch i.Value.Type { 266 | case lex.Int: // integer term §6.3.1.1 267 | n := term.NewInt(i.Value.Content) 268 | *o = i.Next() 269 | return r.restTerm(0, p, *o, o, n, t) 270 | case lex.Float: // float term §6.3.1.1 271 | f := term.NewFloat(i.Value.Content) 272 | *o = i.Next() 273 | return r.restTerm(0, p, *o, o, f, t) 274 | case lex.Atom: // atom term §6.3.1.3 275 | a := term.NewAtomFromLexeme(i.Value.Content) 276 | *o = i.Next() 277 | return r.restTerm(0, p, *o, o, a, t) 278 | case lex.String: // double quated string §6.3.7 279 | cl := term.NewCodeListFromDoubleQuotedString(i.Value.Content) 280 | *o = i.Next() 281 | return r.restTerm(0, p, *o, o, cl, t) 282 | case lex.Variable: // variable term §6.3.2 283 | v := term.NewVar(i.Value.Content) 284 | *o = i.Next() 285 | return r.restTerm(0, p, *o, o, v, t) 286 | case lex.Void: // variable term §6.3.2 287 | v := term.NewVar("_") 288 | *o = i.Next() 289 | return r.restTerm(0, p, *o, o, v, t) 290 | case lex.Comment: 291 | *o = i.Next() // skip the comment 292 | return r.term(p, *o, o, t) // ... and try again 293 | } 294 | 295 | // compound term - functional notation §6.3.3 296 | if r.functor(i, o, &f) && r.tok('(', *o, o) { 297 | var args []term.Term 298 | var arg term.Term 299 | for r.term(999, *o, o, &arg) { // 999 priority per §6.3.3.1 300 | args = append(args, arg) 301 | if r.tok(')', *o, o) { 302 | break 303 | } 304 | if r.tok(',', *o, o) { 305 | continue 306 | } 307 | panic("Unexpected content inside compound term arguments") 308 | } 309 | f := term.NewTermFromLexeme(f, args...) 310 | return r.restTerm(0, p, *o, o, f, t) 311 | } 312 | 313 | *t = term.NewError("Syntax error", i.Value) 314 | return false 315 | } 316 | 317 | func (r *TermReader) restTerm(leftP, p priority, i *lex.List, o **lex.List, leftT term.Term, t *term.Term) bool { 318 | var op string 319 | var rightT term.Term 320 | var opP, lap, rap priority 321 | // fmt.Printf("seeking restTerm @ %d with %s\n", p, i.Value.Content) 322 | 323 | if r.infix(&op, &opP, &lap, &rap, i, o) && p >= opP && leftP <= lap && r.term(rap, *o, o, &rightT) { 324 | // fmt.Printf(" infix %s\n", op) 325 | t0 := term.NewCallable(op, leftT, rightT) 326 | return r.restTerm(opP, p, *o, o, t0, t) 327 | } 328 | if r.postfix(&op, &opP, &lap, i, o) && opP <= p && leftP <= lap { 329 | opT := term.NewCallable(op, leftT) 330 | return r.restTerm(opP, p, *o, o, opT, t) 331 | } 332 | 333 | // ε rule can always succeed 334 | // fmt.Printf(" invoking ε\n") 335 | *o = i 336 | *t = leftT 337 | return true 338 | } 339 | 340 | // consume an infix operator and indicate which one it was along with its priorities 341 | func (r *TermReader) infix(op *string, opP, lap, rap *priority, i *lex.List, o **lex.List) bool { 342 | // fmt.Printf("seeking infix with %s\n", i.Value.Content) 343 | typ := i.Value.Type 344 | if typ != lex.Atom && typ != lex.Functor && typ != ',' { 345 | // fmt.Printf(" type mismatch: %s\n", lex.TokenString(i.Value.Type)) 346 | return false 347 | } 348 | 349 | // is this an operator at all? 350 | name := i.Value.Content 351 | priorities, ok := r.operators[name] 352 | if !ok { 353 | // fmt.Printf(" no operator %s found\n", name) 354 | return false 355 | } 356 | 357 | // what class of operator is it? 358 | switch { 359 | case priorities[yfx] > 0: 360 | *opP = priorities[yfx] 361 | *lap = *opP 362 | *rap = *opP - 1 363 | case priorities[xfy] > 0: 364 | *opP = priorities[xfy] 365 | *lap = *opP - 1 366 | *rap = *opP 367 | case priorities[xfx] > 0: 368 | *opP = priorities[xfx] 369 | *lap = *opP - 1 370 | *rap = *opP - 1 371 | default: // wasn't an infix operator after all 372 | // fmt.Printf(" %s wasn't infix after all", name) 373 | return false 374 | } 375 | 376 | *op = name 377 | *o = i.Next() 378 | // fmt.Printf(" found %s %d %d %d\n", name, *lap, *opP, *rap) 379 | return true 380 | } 381 | 382 | // consume a prefix operator. indicate which one it was along with its priority 383 | func (r *TermReader) prefix(op *string, opP, argP *priority, i *lex.List, o **lex.List) bool { 384 | if i.Value.Type != lex.Atom { 385 | return false 386 | } 387 | 388 | // is this an operator at all? 389 | name := i.Value.Content 390 | priorities, ok := r.operators[name] 391 | if !ok { 392 | return false 393 | } 394 | 395 | // what class of operator is it? 396 | switch { 397 | case priorities[fx] > 0: 398 | *opP = priorities[fx] 399 | *argP = *opP - 1 400 | case priorities[fy] > 0: 401 | *opP = priorities[fy] 402 | *argP = *opP 403 | default: // wasn't a prefix operator after all 404 | return false 405 | } 406 | 407 | *op = name 408 | *o = i.Next() 409 | return true 410 | } 411 | 412 | // consume a postfix operator. indicate which one it was along with its priority 413 | func (r *TermReader) postfix(op *string, opP, argP *priority, i *lex.List, o **lex.List) bool { 414 | if i.Value.Type != lex.Atom { 415 | return false 416 | } 417 | 418 | // is this an operator at all? 419 | name := i.Value.Content 420 | priorities, ok := r.operators[name] 421 | if !ok { 422 | return false 423 | } 424 | 425 | // what class of operator is it? 426 | switch { 427 | case priorities[xf] > 0: 428 | *opP = priorities[xf] 429 | *argP = *opP - 1 430 | case priorities[yf] > 0: 431 | *opP = priorities[yf] 432 | *argP = *opP 433 | default: // wasn't a postfix operator after all 434 | return false 435 | } 436 | 437 | *op = name 438 | *o = i.Next() 439 | return true 440 | } 441 | 442 | func maybePanic(err error) { 443 | if err != nil { 444 | panic(err) 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /term/term.go: -------------------------------------------------------------------------------- 1 | // Represent and unify Prolog terms. Along with golog.Machine, term.Term 2 | // is one of the most important data types in Golog. It provides a Go 3 | // representation of Prolog terms. Terms represent Prolog code, 4 | // Prolog queries and Prolog results. 5 | // 6 | // The current term API is messy and will definitely change in the future. 7 | package term 8 | 9 | import . "fmt" 10 | import . "regexp" 11 | import "math/big" 12 | import "math" 13 | import "strconv" 14 | import "strings" 15 | import "github.com/mndrix/golog/lex" 16 | import "github.com/mndrix/ps" 17 | 18 | // Returned by Unify() if the unification fails 19 | var CantUnify error = Errorf("Can't unify the given terms") 20 | 21 | // Possible term types, in order according to ISO §7.2 22 | const ( 23 | VariableType = iota 24 | FloatType 25 | IntegerType 26 | AtomType 27 | CompoundType 28 | 29 | // odd man out 30 | ErrorType 31 | ) 32 | 33 | // Term represents a single Prolog term which might be an atom, a 34 | // compound structure, an integer, etc. Many methods on Term will 35 | // be replaced with functions in the future. The Term interface is 36 | // also likely to be split into several smaller interfaces like Atomic, 37 | // Number, etc. 38 | type Term interface { 39 | // ReplaceVariables replaces any internal variables with the values 40 | // to which they're bound. Unbound variables are left as they are 41 | ReplaceVariables(Bindings) Term 42 | 43 | // String provides a string representation of a term 44 | String() string 45 | 46 | // Type indicates whether this term is an atom, number, compound, etc. 47 | // ISO §7.2 uses the word "type" to descsribe this idea. Constants are 48 | // defined for each type. 49 | Type() int 50 | 51 | // Indicator() provides a "predicate indicator" representation of a term 52 | Indicator() string 53 | 54 | // Unifies the invocant and another term in the presence of an 55 | // environment. 56 | // On succes, returns a new environment with additional variable 57 | // bindings. On failure, returns CantUnify error along with the 58 | // original environment 59 | Unify(Bindings, Term) (Bindings, error) 60 | } 61 | 62 | // Callable represents either an atom or a compound term. This is the 63 | // terminology used by callable/1 in many Prologs. 64 | type Callable interface { 65 | Term 66 | 67 | // Name returns the term's name. Some people might call this the term's 68 | // functor, but there's ambiguity surrounding that word in the Prolog 69 | // community (some use it for Name/Arity pairs). 70 | Name() string 71 | 72 | // Arity returns the number of arguments a term has. An atom has 0 arity. 73 | Arity() int 74 | 75 | // Arguments returns a slice of this term's arguments, if any 76 | Arguments() []Term 77 | } 78 | 79 | // Returns true if term t is an atom 80 | func IsAtom(t Term) bool { 81 | return t.Type() == AtomType 82 | } 83 | 84 | // IsClause returns true if the term is like 'Head :- Body', otherwise false 85 | func IsClause(t Term) bool { 86 | switch t.Type() { 87 | case CompoundType: 88 | x := t.(*Compound) 89 | return x.Arity() == 2 && x.Name() == ":-" 90 | case AtomType, 91 | VariableType, 92 | IntegerType, 93 | FloatType, 94 | ErrorType: 95 | return false 96 | } 97 | msg := Sprintf("Unexpected term type: %#v", t) 98 | panic(msg) 99 | } 100 | 101 | // Returns true if term t is a compound term. 102 | func IsCompound(t Term) bool { 103 | return t.Type() == CompoundType 104 | } 105 | 106 | // Returns true if term t is an atom or compound term. 107 | func IsCallable(t Term) bool { 108 | tp := t.Type() 109 | return tp == AtomType || tp == CompoundType 110 | } 111 | 112 | // Returns true if term t is a variable. 113 | func IsVariable(t Term) bool { 114 | return t.Type() == VariableType 115 | } 116 | 117 | // Returns true if term t is an error term. 118 | func IsError(t Term) bool { 119 | return t.Type() == ErrorType 120 | } 121 | 122 | // Returns true if term t is a directive like `:- foo.` 123 | func IsDirective(t Term) bool { 124 | return t.Indicator() == ":-/1" 125 | } 126 | 127 | // Head returns a term's first argument. Panics if there isn't one 128 | func Head(t Term) Callable { 129 | return t.(*Compound).Arguments()[0].(Callable) 130 | } 131 | 132 | // Body returns a term's second argument. Panics if there isn't one 133 | func Body(t Term) Callable { 134 | return t.(*Compound).Arguments()[1].(Callable) 135 | } 136 | 137 | // RenameVariables returns a new term like t with all variables replaced 138 | // by fresh ones. 139 | func RenameVariables(t Term) Term { 140 | renamed := make(map[string]*Variable) 141 | return renameVariables(t, renamed) 142 | } 143 | 144 | func renameVariables(t Term, renamed map[string]*Variable) Term { 145 | switch t.Type() { 146 | case FloatType, 147 | IntegerType, 148 | AtomType, 149 | ErrorType: 150 | return t 151 | case CompoundType: 152 | x := t.(*Compound) 153 | newArgs := make([]Term, x.Arity()) 154 | for i, arg := range x.Arguments() { 155 | newArgs[i] = renameVariables(arg, renamed) 156 | } 157 | newTerm := NewCallable(x.Name(), newArgs...) 158 | newTerm.(*Compound).ucache = x.ucache 159 | return newTerm 160 | case VariableType: 161 | x := t.(*Variable) 162 | name := x.Name 163 | if name == "_" { 164 | name = x.Indicator() 165 | } 166 | v, ok := renamed[name] 167 | if ok { 168 | return v 169 | } else { 170 | v = x.WithNewId() 171 | renamed[name] = v 172 | return v 173 | } 174 | } 175 | panic("Unexpected term type") 176 | } 177 | 178 | // Variables returns a ps.Map whose keys are human-readable variable names 179 | // and those values are *Variable used inside term t. 180 | func Variables(t Term) ps.Map { 181 | names := ps.NewMap() 182 | switch t.Type() { 183 | case AtomType, 184 | FloatType, 185 | IntegerType, 186 | ErrorType: 187 | return names 188 | case CompoundType: 189 | x := t.(*Compound) 190 | if x.Arity() == 0 { 191 | return names 192 | } // no variables in an atom 193 | for _, arg := range x.Arguments() { 194 | innerNames := Variables(arg) 195 | innerNames.ForEach(func(key string, val interface{}) { 196 | names = names.Set(key, val) 197 | }) 198 | } 199 | return names 200 | case VariableType: 201 | x := t.(*Variable) 202 | return names.Set(x.Name, x) 203 | } 204 | panic("Unexpected term implementation") 205 | } 206 | 207 | // QuoteFunctor returns a canonical representation of a term's name 208 | // by quoting characters that require quoting 209 | func QuoteFunctor(name string) string { 210 | // cons must be quoted (to avoid confusion with full stop) 211 | if name == "." || name == "" { 212 | return Sprintf("'%s'", name) 213 | } 214 | 215 | // names composed entirely of graphic characters need no quoting 216 | allGraphic := true 217 | for _, c := range name { 218 | if !lex.IsGraphic(c) { 219 | allGraphic = false 220 | break 221 | } 222 | } 223 | if allGraphic || name == "[]" || name == "!" || name == ";" { 224 | return name 225 | } 226 | 227 | nonAlpha, err := MatchString(`\W`, name) 228 | maybePanic(err) 229 | nonLower, err := MatchString(`^[^a-z]`, name) 230 | if nonAlpha || nonLower { 231 | escapedName := strings.Replace(name, `'`, `\'`, -1) 232 | return Sprintf("'%s'", escapedName) 233 | } 234 | 235 | return name 236 | } 237 | 238 | // NewCodeList constructs a list of character codes from a string. 239 | // The string should include opening and closing " characters. 240 | // Nominally, the resulting term is just a chain of cons cells ('.'/2), 241 | // but it might actually be a more efficient implementation under the hood. 242 | func NewCodeListFromDoubleQuotedString(s string) Term { 243 | // make sure the content is long enough 244 | runes := []rune(s) 245 | end := len(runes) - 2 246 | if end < 0 { 247 | msg := Sprintf("Code list string must have bracketing double quotes: %s", s) 248 | panic(msg) 249 | } 250 | 251 | // build a cons cell chain, starting at the end ([]) 252 | codes := NewAtom(`[]`) 253 | for i := end; i > 0; i-- { 254 | c := NewCode(runes[i]) 255 | codes = NewCallable(".", c, codes) 256 | } 257 | 258 | return codes 259 | } 260 | 261 | // Precedes returns true if the first argument 'term-precedes' 262 | // the second argument according to ISO §7.2 263 | func Precedes(a, b Term) bool { 264 | aP := precedence(a) 265 | bP := precedence(b) 266 | if aP < bP { 267 | return true 268 | } 269 | if aP > bP { 270 | return false 271 | } 272 | 273 | // both terms have the same precedence by type, so delve deeper 274 | switch a.Type() { 275 | case VariableType: 276 | x := a.(*Variable) 277 | y := b.(*Variable) 278 | return x.Id() < y.Id() 279 | case FloatType, 280 | IntegerType: // See Note_1 281 | 282 | // comparing via float64 breaks in many, many ways. 283 | // improve as necessary. 284 | x := a.(Number).Float64() 285 | y := b.(Number).Float64() 286 | if x == y && IsFloat(a) && !IsFloat(b) { 287 | return true 288 | } 289 | return x < y 290 | case AtomType: 291 | x := a.(*Atom) 292 | y := b.(*Atom) 293 | return x.Name() < y.Name() 294 | case CompoundType: 295 | x := a.(*Compound) 296 | y := b.(*Compound) 297 | if x.Arity() < y.Arity() { 298 | return true 299 | } 300 | if x.Arity() > y.Arity() { 301 | return false 302 | } 303 | if x.Name() < y.Name() { 304 | return true 305 | } 306 | if x.Name() > y.Name() { 307 | return false 308 | } 309 | for i := 0; i < x.Arity(); i++ { 310 | if Precedes(x.Arguments()[i], y.Arguments()[i]) { 311 | return true 312 | } else if Precedes(y.Arguments()[i], x.Arguments()[i]) { 313 | return false 314 | } 315 | } 316 | return false // identical terms 317 | } 318 | 319 | msg := Sprintf("Unexpected term type %s\n", a) 320 | panic(msg) 321 | } 322 | func precedence(t Term) int { 323 | value := t.Type() // Type() promises values in precedence order 324 | if value == FloatType { // See Note_1 325 | return IntegerType 326 | } 327 | return value 328 | } 329 | 330 | // Note_1: 331 | // 332 | // I've chosen to willfully violate the ISO standard in §7.2 because it 333 | // mandates that floats precede all integers. That means 334 | // `42.3 @< 9` which isn't helpful. I don't deviate lightly, but strongly 335 | // believe it's the right way. 336 | // Incidentally, SWI-Prolog behaves this way by default. 337 | 338 | // UnificationHash generates a special hash value representing the 339 | // terms in a slice. Golog uses these hashes to optimize 340 | // unification. You probably don't need to call this function directly. 341 | // 342 | // In more detail, UnificationHash generates a 64-bit hash which 343 | // represents the shape and content of a term. If two terms share the same 344 | // hash, those terms are likely to unify, although not guaranteed. If 345 | // two terms have different hashes, the two terms are guaranteed not 346 | // to unify. A compound term splits its 64-bit hash into multiple, smaller 347 | // n-bit hashes for its functor and arguments. Other terms occupy the entire 348 | // hash space themselves. 349 | // 350 | // Variables require special handling. During "preparation" we can think of 351 | // 1-bits as representing what content a term "provides". During "query" we 352 | // can think of 1-bits as representing what content a term "requires". 353 | // In the first phase, a variable hashes to all 1s since it can provide 354 | // whatever is needed. In the second phase, a variable hashes to all 0s since 355 | // it demands nothing of the opposing term. 356 | var bigMaxInt64 *big.Int 357 | 358 | func init() { 359 | bigMaxInt64 = big.NewInt(math.MaxInt64) 360 | } 361 | func UnificationHash(terms []Term, n uint, preparation bool) uint64 { 362 | var hash uint64 = 0 363 | var blockSize uint = n / uint(len(terms)) 364 | 365 | // mask to select blockSize least significant bits 366 | var mask uint64 367 | if blockSize == 64 { 368 | mask = math.MaxUint64 369 | } else if blockSize == 0 { 370 | // pretend that terms was a single variable 371 | if preparation { 372 | return (1 << n) - 1 373 | } else { 374 | return 0 375 | } 376 | } else { 377 | mask = (1 << blockSize) - 1 378 | } 379 | 380 | for _, term := range terms { 381 | hash = hash << blockSize 382 | switch t := term.(type) { 383 | case *Atom: 384 | hash = hash | (hashString(t.Name()) & mask) 385 | case *Integer: 386 | if t.Value().Sign() < 0 || t.Value().Cmp(bigMaxInt64) > 0 { 387 | str := Sprintf("%x", t.Value()) 388 | hash = hash | (hashString(str) & mask) 389 | } else { 390 | hash = hash | (uint64(t.Value().Int64()) & mask) 391 | } 392 | case *Rational: 393 | str := t.String() 394 | hash = hash | (hashString(str) & mask) 395 | case *Float: 396 | str := strconv.FormatFloat(t.Value(), 'b', 0, 64) 397 | hash = hash | (hashString(str) & mask) 398 | case *Error: 399 | panic("No UnificationHash for Error terms") 400 | case *Compound: 401 | var termHash uint64 402 | arity := uint(t.Arity()) 403 | if arity == 2 && t.Name() == "." { // don't hash pair's functor 404 | rightSize := blockSize / 2 405 | leftSize := blockSize - rightSize 406 | termHash = UnificationHash(t.Args[0:1], leftSize, preparation) 407 | termHash = termHash << rightSize 408 | termHash |= UnificationHash(t.Args[1:2], rightSize, preparation) 409 | } else { 410 | // how many bits allocated to functor vs arguments? 411 | functorBits := blockSize / (arity + 1) 412 | if functorBits > 12 { 413 | functorBits = 12 414 | } 415 | argumentBits := (blockSize - functorBits) / arity 416 | 417 | // give extra bits (from rounding) back to the functor 418 | functorBits = blockSize - argumentBits*arity 419 | 420 | // generate the hash 421 | var functorMask uint64 = (1 << functorBits) - 1 422 | termHash = hashString(t.Name()) & functorMask 423 | for _, arg := range t.Arguments() { 424 | termHash = termHash << argumentBits 425 | termHash = termHash | UnificationHash([]Term{arg}, argumentBits, preparation) 426 | } 427 | } 428 | hash |= (termHash & mask) 429 | case *Variable: 430 | if preparation { 431 | hash = hash | mask 432 | } 433 | default: 434 | msg := Sprintf("Unexpected term type %s\n", t) 435 | panic(msg) 436 | } 437 | } 438 | 439 | return hash 440 | } 441 | 442 | // constants for FNV-1a hash algorithm 443 | const ( 444 | offset64 uint64 = 14695981039346656037 445 | prime64 uint64 = 1099511628211 446 | ) 447 | 448 | // hashString returns a hash code for a given string 449 | func hashString(x string) uint64 { 450 | hash := offset64 451 | for _, codepoint := range x { 452 | hash ^= uint64(codepoint) 453 | hash *= prime64 454 | } 455 | return hash 456 | } 457 | 458 | // Converts a '.'/2 list terminated in []/0 into a slice of the associated 459 | // terms. Panics if the argument is not a proper list. 460 | func ProperListToTermSlice(x Term) []Term { 461 | l := make([]Term, 0) 462 | t := x.(Callable) 463 | for { 464 | switch t.Indicator() { 465 | case "[]/0": 466 | return l 467 | case "./2": 468 | l = append(l, t.Arguments()[0]) 469 | t = t.Arguments()[1].(Callable) 470 | default: 471 | panic("Improper list") 472 | } 473 | } 474 | } 475 | 476 | // Implement sort.Interface for []Term 477 | type TermSlice []Term 478 | 479 | func (self *TermSlice) Len() int { 480 | ts := []Term(*self) 481 | return len(ts) 482 | } 483 | func (self *TermSlice) Less(i, j int) bool { 484 | ts := []Term(*self) 485 | return Precedes(ts[i], ts[j]) 486 | } 487 | func (self *TermSlice) Swap(i, j int) { 488 | ts := []Term(*self) 489 | tmp := ts[i] 490 | ts[i] = ts[j] 491 | ts[j] = tmp 492 | } 493 | 494 | func maybePanic(err error) { 495 | if err != nil { 496 | panic(err) 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /lex/lex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Copyright 2013 Michael Hendricks. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package lex 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "io" 12 | "strings" 13 | "testing" 14 | "unicode/utf8" 15 | ) 16 | 17 | // A StringReader delivers its data one string segment at a time via Read. 18 | type StringReader struct { 19 | data []string 20 | step int 21 | } 22 | 23 | func (r *StringReader) Read(p []byte) (n int, err error) { 24 | if r.step < len(r.data) { 25 | s := r.data[r.step] 26 | n = copy(p, s) 27 | r.step++ 28 | } else { 29 | err = io.EOF 30 | } 31 | return 32 | } 33 | 34 | func readRuneSegments(t *testing.T, segments []string) { 35 | got := "" 36 | want := strings.Join(segments, "") 37 | s := new(Scanner).Init(&StringReader{data: segments}) 38 | for { 39 | ch := s.Next() 40 | if ch == EOF { 41 | break 42 | } 43 | got += string(ch) 44 | } 45 | if got != want { 46 | t.Errorf("segments=%v got=%s want=%s", segments, got, want) 47 | } 48 | } 49 | 50 | var segmentList = [][]string{ 51 | {}, 52 | {""}, 53 | {"日", "本語"}, 54 | {"\u65e5", "\u672c", "\u8a9e"}, 55 | {"\U000065e5", " ", "\U0000672c", "\U00008a9e"}, 56 | {"\xe6", "\x97\xa5\xe6", "\x9c\xac\xe8\xaa\x9e"}, 57 | {"Hello", ", ", "World", "!"}, 58 | {"Hello", ", ", "", "World", "!"}, 59 | } 60 | 61 | func TestNext(t *testing.T) { 62 | for _, s := range segmentList { 63 | readRuneSegments(t, s) 64 | } 65 | } 66 | 67 | type token struct { 68 | tok rune 69 | text string 70 | } 71 | 72 | var f100 = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 73 | 74 | var tokenList = []token{ 75 | {Comment, "% line comments"}, 76 | {Comment, "%"}, 77 | {Comment, "%%%%"}, 78 | {Comment, "% comment"}, 79 | {Comment, "% /* comment */"}, 80 | {Comment, "% % comment %"}, 81 | {Comment, "%" + f100}, 82 | 83 | {Comment, "% general comments"}, 84 | {Comment, "/**/"}, 85 | {Comment, "/***/"}, 86 | {Comment, "/* comment */"}, 87 | {Comment, "/* % comment */"}, 88 | {Comment, "/* /* embedded */ comment */"}, 89 | {Comment, "/*\n comment\n*/"}, 90 | {Comment, "/*" + f100 + "*/"}, 91 | 92 | {Comment, "% identifiers"}, 93 | {Atom, "a"}, 94 | {Atom, "a0"}, 95 | {Atom, "foobar"}, 96 | {Atom, "abc123"}, 97 | {Atom, "'hello world'"}, 98 | {Atom, "abc123_"}, 99 | {Atom, "äöü"}, 100 | // {Atom, "本"}, // "unicode" package doesn't have IsIdStart() 101 | {Atom, "a۰۱۸"}, 102 | {Atom, "foo६४"}, 103 | {Atom, "bar9876"}, 104 | {Atom, f100}, 105 | {Atom, "+"}, 106 | {Atom, "/"}, 107 | {Atom, "~"}, 108 | {FullStop, "."}, 109 | 110 | {Comment, "% variables"}, 111 | {Variable, "LGTM"}, 112 | {Variable, "ΛΔΡ"}, // starts with uppercase lambda 113 | {Variable, "List0"}, 114 | {Variable, "_abc123"}, 115 | {Variable, "_abc_123_"}, 116 | {Variable, "_äöü"}, 117 | {Variable, "_本"}, 118 | {Void, "_"}, 119 | 120 | {Comment, "% decimal ints"}, 121 | {Int, "0"}, 122 | {Int, "1"}, 123 | {Int, "9"}, 124 | {Int, "42"}, 125 | {Int, "1234567890"}, 126 | 127 | {Comment, "% octal ints"}, 128 | {Int, "00"}, 129 | {Int, "01"}, 130 | {Int, "07"}, 131 | {Int, "042"}, 132 | {Int, "01234567"}, 133 | 134 | {Comment, "% hexadecimal ints"}, 135 | {Int, "0x0"}, 136 | {Int, "0x1"}, 137 | {Int, "0xf"}, 138 | {Int, "0x42"}, 139 | {Int, "0x123456789abcDEF"}, 140 | {Int, "0x" + f100}, 141 | {Int, "0X0"}, 142 | {Int, "0X1"}, 143 | {Int, "0XF"}, 144 | {Int, "0X42"}, 145 | {Int, "0X123456789abcDEF"}, 146 | {Int, "0X" + f100}, 147 | 148 | {Comment, "% floats"}, 149 | {Float, "0.0"}, 150 | {Float, "1.0"}, 151 | {Float, "42.0"}, 152 | {Float, "01234567890.0"}, 153 | {Float, "0e0"}, 154 | {Float, "1e0"}, 155 | {Float, "42e0"}, 156 | {Float, "01234567890e0"}, 157 | {Float, "0E0"}, 158 | {Float, "1E0"}, 159 | {Float, "42E0"}, 160 | {Float, "01234567890E0"}, 161 | {Float, "0e+10"}, 162 | {Float, "1e-10"}, 163 | {Float, "42e+10"}, 164 | {Float, "01234567890e-10"}, 165 | {Float, "0E+10"}, 166 | {Float, "1E-10"}, 167 | {Float, "42E+10"}, 168 | {Float, "01234567890E-10"}, 169 | 170 | {Comment, "% character ints"}, 171 | {Int, `0'\s`}, // space character 172 | {Int, `0'a`}, 173 | {Int, `0'本`}, 174 | {Int, `0'\a`}, 175 | {Int, `0'\b`}, 176 | {Int, `0'\f`}, 177 | {Int, `0'\n`}, 178 | {Int, `0'\r`}, 179 | {Int, `0'\s`}, 180 | {Int, `0'\t`}, 181 | {Int, `0'\v`}, 182 | {Int, `0''`}, 183 | {Int, `0'\000`}, 184 | {Int, `0'\777`}, 185 | {Int, `0'\x00`}, 186 | {Int, `0'\xff`}, 187 | {Int, `0'\u0000`}, 188 | {Int, `0'\ufA16`}, 189 | {Int, `0'\U00000000`}, 190 | {Int, `0'\U0000ffAB`}, 191 | 192 | {Comment, "% strings"}, 193 | {String, `" "`}, 194 | {String, `"a"`}, 195 | {String, `"本"`}, 196 | {String, `"\a"`}, 197 | {String, `"\b"`}, 198 | {String, `"\f"`}, 199 | {String, `"\n"`}, 200 | {String, `"\r"`}, 201 | {String, `"\s"`}, 202 | {String, `"\t"`}, 203 | {String, `"\v"`}, 204 | {String, `"\""`}, 205 | {String, `"\000"`}, 206 | {String, `"\777"`}, 207 | {String, `"\x00"`}, 208 | {String, `"\xff"`}, 209 | {String, `"\u0000"`}, 210 | {String, `"\ufA16"`}, 211 | {String, `"\U00000000"`}, 212 | {String, `"\U0000ffAB"`}, 213 | {String, `"` + f100 + `"`}, 214 | 215 | {Comment, "% individual characters"}, 216 | // NUL character is not allowed 217 | {'\x01', "\x01"}, 218 | {' ' - 1, string(' ' - 1)}, 219 | {'(', "("}, 220 | } 221 | 222 | func makeSource(pattern string) *bytes.Buffer { 223 | var buf bytes.Buffer 224 | for _, k := range tokenList { 225 | fmt.Fprintf(&buf, pattern, k.text) 226 | } 227 | return &buf 228 | } 229 | 230 | func checkTok(t *testing.T, s *Scanner, line int, got, want rune, text string) { 231 | if got != want { 232 | t.Fatalf("tok = %s, want %s for %q", TokenString(got), TokenString(want), text) 233 | } 234 | if s.Line != line { 235 | t.Errorf("line = %d, want %d for %q", s.Line, line, text) 236 | } 237 | stext := s.TokenText() 238 | if stext != text { 239 | t.Errorf("text = %q, want %q", stext, text) 240 | } else { 241 | // check idempotency of TokenText() call 242 | stext = s.TokenText() 243 | if stext != text { 244 | t.Errorf("text = %q, want %q (idempotency check)", stext, text) 245 | } 246 | } 247 | } 248 | 249 | func countNewlines(s string) int { 250 | n := 0 251 | for _, ch := range s { 252 | if ch == '\n' { 253 | n++ 254 | } 255 | } 256 | return n 257 | } 258 | 259 | func testScan(t *testing.T) { 260 | s := new(Scanner).Init(makeSource(" \t%s\n")) 261 | tok := s.Scan() 262 | line := 1 263 | for _, k := range tokenList { 264 | checkTok(t, s, line, tok, k.tok, k.text) 265 | tok = s.Scan() 266 | line += countNewlines(k.text) + 1 // each token is on a new line 267 | } 268 | checkTok(t, s, line, tok, EOF, "") 269 | } 270 | 271 | func TestScan(t *testing.T) { 272 | testScan(t) 273 | } 274 | 275 | func TestPosition(t *testing.T) { 276 | src := makeSource("\t\t\t\t%s\n") 277 | s := new(Scanner).Init(src) 278 | s.Scan() 279 | pos := Position{"", 4, 1, 5} 280 | for _, k := range tokenList { 281 | if s.Offset != pos.Offset { 282 | t.Errorf("offset = %d, want %d for %q", s.Offset, pos.Offset, k.text) 283 | } 284 | if s.Line != pos.Line { 285 | t.Errorf("line = %d, want %d for %q", s.Line, pos.Line, k.text) 286 | } 287 | if s.Column != pos.Column { 288 | t.Errorf("column = %d, want %d for %q", s.Column, pos.Column, k.text) 289 | } 290 | pos.Offset += 4 + len(k.text) + 1 // 4 tabs + token bytes + newline 291 | pos.Line += countNewlines(k.text) + 1 // each token is on a new line 292 | s.Scan() 293 | } 294 | // make sure there were no token-internal errors reported by scanner 295 | if s.ErrorCount != 0 { 296 | t.Errorf("%d errors", s.ErrorCount) 297 | } 298 | } 299 | 300 | func TestScanNext(t *testing.T) { 301 | const BOM = '\uFEFF' 302 | BOMs := string(BOM) 303 | s := new(Scanner).Init(bytes.NewBufferString(BOMs + "if a == bcd /* com" + BOMs + "ment */ {\n\ta += c\n}" + BOMs + "% line comment ending in eof")) 304 | checkTok(t, s, 1, s.Scan(), Atom, "if") // the first BOM is ignored 305 | checkTok(t, s, 1, s.Scan(), Atom, "a") 306 | checkTok(t, s, 1, s.Scan(), Atom, "==") 307 | checkTok(t, s, 0, s.Next(), ' ', "") 308 | checkTok(t, s, 0, s.Next(), 'b', "") 309 | checkTok(t, s, 1, s.Scan(), Atom, "cd") 310 | checkTok(t, s, 1, s.Scan(), Comment, "/* com"+BOMs+"ment */") 311 | checkTok(t, s, 1, s.Scan(), '{', "{") 312 | checkTok(t, s, 2, s.Scan(), Atom, "a") 313 | checkTok(t, s, 2, s.Scan(), Atom, "+=") 314 | checkTok(t, s, 2, s.Scan(), Atom, "c") 315 | checkTok(t, s, 3, s.Scan(), '}', "}") 316 | checkTok(t, s, 3, s.Scan(), BOM, BOMs) 317 | checkTok(t, s, 3, s.Scan(), Comment, "% line comment ending in eof") 318 | checkTok(t, s, 3, s.Scan(), -1, "") 319 | if s.ErrorCount != 0 { 320 | t.Errorf("%d errors", s.ErrorCount) 321 | } 322 | } 323 | 324 | func testError(t *testing.T, src, pos, msg string, tok rune) { 325 | s := new(Scanner).Init(bytes.NewBufferString(src)) 326 | errorCalled := false 327 | s.Error = func(s *Scanner, m string) { 328 | if !errorCalled { 329 | // only look at first error 330 | if p := s.Pos().String(); p != pos { 331 | t.Errorf("pos = %q, want %q for %q", p, pos, src) 332 | } 333 | if m != msg { 334 | t.Errorf("msg = %q, want %q for %q", m, msg, src) 335 | } 336 | errorCalled = true 337 | } 338 | } 339 | tk := s.Scan() 340 | if tk != tok { 341 | t.Errorf("tok = %s, want %s for %q", TokenString(tk), TokenString(tok), src) 342 | } 343 | if !errorCalled { 344 | t.Errorf("error handler not called for %q", src) 345 | } 346 | if s.ErrorCount == 0 { 347 | t.Errorf("count = %d, want > 0 for %q", s.ErrorCount, src) 348 | } 349 | } 350 | 351 | func TestError(t *testing.T) { 352 | testError(t, "\x00", "1:1", "illegal character NUL", 0) 353 | testError(t, "\x80", "1:1", "illegal UTF-8 encoding", utf8.RuneError) 354 | testError(t, "\xff", "1:1", "illegal UTF-8 encoding", utf8.RuneError) 355 | 356 | testError(t, "a\x00", "1:2", "illegal character NUL", Atom) 357 | testError(t, "ab\x80", "1:3", "illegal UTF-8 encoding", Atom) 358 | testError(t, "abc\xff", "1:4", "illegal UTF-8 encoding", Atom) 359 | 360 | testError(t, `"a`+"\x00", "1:3", "illegal character NUL", String) 361 | testError(t, `"ab`+"\x80", "1:4", "illegal UTF-8 encoding", String) 362 | testError(t, `"abc`+"\xff", "1:5", "illegal UTF-8 encoding", String) 363 | 364 | testError(t, `'\"'`, "1:3", "illegal char escape", Atom) 365 | testError(t, `"\'"`, "1:3", "illegal char escape", String) 366 | 367 | testError(t, `01238`, "1:6", "illegal octal number", Int) 368 | testError(t, `01238123`, "1:9", "illegal octal number", Int) 369 | testError(t, `0x`, "1:3", "illegal hexadecimal number", Int) 370 | testError(t, `0xg`, "1:3", "illegal hexadecimal number", Int) 371 | 372 | testError(t, `'`, "1:2", "literal not terminated", Atom) 373 | testError(t, `'`+"\n", "1:2", "literal not terminated", Atom) 374 | testError(t, `"abc`, "1:5", "literal not terminated", String) 375 | testError(t, `"abc`+"\n", "1:5", "literal not terminated", String) 376 | testError(t, `/*/`, "1:4", "comment not terminated", Comment) 377 | } 378 | 379 | func checkPos(t *testing.T, got, want Position) { 380 | if got.Offset != want.Offset || got.Line != want.Line || got.Column != want.Column { 381 | t.Errorf("got offset, line, column = %d, %d, %d; want %d, %d, %d", 382 | got.Offset, got.Line, got.Column, want.Offset, want.Line, want.Column) 383 | } 384 | } 385 | 386 | func checkNextPos(t *testing.T, s *Scanner, offset, line, column int, char rune) { 387 | if ch := s.Next(); ch != char { 388 | t.Errorf("ch = %s, want %s", TokenString(ch), TokenString(char)) 389 | } 390 | want := Position{Offset: offset, Line: line, Column: column} 391 | checkPos(t, s.Pos(), want) 392 | } 393 | 394 | func checkScanPos(t *testing.T, s *Scanner, offset, line, column int, char rune, text string) { 395 | want := Position{Offset: offset, Line: line, Column: column} 396 | if ch := s.Scan(); ch != char { 397 | t.Errorf("ch = %s, want %s", TokenString(ch), TokenString(char)) 398 | } 399 | if text != s.TokenText() { 400 | t.Errorf("tok = %q, want %q", s.TokenText(), text) 401 | } 402 | checkPos(t, s.Position, want) 403 | } 404 | 405 | func TestPos(t *testing.T) { 406 | // corner case: empty source 407 | s := new(Scanner).Init(bytes.NewBufferString("")) 408 | checkPos(t, s.Pos(), Position{Offset: 0, Line: 1, Column: 1}) 409 | s.Peek() // peek doesn't affect the position 410 | checkPos(t, s.Pos(), Position{Offset: 0, Line: 1, Column: 1}) 411 | 412 | // corner case: source with only a newline 413 | s = new(Scanner).Init(bytes.NewBufferString("\n")) 414 | checkPos(t, s.Pos(), Position{Offset: 0, Line: 1, Column: 1}) 415 | checkNextPos(t, s, 1, 2, 1, '\n') 416 | // after EOF position doesn't change 417 | for i := 10; i > 0; i-- { 418 | checkScanPos(t, s, 1, 2, 1, EOF, "") 419 | } 420 | if s.ErrorCount != 0 { 421 | t.Errorf("%d errors", s.ErrorCount) 422 | } 423 | 424 | // corner case: source with only a single character 425 | s = new(Scanner).Init(bytes.NewBufferString("j")) 426 | checkPos(t, s.Pos(), Position{Offset: 0, Line: 1, Column: 1}) 427 | checkNextPos(t, s, 1, 1, 2, 'j') 428 | // after EOF position doesn't change 429 | for i := 10; i > 0; i-- { 430 | checkScanPos(t, s, 1, 1, 2, EOF, "") 431 | } 432 | if s.ErrorCount != 0 { 433 | t.Errorf("%d errors", s.ErrorCount) 434 | } 435 | 436 | // positions after calling Next 437 | s = new(Scanner).Init(bytes.NewBufferString(" foo६४ \n\n本語\n")) 438 | s.Peek() // peek doesn't affect the position 439 | s.Next() 440 | s.Next() 441 | checkNextPos(t, s, 3, 1, 4, 'f') 442 | checkNextPos(t, s, 4, 1, 5, 'o') 443 | checkNextPos(t, s, 5, 1, 6, 'o') 444 | checkNextPos(t, s, 8, 1, 7, '६') 445 | checkNextPos(t, s, 11, 1, 8, '४') 446 | s.Next() 447 | s.Next() 448 | s.Next() 449 | s.Next() 450 | checkNextPos(t, s, 18, 3, 2, '本') 451 | checkNextPos(t, s, 21, 3, 3, '語') 452 | // after EOF position doesn't change 453 | s.Next() 454 | for i := 10; i > 0; i-- { 455 | checkScanPos(t, s, 22, 4, 1, EOF, "") 456 | } 457 | if s.ErrorCount != 0 { 458 | t.Errorf("%d errors", s.ErrorCount) 459 | } 460 | 461 | // positions after calling Scan 462 | s = new(Scanner).Init(bytes.NewBufferString("abc\nλα\n\nx")) 463 | checkScanPos(t, s, 0, 1, 1, Atom, "abc") 464 | s.Peek() // peek doesn't affect the position 465 | s.Next() 466 | checkScanPos(t, s, 4, 2, 1, Atom, "λα") 467 | s.Next() 468 | s.Next() 469 | checkScanPos(t, s, 10, 4, 1, Atom, "x") 470 | // after EOF position doesn't change 471 | for i := 10; i > 0; i-- { 472 | checkScanPos(t, s, 11, 4, 2, EOF, "") 473 | } 474 | if s.ErrorCount != 0 { 475 | t.Errorf("%d errors", s.ErrorCount) 476 | } 477 | } 478 | 479 | // similar in spirit to the Acid Tests by the web standards project. 480 | // in a single sample, try to include everything that might be hard to lex 481 | const acidTest = `/* multiline 482 | and /* embedded */ 483 | comment */ 484 | thing(A) :- foo(A, bar, "baz"), !. 485 | thing(_) :- 486 | format("~p~p~n", [hello, world]). 487 | % entire line comment 488 | ...('tschüß', 9, 3.14). % postfix comment 489 | greek(λαμβδα, 0'\n, 0'a). 490 | /+(1, 2). 491 | hello.% still a valid term 492 | append([],L,L). 493 | append([A|B], [A|C]). 494 | X = ''. 495 | 'one two'(three) :- four. 496 | ` 497 | 498 | func TestAcid(t *testing.T) { 499 | s := new(Scanner).Init(bytes.NewBufferString(acidTest)) 500 | checkScanPos(t, s, 0, 1, 1, Comment, "/* multiline\nand /* embedded */\ncomment */") 501 | 502 | checkScanPos(t, s, 43, 4, 1, Functor, "thing") 503 | checkScanPos(t, s, 48, 4, 6, '(', "(") 504 | checkScanPos(t, s, 49, 4, 7, Variable, "A") 505 | checkScanPos(t, s, 50, 4, 8, ')', ")") 506 | checkScanPos(t, s, 52, 4, 10, Atom, ":-") 507 | checkScanPos(t, s, 55, 4, 13, Functor, "foo") 508 | checkScanPos(t, s, 58, 4, 16, '(', "(") 509 | checkScanPos(t, s, 59, 4, 17, Variable, "A") 510 | checkScanPos(t, s, 60, 4, 18, ',', ",") 511 | checkScanPos(t, s, 62, 4, 20, Atom, "bar") 512 | checkScanPos(t, s, 65, 4, 23, ',', ",") 513 | checkScanPos(t, s, 67, 4, 25, String, `"baz"`) 514 | checkScanPos(t, s, 72, 4, 30, ')', ")") 515 | checkScanPos(t, s, 73, 4, 31, ',', ",") 516 | checkScanPos(t, s, 75, 4, 33, Atom, "!") 517 | checkScanPos(t, s, 76, 4, 34, FullStop, ".") 518 | 519 | checkScanPos(t, s, 78, 5, 1, Functor, "thing") 520 | checkScanPos(t, s, 83, 5, 6, '(', "(") 521 | checkScanPos(t, s, 84, 5, 7, Void, "_") 522 | checkScanPos(t, s, 85, 5, 8, ')', ")") 523 | checkScanPos(t, s, 87, 5, 10, Atom, ":-") 524 | checkScanPos(t, s, 94, 6, 5, Functor, "format") 525 | checkScanPos(t, s, 100, 6, 11, '(', "(") 526 | checkScanPos(t, s, 101, 6, 12, String, `"~p~p~n"`) 527 | checkScanPos(t, s, 109, 6, 20, ',', ",") 528 | checkScanPos(t, s, 111, 6, 22, '[', "[") 529 | checkScanPos(t, s, 112, 6, 23, Atom, "hello") 530 | checkScanPos(t, s, 117, 6, 28, ',', ",") 531 | checkScanPos(t, s, 119, 6, 30, Atom, "world") 532 | checkScanPos(t, s, 124, 6, 35, ']', "]") 533 | checkScanPos(t, s, 125, 6, 36, ')', ")") 534 | checkScanPos(t, s, 126, 6, 37, FullStop, ".") 535 | 536 | checkScanPos(t, s, 128, 7, 1, Comment, "% entire line comment") 537 | 538 | checkScanPos(t, s, 150, 8, 1, Functor, "...") 539 | checkScanPos(t, s, 153, 8, 4, '(', "(") 540 | checkScanPos(t, s, 154, 8, 5, Atom, "'tschüß'") 541 | checkScanPos(t, s, 164, 8, 13, ',', ",") 542 | checkScanPos(t, s, 166, 8, 15, Int, "9") 543 | checkScanPos(t, s, 167, 8, 16, ',', ",") 544 | checkScanPos(t, s, 169, 8, 18, Float, "3.14") 545 | checkScanPos(t, s, 173, 8, 22, ')', ")") 546 | checkScanPos(t, s, 174, 8, 23, FullStop, ".") 547 | checkScanPos(t, s, 177, 8, 26, Comment, "% postfix comment") 548 | 549 | checkScanPos(t, s, 195, 9, 1, Functor, "greek") 550 | checkScanPos(t, s, 200, 9, 6, '(', "(") 551 | checkScanPos(t, s, 201, 9, 7, Atom, "λαμβδα") 552 | checkScanPos(t, s, 213, 9, 13, ',', ",") 553 | checkScanPos(t, s, 215, 9, 15, Int, `0'\n`) 554 | checkScanPos(t, s, 219, 9, 19, ',', ",") 555 | checkScanPos(t, s, 221, 9, 21, Int, `0'a`) 556 | checkScanPos(t, s, 224, 9, 24, ')', ")") 557 | checkScanPos(t, s, 225, 9, 25, FullStop, ".") 558 | 559 | checkScanPos(t, s, 227, 10, 1, Functor, "/+") 560 | checkScanPos(t, s, 229, 10, 3, '(', "(") 561 | checkScanPos(t, s, 230, 10, 4, Int, "1") 562 | checkScanPos(t, s, 231, 10, 5, ',', ",") 563 | checkScanPos(t, s, 233, 10, 7, Int, "2") 564 | checkScanPos(t, s, 234, 10, 8, ')', ")") 565 | checkScanPos(t, s, 235, 10, 9, FullStop, ".") 566 | 567 | checkScanPos(t, s, 237, 11, 1, Atom, "hello") 568 | checkScanPos(t, s, 242, 11, 6, FullStop, ".") 569 | checkScanPos(t, s, 243, 11, 7, Comment, "% still a valid term") 570 | 571 | checkScanPos(t, s, 264, 12, 1, Functor, "append") 572 | checkScanPos(t, s, 270, 12, 7, '(', "(") 573 | checkScanPos(t, s, 271, 12, 8, '[', "[") 574 | checkScanPos(t, s, 272, 12, 9, ']', "]") 575 | checkScanPos(t, s, 273, 12, 10, ',', ",") 576 | checkScanPos(t, s, 274, 12, 11, Variable, "L") 577 | checkScanPos(t, s, 275, 12, 12, ',', ",") 578 | checkScanPos(t, s, 276, 12, 13, Variable, "L") 579 | checkScanPos(t, s, 277, 12, 14, ')', ")") 580 | checkScanPos(t, s, 278, 12, 15, FullStop, ".") 581 | 582 | checkScanPos(t, s, 280, 13, 1, Functor, "append") 583 | checkScanPos(t, s, 286, 13, 7, '(', "(") 584 | checkScanPos(t, s, 287, 13, 8, '[', "[") 585 | checkScanPos(t, s, 288, 13, 9, Variable, "A") 586 | checkScanPos(t, s, 289, 13, 10, '|', "|") 587 | checkScanPos(t, s, 290, 13, 11, Variable, "B") 588 | checkScanPos(t, s, 291, 13, 12, ']', "]") 589 | checkScanPos(t, s, 292, 13, 13, ',', ",") 590 | checkScanPos(t, s, 294, 13, 15, '[', "[") 591 | checkScanPos(t, s, 295, 13, 16, Variable, "A") 592 | checkScanPos(t, s, 296, 13, 17, '|', "|") 593 | checkScanPos(t, s, 297, 13, 18, Variable, "C") 594 | checkScanPos(t, s, 298, 13, 19, ']', "]") 595 | checkScanPos(t, s, 299, 13, 20, ')', ")") 596 | checkScanPos(t, s, 300, 13, 21, FullStop, ".") 597 | 598 | checkScanPos(t, s, 302, 14, 1, Variable, "X") 599 | checkScanPos(t, s, 304, 14, 3, Atom, "=") 600 | checkScanPos(t, s, 306, 14, 5, Atom, `''`) 601 | checkScanPos(t, s, 308, 14, 7, FullStop, ".") 602 | 603 | // 'one two'(three) :- four. 604 | checkScanPos(t, s, 310, 15, 1, Functor, "'one two'") 605 | checkScanPos(t, s, 319, 15, 10, '(', "(") 606 | checkScanPos(t, s, 320, 15, 11, Atom, "three") 607 | checkScanPos(t, s, 325, 15, 16, ')', ")") 608 | checkScanPos(t, s, 327, 15, 18, Atom, ":-") 609 | checkScanPos(t, s, 330, 15, 21, Atom, "four") 610 | checkScanPos(t, s, 334, 15, 25, FullStop, ".") 611 | 612 | checkScanPos(t, s, 336, 16, 1, EOF, "") 613 | } 614 | -------------------------------------------------------------------------------- /lex/lex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Copyright 2013 Michael Hendricks. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Tokenize UTF-8-encoded Prolog text. 7 | // It takes an io.Reader providing the source, which then can be tokenized 8 | // with the Scan function. For compatibility with 9 | // existing tools, the NUL character is not allowed. If the first character 10 | // in the source is a UTF-8 encoded byte order mark (BOM), it is discarded. 11 | // 12 | // Basic usage pattern: 13 | // 14 | // lexemes := lex.Scan(file) 15 | // for lexeme := range lexemes { 16 | // // do something with lexeme 17 | // } 18 | // 19 | package lex 20 | 21 | import ( 22 | "bytes" 23 | "fmt" 24 | "io" 25 | "os" 26 | "unicode" 27 | "unicode/utf8" 28 | ) 29 | 30 | // A lex.Eme encapsulating its type and content 31 | type Eme struct { 32 | Type rune // EOF, Atom, Comment, etc. 33 | Content string 34 | Pos *Position 35 | } 36 | 37 | // Scan tokenizes src in a separate goroutine sending lexemes down a 38 | // channel as they become available. The channel is closed on EOF. 39 | func Scan(src io.Reader) <-chan *Eme { 40 | ch := make(chan *Eme) 41 | go func() { 42 | s := new(Scanner).Init(src) 43 | p := s.Pos() 44 | tok := s.Scan() 45 | for tok != EOF { 46 | l := &Eme{ 47 | Type: tok, 48 | Content: s.TokenText(), 49 | Pos: &p, 50 | } 51 | ch <- l 52 | p = s.Pos() 53 | tok = s.Scan() 54 | } 55 | close(ch) 56 | }() 57 | return ch 58 | } 59 | 60 | // A source position is represented by a Position value. 61 | // A position is valid if Line > 0. 62 | type Position struct { 63 | Filename string // filename, if any 64 | Offset int // byte offset, starting at 0 65 | Line int // line number, starting at 1 66 | Column int // column number, starting at 1 (character count per line) 67 | } 68 | 69 | // IsValid returns true if the position is valid. 70 | func (pos *Position) IsValid() bool { return pos.Line > 0 } 71 | 72 | func (pos Position) String() string { 73 | s := pos.Filename 74 | if pos.IsValid() { 75 | if s != "" { 76 | s += ":" 77 | } 78 | s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) 79 | } 80 | if s == "" { 81 | s = "???" 82 | } 83 | return s 84 | } 85 | 86 | // The result of Scan is one of these tokens or a Unicode character. 87 | const ( 88 | EOF = -(iota + 1) // reached end of source 89 | Atom // a Prolog atom, possibly quoted 90 | Comment // a comment 91 | Float // a floating point number 92 | Functor // an atom used as a predicate functor 93 | FullStop // "." ending a term 94 | Int // an integer 95 | String // a double-quoted string 96 | Variable // a Prolog variable 97 | Void // the special "_" variable 98 | ) 99 | 100 | var tokenString = map[rune]string{ 101 | EOF: "EOF", 102 | Atom: "Atom", 103 | Comment: "Comment", 104 | Float: "Float", 105 | Functor: "Functor", 106 | FullStop: "FullStop", 107 | Int: "Int", 108 | String: "String", 109 | Variable: "Variable", 110 | Void: "Void", 111 | } 112 | 113 | // TokenString returns a printable string for a token or Unicode character. 114 | func TokenString(tok rune) string { 115 | if s, found := tokenString[tok]; found { 116 | return s 117 | } 118 | return fmt.Sprintf("%q", string(tok)) 119 | } 120 | 121 | const bufLen = 1024 // at least utf8.UTFMax 122 | 123 | // A Scanner implements reading of Unicode characters and tokens from an io.Reader. 124 | type Scanner struct { 125 | // Input 126 | src io.Reader 127 | 128 | // Source buffer 129 | srcBuf [bufLen + 1]byte // +1 for sentinel for common case of s.next() 130 | srcPos int // reading position (srcBuf index) 131 | srcEnd int // source end (srcBuf index) 132 | 133 | // Source position 134 | srcBufOffset int // byte offset of srcBuf[0] in source 135 | line int // line count 136 | column int // character count 137 | lastLineLen int // length of last line in characters (for correct column reporting) 138 | lastCharLen int // length of last character in bytes 139 | 140 | // Token text buffer 141 | // Typically, token text is stored completely in srcBuf, but in general 142 | // the token text's head may be buffered in tokBuf while the token text's 143 | // tail is stored in srcBuf. 144 | tokBuf bytes.Buffer // token text head that is not in srcBuf anymore 145 | tokPos int // token text tail position (srcBuf index); valid if >= 0 146 | tokEnd int // token text tail end (srcBuf index) 147 | 148 | // One character look-ahead 149 | ch rune // character before current srcPos 150 | 151 | extraTok rune // an extra token accidentally read early 152 | 153 | // Error is called for each error encountered. If no Error 154 | // function is set, the error is reported to os.Stderr. 155 | Error func(s *Scanner, msg string) 156 | 157 | // ErrorCount is incremented by one for each error encountered. 158 | ErrorCount int 159 | 160 | // Start position of most recently scanned token; set by Scan. 161 | // Calling Init or Next invalidates the position (Line == 0). 162 | // The Filename field is always left untouched by the Scanner. 163 | // If an error is reported (via Error) and Position is invalid, 164 | // the scanner is not inside a token. Call Pos to obtain an error 165 | // position in that case. 166 | Position 167 | } 168 | 169 | // Init initializes a Scanner with a new source and returns s. 170 | // Error is set to nil, ErrorCount is set to 0 171 | func (s *Scanner) Init(src io.Reader) *Scanner { 172 | s.src = src 173 | 174 | // initialize source buffer 175 | // (the first call to next() will fill it by calling src.Read) 176 | s.srcBuf[0] = utf8.RuneSelf // sentinel 177 | s.srcPos = 0 178 | s.srcEnd = 0 179 | 180 | // initialize source position 181 | s.srcBufOffset = 0 182 | s.line = 1 183 | s.column = 0 184 | s.lastLineLen = 0 185 | s.lastCharLen = 0 186 | 187 | // initialize token text buffer 188 | // (required for first call to next()). 189 | s.tokPos = -1 190 | 191 | // initialize one character look-ahead 192 | s.ch = -1 // no char read yet 193 | 194 | // initialize extra token 195 | s.extraTok = 0 196 | 197 | // initialize public fields 198 | s.Error = nil 199 | s.ErrorCount = 0 200 | s.Line = 0 // invalidate token position 201 | 202 | return s 203 | } 204 | 205 | // next reads and returns the next Unicode character. It is designed such 206 | // that only a minimal amount of work needs to be done in the common ASCII 207 | // case (one test to check for both ASCII and end-of-buffer, and one test 208 | // to check for newlines). 209 | func (s *Scanner) next() rune { 210 | // if there's an extra token, return it instead of scanning a new one 211 | if s.extraTok != 0 { 212 | ch := s.extraTok 213 | s.extraTok = 0 214 | return ch 215 | } 216 | 217 | ch, width := rune(s.srcBuf[s.srcPos]), 1 218 | 219 | if ch >= utf8.RuneSelf { 220 | // uncommon case: not ASCII or not enough bytes 221 | for s.srcPos+utf8.UTFMax > s.srcEnd && !utf8.FullRune(s.srcBuf[s.srcPos:s.srcEnd]) { 222 | // not enough bytes: read some more, but first 223 | // save away token text if any 224 | if s.tokPos >= 0 { 225 | s.tokBuf.Write(s.srcBuf[s.tokPos:s.srcPos]) 226 | s.tokPos = 0 227 | // s.tokEnd is set by Scan() 228 | } 229 | // move unread bytes to beginning of buffer 230 | copy(s.srcBuf[0:], s.srcBuf[s.srcPos:s.srcEnd]) 231 | s.srcBufOffset += s.srcPos 232 | // read more bytes 233 | // (an io.Reader must return io.EOF when it reaches 234 | // the end of what it is reading - simply returning 235 | // n == 0 will make this loop retry forever; but the 236 | // error is in the reader implementation in that case) 237 | i := s.srcEnd - s.srcPos 238 | n, err := s.src.Read(s.srcBuf[i:bufLen]) 239 | s.srcPos = 0 240 | s.srcEnd = i + n 241 | s.srcBuf[s.srcEnd] = utf8.RuneSelf // sentinel 242 | if err != nil { 243 | if s.srcEnd == 0 { 244 | if s.lastCharLen > 0 { 245 | // previous character was not EOF 246 | s.column++ 247 | } 248 | s.lastCharLen = 0 249 | return EOF 250 | } 251 | if err != io.EOF { 252 | s.error(err.Error()) 253 | } 254 | // If err == EOF, we won't be getting more 255 | // bytes; break to avoid infinite loop. If 256 | // err is something else, we don't know if 257 | // we can get more bytes; thus also break. 258 | break 259 | } 260 | } 261 | // at least one byte 262 | ch = rune(s.srcBuf[s.srcPos]) 263 | if ch >= utf8.RuneSelf { 264 | // uncommon case: not ASCII 265 | ch, width = utf8.DecodeRune(s.srcBuf[s.srcPos:s.srcEnd]) 266 | if ch == utf8.RuneError && width == 1 { 267 | // advance for correct error position 268 | s.srcPos += width 269 | s.lastCharLen = width 270 | s.column++ 271 | s.error("illegal UTF-8 encoding") 272 | return ch 273 | } 274 | } 275 | } 276 | 277 | // advance 278 | s.srcPos += width 279 | s.lastCharLen = width 280 | s.column++ 281 | 282 | // special situations 283 | switch ch { 284 | case 0: 285 | // for compatibility with other tools 286 | s.error("illegal character NUL") 287 | case '\n': 288 | s.line++ 289 | s.lastLineLen = s.column 290 | s.column = 0 291 | } 292 | 293 | return ch 294 | } 295 | 296 | // Next reads and returns the next Unicode character. 297 | // It returns EOF at the end of the source. It reports 298 | // a read error by calling s.Error, if not nil; otherwise 299 | // it prints an error message to os.Stderr. Next does not 300 | // update the Scanner's Position field; use Pos() to 301 | // get the current position. 302 | func (s *Scanner) Next() rune { 303 | s.tokPos = -1 // don't collect token text 304 | s.Line = 0 // invalidate token position 305 | ch := s.Peek() 306 | s.ch = s.next() 307 | return ch 308 | } 309 | 310 | // Peek returns the next Unicode character in the source without advancing 311 | // the scanner. It returns EOF if the scanner's position is at the last 312 | // character of the source. 313 | func (s *Scanner) Peek() rune { 314 | if s.ch < 0 { 315 | // this code is only run for the very first character 316 | s.ch = s.next() 317 | if s.ch == '\uFEFF' { 318 | s.ch = s.next() // ignore BOM 319 | } 320 | } 321 | return s.ch 322 | } 323 | 324 | func (s *Scanner) error(msg string) { 325 | s.ErrorCount++ 326 | if s.Error != nil { 327 | s.Error(s, msg) 328 | return 329 | } 330 | pos := s.Position 331 | if !pos.IsValid() { 332 | pos = s.Pos() 333 | } 334 | fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg) 335 | } 336 | 337 | func (s *Scanner) scanAlphanumeric(ch rune) rune { 338 | for isAlphanumeric(ch) { 339 | ch = s.next() 340 | } 341 | return ch 342 | } 343 | 344 | func (s *Scanner) scanGraphic(ch rune) rune { 345 | for IsGraphic(ch) { 346 | ch = s.next() 347 | } 348 | return ch 349 | } 350 | 351 | func digitVal(ch rune) int { 352 | switch { 353 | case '0' <= ch && ch <= '9': 354 | return int(ch - '0') 355 | case 'a' <= ch && ch <= 'f': 356 | return int(ch - 'a' + 10) 357 | case 'A' <= ch && ch <= 'F': 358 | return int(ch - 'A' + 10) 359 | } 360 | return 16 // larger than any legal digit val 361 | } 362 | 363 | func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' } 364 | 365 | // True if the rune is a graphic token char per ISO §6.4.2 366 | func IsGraphic(ch rune) bool { 367 | return isOneOf(ch, `#$&*+-./:<=>?@^\~`) 368 | } 369 | 370 | // ISO §6.5.2 "alphanumeric char" extended to Unicode 371 | func isAlphanumeric(ch rune) bool { 372 | if ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch) { 373 | return true 374 | } 375 | return false 376 | } 377 | 378 | // true if the rune is a valid start for a variable 379 | func isVariableStart(ch rune) bool { 380 | return ch == '_' || unicode.IsUpper(ch) 381 | } 382 | 383 | func isOneOf(ch rune, chars string) bool { 384 | for _, allowed := range chars { 385 | if ch == allowed { 386 | return true 387 | } 388 | } 389 | return false 390 | } 391 | 392 | func isSolo(ch rune) bool { return ch == '!' || ch == ';' } 393 | 394 | func (s *Scanner) scanMantissa(ch rune) rune { 395 | for isDecimal(ch) { 396 | ch = s.next() 397 | } 398 | return ch 399 | } 400 | 401 | func (s *Scanner) scanFraction(ch rune) rune { 402 | if ch == '.' { 403 | ch = s.scanMantissa(s.next()) 404 | } 405 | return ch 406 | } 407 | 408 | func (s *Scanner) scanExponent(ch rune) rune { 409 | if ch == 'e' || ch == 'E' { 410 | ch = s.next() 411 | if ch == '-' || ch == '+' { 412 | ch = s.next() 413 | } 414 | ch = s.scanMantissa(ch) 415 | } 416 | return ch 417 | } 418 | 419 | func (s *Scanner) scanNumber(ch rune) (rune, rune, rune) { 420 | // isDecimal(ch) 421 | if ch == '0' { 422 | // int or float 423 | ch = s.next() 424 | switch ch { 425 | case 'x', 'X': 426 | // hexadecimal int 427 | ch = s.next() 428 | hasMantissa := false 429 | for digitVal(ch) < 16 { 430 | ch = s.next() 431 | hasMantissa = true 432 | } 433 | if !hasMantissa { 434 | s.error("illegal hexadecimal number") 435 | } 436 | case '\'': 437 | ch = s.next() 438 | if ch == '\\' { 439 | ch = s.scanEscape('\'') 440 | } else { 441 | ch = s.next() 442 | } 443 | default: 444 | // octal int or float 445 | has8or9 := false 446 | for isDecimal(ch) { 447 | if ch > '7' { 448 | has8or9 = true 449 | } 450 | ch = s.next() 451 | } 452 | if ch == '.' || ch == 'e' || ch == 'E' { 453 | // float 454 | ch = s.scanFraction(ch) 455 | ch = s.scanExponent(ch) 456 | return Float, ch, 0 457 | } 458 | // octal int 459 | if has8or9 { 460 | s.error("illegal octal number") 461 | } 462 | } 463 | return Int, ch, 0 464 | } 465 | // decimal int or float 466 | ch = s.scanMantissa(ch) 467 | if ch == 'e' || ch == 'E' { // float 468 | ch = s.scanExponent(ch) 469 | return Float, ch, 0 470 | } 471 | if ch == '.' { 472 | ch = s.next() 473 | if isDecimal(ch) { 474 | ch = s.scanMantissa(ch) 475 | ch = s.scanExponent(ch) 476 | return Float, ch, 0 477 | } 478 | return Int, ch, FullStop 479 | } 480 | return Int, ch, 0 481 | } 482 | 483 | func (s *Scanner) scanDigits(ch rune, base, n int) rune { 484 | for n > 0 && digitVal(ch) < base { 485 | ch = s.next() 486 | n-- 487 | } 488 | if n > 0 { 489 | s.error("illegal char escape") 490 | } 491 | return ch 492 | } 493 | 494 | func (s *Scanner) scanEscape(quote rune) rune { 495 | ch := s.next() // read character after '/' 496 | switch ch { 497 | case 'a', 'b', 'f', 'n', 'r', 's', 't', 'v', '\\', quote: 498 | // nothing to do 499 | ch = s.next() 500 | case '0', '1', '2', '3', '4', '5', '6', '7': 501 | ch = s.scanDigits(ch, 8, 3) 502 | case 'x': 503 | ch = s.scanDigits(s.next(), 16, 2) 504 | case 'u': 505 | ch = s.scanDigits(s.next(), 16, 4) 506 | case 'U': 507 | ch = s.scanDigits(s.next(), 16, 8) 508 | default: 509 | s.error("illegal char escape") 510 | } 511 | return ch 512 | } 513 | 514 | func (s *Scanner) scanString(quote rune) (n int) { 515 | ch := s.next() // read character after quote 516 | for ch != quote { 517 | if ch == '\n' || ch < 0 { 518 | s.error("literal not terminated") 519 | return 520 | } 521 | if ch == '\\' { 522 | ch = s.scanEscape(quote) 523 | } else { 524 | ch = s.next() 525 | } 526 | n++ 527 | } 528 | return 529 | } 530 | 531 | func (s *Scanner) scanComment(ch rune) rune { 532 | // ch == '%' || ch == '*' 533 | if ch == '%' { 534 | // line comment 535 | ch = s.next() // read character after "%" 536 | for ch != '\n' && ch >= 0 { 537 | ch = s.next() 538 | } 539 | return ch 540 | } 541 | 542 | // general comment. See Note1 543 | depth := 1 544 | ch = s.next() // read character after "/*" 545 | for depth > 0 { 546 | if ch < 0 { 547 | s.error("comment not terminated") 548 | break 549 | } 550 | ch0 := ch 551 | ch = s.next() 552 | if ch0 == '*' && ch == '/' { 553 | ch = s.next() 554 | depth-- 555 | } else if ch0 == '/' && ch == '*' { 556 | ch = s.next() 557 | depth++ 558 | } 559 | } 560 | return ch 561 | } 562 | 563 | // Note1: Nested comments are prohibited by ISO Prolog §6.4.1. To wit, 564 | // "The comment text of a bracketed comment shall not contain the comment 565 | // close sequence." However, nested comments are ridiculously practical 566 | // during debugging and development, so I've chosen to deviate by being 567 | // more permissive than is strictly allowed. SWI-Prolog does the same thing. 568 | 569 | // Scan reads the next token or Unicode character from source and returns it. 570 | // It returns EOF at the end of the source. It reports scanner errors (read and 571 | // token errors) by calling s.Error, if not nil; otherwise it prints an error 572 | // message to os.Stderr. 573 | func (s *Scanner) Scan() rune { 574 | ch := s.Peek() 575 | 576 | // reset token text position 577 | s.tokPos = -1 578 | s.Line = 0 579 | 580 | // skip white space 581 | for unicode.IsSpace(ch) { 582 | ch = s.next() 583 | } 584 | 585 | // start collecting token text 586 | s.tokBuf.Reset() 587 | s.tokPos = s.srcPos - s.lastCharLen 588 | 589 | // set token position 590 | // (this is a slightly optimized version of the code in Pos()) 591 | s.Offset = s.srcBufOffset + s.tokPos 592 | if s.column > 0 { 593 | // common case: last character was not a '\n' 594 | s.Line = s.line 595 | s.Column = s.column 596 | } else { 597 | // last character was a '\n' 598 | // (we cannot be at the beginning of the source 599 | // since we have called next() at least once) 600 | s.Line = s.line - 1 601 | s.Column = s.lastLineLen 602 | } 603 | 604 | // determine token value 605 | tok := ch 606 | switch { 607 | case ch == '/': // '/' can start a comment or an atom 608 | ch = s.next() 609 | if ch == '*' { 610 | ch = s.scanComment(ch) 611 | tok = Comment 612 | } else { 613 | tok = Atom 614 | ch = s.scanGraphic(ch) 615 | if ch == '(' { 616 | tok = Functor 617 | } 618 | } 619 | case IsGraphic(ch): 620 | ch = s.next() 621 | tok = Atom 622 | ch = s.scanGraphic(ch) 623 | if ch == '(' { 624 | tok = Functor 625 | } 626 | case isSolo(ch): 627 | tok = Atom 628 | ch = s.next() 629 | case unicode.IsLower(ch): // name by "letter digit token" rule §6.4.2 w/ Unicode 630 | tok = Atom 631 | ch = s.next() 632 | ch = s.scanAlphanumeric(ch) 633 | if ch == '(' { 634 | tok = Functor 635 | } 636 | case isVariableStart(ch): 637 | tok = Variable 638 | ch = s.next() 639 | ch = s.scanAlphanumeric(ch) // variables look like atoms after the start 640 | case isDecimal(ch): 641 | var extraTok rune 642 | tok, ch, extraTok = s.scanNumber(ch) 643 | if extraTok != 0 { 644 | s.extraTok = extraTok 645 | } 646 | default: 647 | switch ch { 648 | case '"': 649 | s.scanString('"') 650 | tok = String 651 | ch = s.next() 652 | case '\'': 653 | s.scanString('\'') 654 | tok = Atom 655 | ch = s.next() 656 | if ch == '(' { 657 | tok = Functor 658 | } 659 | case '%': 660 | ch = s.scanComment(ch) 661 | tok = Comment 662 | default: 663 | ch = s.next() 664 | } 665 | } 666 | 667 | // end of token text 668 | s.tokEnd = s.srcPos - s.lastCharLen 669 | 670 | s.ch = ch 671 | 672 | // last minute specializations 673 | switch tok { 674 | case Atom: 675 | switch s.TokenText() { 676 | case ".": 677 | return FullStop 678 | } 679 | case Variable: 680 | switch s.TokenText() { 681 | case "_": 682 | return Void 683 | } 684 | } 685 | return tok 686 | } 687 | 688 | // Pos returns the position of the character immediately after 689 | // the character or token returned by the last call to Next or Scan. 690 | func (s *Scanner) Pos() (pos Position) { 691 | pos.Filename = s.Filename 692 | pos.Offset = s.srcBufOffset + s.srcPos - s.lastCharLen 693 | switch { 694 | case s.column > 0: 695 | // common case: last character was not a '\n' 696 | pos.Line = s.line 697 | pos.Column = s.column 698 | case s.lastLineLen > 0: 699 | // last character was a '\n' 700 | pos.Line = s.line - 1 701 | pos.Column = s.lastLineLen 702 | default: 703 | // at the beginning of the source 704 | pos.Line = 1 705 | pos.Column = 1 706 | } 707 | return 708 | } 709 | 710 | // TokenText returns the string corresponding to the most recently scanned token. 711 | // Valid after calling Scan(). 712 | func (s *Scanner) TokenText() string { 713 | if s.tokPos < 0 { 714 | // no token text 715 | return "" 716 | } 717 | 718 | if s.tokEnd < 0 { 719 | // if EOF was reached, s.tokEnd is set to -1 (s.srcPos == 0) 720 | s.tokEnd = s.tokPos 721 | } 722 | 723 | if s.tokBuf.Len() == 0 { 724 | // common case: the entire token text is still in srcBuf 725 | return string(s.srcBuf[s.tokPos:s.tokEnd]) 726 | } 727 | 728 | // part of the token text was saved in tokBuf: save the rest in 729 | // tokBuf as well and return its content 730 | s.tokBuf.Write(s.srcBuf[s.tokPos:s.tokEnd]) 731 | s.tokPos = s.tokEnd // ensure idempotency of TokenText() call 732 | return s.tokBuf.String() 733 | } 734 | -------------------------------------------------------------------------------- /machine.go: -------------------------------------------------------------------------------- 1 | // Golog aspires to be an ISO Prolog interpreter. It currently 2 | // supports a small subset of the standard. Any deviations from 3 | // the standard are bugs. Typical usage looks something like this: 4 | // 5 | // m := NewMachine().Consult(` 6 | // father(john). 7 | // father(jacob). 8 | // 9 | // mother(sue). 10 | // 11 | // parent(X) :- 12 | // father(X). 13 | // parent(X) :- 14 | // mother(X). 15 | // `) 16 | // if m.CanProve(`father(john).`) { 17 | // fmt.Printf("john is a father\n") 18 | // } 19 | // 20 | // solutions := m.ProveAll(`parent(X).`) 21 | // for _, solution := range solutions { 22 | // fmt.Printf("%s is a parent\n", solution.ByName_("X")) 23 | // } 24 | // 25 | // This sample highlights a few key aspects of using Golog. To start, 26 | // Golog data structures are immutable. NewMachine() creates an empty 27 | // Golog machine containing just the standard library. 28 | // Consult() creates another new machine with some extra 29 | // code loaded. The original, empty machine is untouched. 30 | // It's common to build a large Golog machine during init() 31 | // and then add extra rules to it at runtime. 32 | // Because Golog machines are immutable, 33 | // multiple goroutines can access, run and "modify" machines in parallel. 34 | // This design also opens possibilities for and-parallel and or-parallel 35 | // execution. 36 | // 37 | // Most methods, like Consult(), can accept Prolog code in several forms. 38 | // The example above shows Prolog as a string. We could have used any 39 | // io.Reader instead. 40 | // 41 | // Error handling is one oddity. Golog methods follow Go convention by 42 | // returning an error value to indicate that something went wrong. However, 43 | // in many cases the caller knows that an error is highly improbable and 44 | // doesn't want extra code to deal with the common case. 45 | // For most 46 | // methods, Golog offers one with a trailing underscore, like ByName_(), 47 | // which panics on error instead of returning an error value. 48 | // 49 | // See also: 50 | // * Golog's architecture: https://github.com/mndrix/golog/blob/master/doc/architecture.md 51 | // * Built in and foreign predicates: See func Builtin... 52 | // * Standard library: See golog/prelude package 53 | package golog 54 | 55 | import . "github.com/mndrix/golog/term" 56 | import . "github.com/mndrix/golog/util" 57 | 58 | import ( 59 | "bufio" 60 | "bytes" 61 | "fmt" 62 | "os" 63 | "strconv" 64 | "strings" 65 | 66 | "github.com/mndrix/golog/prelude" 67 | "github.com/mndrix/golog/read" 68 | "github.com/mndrix/ps" 69 | ) 70 | 71 | // NoBarriers error is returned when trying to access a cut barrier that 72 | // doesn't exist. See MostRecentCutBarrier 73 | var NoBarriers = fmt.Errorf("There are no cut barriers") 74 | 75 | // MachineDone error is returned when a Golog machine has been stepped 76 | // as far forward as it can go. It's unusual to need this variable. 77 | var MachineDone = fmt.Errorf("Machine can't step any further") 78 | 79 | // EmptyDisjunctions error is returned when trying to pop a disjunction 80 | // off an empty disjunction stack. This is mostly useful for those hacking 81 | // on the interpreter. 82 | var EmptyDisjunctions = fmt.Errorf("Disjunctions list is empty") 83 | 84 | // EmptyConjunctions error is returned when trying to pop a conjunction 85 | // off an empty conjunction stack. This is mostly useful for those hacking 86 | // on the interpreter. 87 | var EmptyConjunctions = fmt.Errorf("Conjunctions list is empty") 88 | 89 | // Golog users interact almost exclusively with a Machine value. 90 | // Specifically, by calling one of the three methods Consult, CanProve and 91 | // ProveAll. All others methods are for those hacking on the interpreter or 92 | // doing low-level operations in foreign predicates. 93 | type Machine interface { 94 | // A Machine is an acceptable return value from a foreign predicate 95 | // definition. In other words, a foreign predicate can perform low-level 96 | // manipulations on a Golog machine and return the result as the new 97 | // machine on which future execution occurs. It's unlikely that you'll 98 | // need to do this. 99 | ForeignReturn 100 | 101 | // Temporary. These will eventually become functions rather than methods. 102 | // All three accept Prolog terms as strings or as io.Reader objects from 103 | // which Prolog terms can be read. 104 | CanProve(interface{}) bool 105 | Consult(interface{}) Machine 106 | ProveAll(interface{}) []Bindings 107 | 108 | String() string 109 | 110 | // Bindings returns the machine's most current variable bindings. 111 | // 112 | // This method is typically only used by ChoicePoint implementations 113 | Bindings() Bindings 114 | 115 | // SetBindings returns a new machine like this one but with the given 116 | // bindings 117 | SetBindings(Bindings) Machine 118 | 119 | // PushConj returns a machine like this one but with an extra term 120 | // on front of the conjunction stack 121 | PushConj(Callable) Machine 122 | 123 | // PopConj returns a machine with one less item on the conjunction stack 124 | // along with the term removed. Returns err = EmptyConjunctions if there 125 | // are no more conjunctions on that stack 126 | PopConj() (Callable, Machine, error) 127 | 128 | // ClearConjs replaces the conjunction stack with an empty one 129 | ClearConjs() Machine 130 | 131 | // ClearDisjs replaces the disjunction stack with an empty one 132 | ClearDisjs() Machine 133 | 134 | // DemandCutBarrier makes sure the disjunction stack has a cut barrier 135 | // on top. If not, one is pushed. 136 | // This marker can be used to locate which disjunctions came immediately 137 | // before the marker existed. 138 | DemandCutBarrier() Machine 139 | 140 | // MostRecentCutBarrier returns an opaque value which uniquely 141 | // identifies the most recent cut barrier on the disjunction stack. 142 | // Used with CutTo to remove several disjunctions at once. 143 | // Returns NoBarriers if there are no cut barriers on the disjunctions 144 | // stack. 145 | MostRecentCutBarrier() (int64, error) 146 | 147 | // CutTo removes all disjunctions stacked on top of a specific cut barrier. 148 | // It does not remove the cut barrier itself. 149 | // A barrier ID is obtained from MostRecentCutBarrier. 150 | CutTo(int64) Machine 151 | 152 | // PushDisj returns a machine like this one but with an extra ChoicePoint 153 | // on the disjunctions stack. 154 | PushDisj(ChoicePoint) Machine 155 | 156 | // PopDisj returns a machine with one fewer choice points on the 157 | // disjunction stack and the choice point that was removed. Returns 158 | // err = EmptyDisjunctions if there are no more disjunctions on 159 | // that stack 160 | PopDisj() (ChoicePoint, Machine, error) 161 | 162 | // RegisterForeign registers Go functions to implement Golog predicates. 163 | // When Golog tries to prove a predicate with one of these predicate 164 | // indicators, it executes the given function instead. 165 | // Calling RegisterForeign with a predicate indicator that's already 166 | // been registered replaces the predicate implementation. 167 | RegisterForeign(map[string]ForeignPredicate) Machine 168 | 169 | // Step advances the machine one "step" (implementation dependent). 170 | // It produces a new machine which can take the next step. It might 171 | // produce a proof by giving some variable bindings. When the machine 172 | // has done as much work as it can do, it returns err=MachineDone 173 | Step() (Machine, Bindings, error) 174 | } 175 | 176 | // Golog allows Prolog predicates to be defined in Go. The foreign predicate 177 | // mechanism is implemented via functions whose type is ForeignPredicate. 178 | type ForeignPredicate func(Machine, []Term) ForeignReturn 179 | 180 | const smallThreshold = 4 181 | 182 | type machine struct { 183 | db Database 184 | env Bindings 185 | disjs ps.List // of ChoicePoint 186 | conjs ps.List // of Term 187 | 188 | smallForeign [smallThreshold]ps.Map // arity => functor => ForeignPredicate 189 | largeForeign ps.Map // predicate indicator => ForeignPredicate 190 | 191 | help map[string]string 192 | } 193 | 194 | func (*machine) IsaForeignReturn() {} 195 | 196 | // NewMachine creates a new Golog machine. This machine has the standard 197 | // library already loaded and is typically the way one obtains 198 | // a machine. 199 | func NewMachine() Machine { 200 | return NewBlankMachine(). 201 | Consult(prelude.Prelude). 202 | RegisterForeign(map[string]ForeignPredicate{ 203 | "!/0": BuiltinCut, 204 | "$cut_to/1": BuiltinCutTo, 205 | ",/2": BuiltinComma, 206 | "->/2": BuiltinIfThen, 207 | ";/2": BuiltinSemicolon, 208 | "=/2": BuiltinUnify, 209 | "=:=/2": BuiltinNumericEquals, 210 | "==/2": BuiltinTermEquals, 211 | "\\==/2": BuiltinTermNotEquals, 212 | "@/2": BuiltinTermGreater, 215 | "@>=/2": BuiltinTermGreaterEquals, 216 | `\+/1`: BuiltinNot, 217 | "atom_codes/2": BuiltinAtomCodes2, 218 | "atom_number/2": BuiltinAtomNumber2, 219 | "call/1": BuiltinCall, 220 | "call/2": BuiltinCall, 221 | "call/3": BuiltinCall, 222 | "call/4": BuiltinCall, 223 | "call/5": BuiltinCall, 224 | "call/6": BuiltinCall, 225 | "downcase_atom/2": BuiltinDowncaseAtom2, 226 | "fail/0": BuiltinFail, 227 | "findall/3": BuiltinFindall3, 228 | "ground/1": BuiltinGround, 229 | "is/2": BuiltinIs, 230 | "listing/0": BuiltinListing0, 231 | "msort/2": BuiltinMsort2, 232 | "printf/1": BuiltinPrintf, 233 | "printf/2": BuiltinPrintf, 234 | "printf/3": BuiltinPrintf, 235 | "succ/2": BuiltinSucc2, 236 | "var/1": BuiltinVar1, 237 | }) 238 | } 239 | 240 | // NewBlankMachine creates a new Golog machine without loading the 241 | // standard library (prelude) 242 | func NewBlankMachine() Machine { 243 | var m machine 244 | m.db = NewDatabase() 245 | m.env = NewBindings() 246 | m.disjs = ps.NewList() 247 | m.conjs = ps.NewList() 248 | 249 | for i := 0; i < smallThreshold; i++ { 250 | m.smallForeign[i] = ps.NewMap() 251 | } 252 | m.largeForeign = ps.NewMap() 253 | return (&m).DemandCutBarrier() 254 | } 255 | 256 | func (m *machine) clone() *machine { 257 | m1 := *m 258 | return &m1 259 | } 260 | 261 | func (m *machine) Consult(text interface{}) Machine { 262 | terms := read.TermAll_(text) 263 | m1 := m.clone() 264 | for _, t := range terms { 265 | if IsDirective(t) { 266 | // ignore all directives, for now 267 | continue 268 | } 269 | m1.db = m1.db.Assertz(t) 270 | } 271 | return m1 272 | } 273 | 274 | func (m *machine) RegisterForeign(fs map[string]ForeignPredicate) Machine { 275 | m1 := m.clone() 276 | for indicator, f := range fs { 277 | parts := strings.SplitN(indicator, "/", 2) 278 | functor := parts[0] 279 | arity, err := strconv.Atoi(parts[1]) 280 | MaybePanic(err) 281 | 282 | if arity < smallThreshold { 283 | m1.smallForeign[arity] = m1.smallForeign[arity].Set(functor, f) 284 | } else { 285 | m1.largeForeign = m1.largeForeign.Set(indicator, f) 286 | } 287 | } 288 | return m1 289 | } 290 | 291 | func (m *machine) String() string { 292 | var buf bytes.Buffer 293 | fmt.Fprintf(&buf, "disjs:\n") 294 | m.disjs.ForEach(func(v interface{}) { 295 | fmt.Fprintf(&buf, " %s\n", v) 296 | }) 297 | fmt.Fprintf(&buf, "conjs:\n") 298 | m.conjs.ForEach(func(v interface{}) { 299 | fmt.Fprintf(&buf, " %s\n", v) 300 | }) 301 | fmt.Fprintf(&buf, "bindings: %s", m.env) 302 | return buf.String() 303 | } 304 | 305 | // CanProve returns true if goal can be proven from facts and clauses 306 | // in the database. Once a solution is found, it abandons other 307 | // solutions (like once/1). 308 | func (self *machine) CanProve(goal interface{}) bool { 309 | var answer Bindings 310 | var err error 311 | 312 | goalTerm := self.toGoal(goal) 313 | m := self.PushConj(goalTerm) 314 | for { 315 | m, answer, err = m.Step() 316 | if err == MachineDone { 317 | return answer != nil 318 | } 319 | MaybePanic(err) 320 | if answer != nil { 321 | return true 322 | } 323 | } 324 | } 325 | 326 | func (self *machine) ProveAll(goal interface{}) []Bindings { 327 | var answer Bindings 328 | var err error 329 | answers := make([]Bindings, 0) 330 | 331 | goalTerm := self.toGoal(goal) 332 | vars := Variables(goalTerm) // preserve incoming human-readable names 333 | m := self.PushConj(goalTerm) 334 | for { 335 | m, answer, err = m.Step() 336 | if err == MachineDone { 337 | break 338 | } 339 | MaybePanic(err) 340 | if answer != nil { 341 | answers = append(answers, answer.WithNames(vars)) 342 | } 343 | } 344 | 345 | return answers 346 | } 347 | 348 | // advance the Golog machine one step closer to proving the goal at hand. 349 | // at the end of each invocation, the top item on the conjunctions stack 350 | // is the goal we should next try to prove. 351 | func (self *machine) Step() (Machine, Bindings, error) { 352 | var m Machine = self 353 | var goal Callable 354 | var err error 355 | var cp ChoicePoint 356 | 357 | //Debugf("stepping...\n%s\n", self) 358 | if false { // for debugging. commenting out needs import changes 359 | _, _ = bufio.NewReader(os.Stdin).ReadString('\n') 360 | } 361 | 362 | // find a goal other than true/0 to prove 363 | arity := 0 364 | functor := "true" 365 | for arity == 0 && functor == "true" { 366 | var mTmp Machine 367 | goal, mTmp, err = m.PopConj() 368 | if err == EmptyConjunctions { // found an answer 369 | answer := m.Bindings() 370 | Debugf(" emitting answer %s\n", answer) 371 | m = m.PushConj(NewAtom("fail")) // backtrack on next Step() 372 | return m, answer, nil 373 | } 374 | MaybePanic(err) 375 | m = mTmp 376 | arity = goal.Arity() 377 | functor = goal.Name() 378 | } 379 | 380 | // are we proving a foreign predicate? 381 | f, ok := m.(*machine).lookupForeign(goal) 382 | if ok { // foreign predicate 383 | args := m.(*machine).resolveAllArguments(goal) 384 | Debugf(" running foreign predicate %s with %s\n", goal, args) 385 | ret := f(m, args) 386 | switch x := ret.(type) { 387 | case *foreignTrue: 388 | return m, nil, nil 389 | case *foreignFail: 390 | // do nothing. continue to iterate disjunctions below 391 | case *machine: 392 | return x, nil, nil 393 | case *foreignUnify: 394 | terms := []Term(*x) // guaranteed even number of elements 395 | env := m.Bindings() 396 | for i := 0; i < len(terms); i += 2 { 397 | env, err = terms[i].Unify(env, terms[i+1]) 398 | if err == CantUnify { 399 | env = nil 400 | break 401 | } 402 | MaybePanic(err) 403 | } 404 | if env != nil { 405 | return m.SetBindings(env), nil, nil 406 | } 407 | } 408 | } else { // user-defined predicate, push all its disjunctions 409 | goal = goal.ReplaceVariables(m.Bindings()).(Callable) 410 | Debugf(" running user-defined predicate %s\n", goal) 411 | clauses, err := m.(*machine).db.Candidates(goal) 412 | MaybePanic(err) 413 | m = m.DemandCutBarrier() 414 | for i := len(clauses) - 1; i >= 0; i-- { 415 | clause := clauses[i] 416 | cp := NewHeadBodyChoicePoint(m, goal, clause) 417 | m = m.PushDisj(cp) 418 | } 419 | } 420 | 421 | // iterate disjunctions looking for one that succeeds 422 | for { 423 | cp, m, err = m.PopDisj() 424 | if err == EmptyDisjunctions { // nothing left to do 425 | Debugf("Stopping because of EmptyDisjunctions\n") 426 | return nil, nil, MachineDone 427 | } 428 | MaybePanic(err) 429 | 430 | // follow the next choice point 431 | Debugf(" trying to follow CP %s\n", cp) 432 | mTmp, err := cp.Follow() 433 | switch err { 434 | case nil: 435 | Debugf(" ... followed\n") 436 | return mTmp, nil, nil 437 | case CantUnify: 438 | Debugf(" ... couldn't unify\n") 439 | continue 440 | case CutBarrierFails: 441 | Debugf(" ... skipping over cut barrier\n") 442 | continue 443 | } 444 | MaybePanic(err) 445 | } 446 | } 447 | 448 | func (m *machine) lookupForeign(goal Callable) (ForeignPredicate, bool) { 449 | var f interface{} 450 | var ok bool 451 | 452 | arity := goal.Arity() 453 | if arity < smallThreshold { 454 | f, ok = m.smallForeign[arity].Lookup(goal.Name()) 455 | } else { 456 | f, ok = m.largeForeign.Lookup(goal.Indicator()) 457 | } 458 | 459 | if ok { 460 | return f.(ForeignPredicate), ok 461 | } 462 | return nil, ok 463 | } 464 | 465 | func (m *machine) toGoal(thing interface{}) Callable { 466 | switch x := thing.(type) { 467 | case Term: 468 | return x.(Callable) 469 | case string: 470 | return m.readTerm(x).(Callable) 471 | } 472 | msg := fmt.Sprintf("Can't convert %#v to a term", thing) 473 | panic(msg) 474 | } 475 | 476 | func (m *machine) resolveAllArguments(goal Callable) []Term { 477 | Debugf("resolving all arguments: %s\n", goal) 478 | env := m.Bindings() 479 | args := goal.Arguments() 480 | resolved := make([]Term, len(args)) 481 | for i, arg := range args { 482 | if IsVariable(arg) { 483 | a, err := env.Resolve(arg.(*Variable)) 484 | if err == nil { 485 | resolved[i] = a 486 | continue 487 | } 488 | } else if IsCompound(arg) { 489 | resolved[i] = arg.ReplaceVariables(env) 490 | continue 491 | } 492 | resolved[i] = arg 493 | } 494 | 495 | return resolved 496 | } 497 | 498 | func (m *machine) readTerm(src interface{}) Term { 499 | return read.Term_(src) 500 | } 501 | 502 | func (m *machine) Bindings() Bindings { 503 | return m.env 504 | } 505 | 506 | func (m *machine) SetBindings(env Bindings) Machine { 507 | m1 := m.clone() 508 | m1.env = env 509 | return m1 510 | } 511 | 512 | func (m *machine) PushConj(t Callable) Machine { 513 | // change all !/0 goals into '$cut_to'(RecentBarrierId) goals 514 | barrierID, err := m.MostRecentCutBarrier() 515 | if err == nil { 516 | t = resolveCuts(barrierID, t) 517 | } 518 | 519 | m1 := m.clone() 520 | m1.conjs = m.conjs.Cons(t) 521 | return m1 522 | } 523 | 524 | func (m *machine) PopConj() (Callable, Machine, error) { 525 | if m.conjs.IsNil() { 526 | return nil, nil, EmptyConjunctions 527 | } 528 | 529 | t := m.conjs.Head().(Callable) 530 | m1 := m.clone() 531 | m1.conjs = m.conjs.Tail() 532 | return t, m1, nil 533 | } 534 | 535 | func (m *machine) ClearConjs() Machine { 536 | m1 := m.clone() 537 | m1.conjs = ps.NewList() 538 | return m1 539 | } 540 | 541 | func (m *machine) ClearDisjs() Machine { 542 | m1 := m.clone() 543 | m1.disjs = ps.NewList() 544 | return m1 545 | } 546 | 547 | func (m *machine) PushDisj(cp ChoicePoint) Machine { 548 | m1 := m.clone() 549 | m1.disjs = m.disjs.Cons(cp) 550 | return m1 551 | } 552 | 553 | func (m *machine) PopDisj() (ChoicePoint, Machine, error) { 554 | if m.disjs.IsNil() { 555 | return nil, nil, EmptyDisjunctions 556 | } 557 | 558 | cp := m.disjs.Head().(ChoicePoint) 559 | m1 := m.clone() 560 | m1.disjs = m.disjs.Tail() 561 | return cp, m1, nil 562 | } 563 | 564 | func (m *machine) DemandCutBarrier() Machine { 565 | // is the top choice point already a cut barrier? 566 | if !m.disjs.IsNil() { 567 | topCP := m.disjs.Head().(ChoicePoint) 568 | _, ok := BarrierId(topCP) 569 | if ok { 570 | return m 571 | } 572 | } 573 | 574 | // nope, push a new cut barrier 575 | barrier := NewCutBarrier(m) 576 | return m.PushDisj(barrier) 577 | } 578 | 579 | func (m *machine) MostRecentCutBarrier() (int64, error) { 580 | ds := m.disjs 581 | for { 582 | if ds.IsNil() { 583 | return -1, NoBarriers 584 | } 585 | 586 | id, ok := BarrierId(ds.Head().(ChoicePoint)) 587 | if ok { 588 | return id, nil 589 | } 590 | 591 | ds = ds.Tail() 592 | } 593 | } 594 | 595 | func (m *machine) CutTo(want int64) Machine { 596 | ds := m.disjs 597 | for { 598 | if ds.IsNil() { 599 | msg := fmt.Sprintf("No cut barrier with ID %d", want) 600 | panic(msg) 601 | } 602 | 603 | found, ok := BarrierId(ds.Head().(ChoicePoint)) 604 | if ok && found == want { 605 | m1 := m.clone() 606 | m1.disjs = ds 607 | return m1 608 | } 609 | 610 | ds = ds.Tail() 611 | } 612 | } 613 | 614 | func resolveCuts(id int64, t Callable) Callable { 615 | switch t.Arity() { 616 | case 0: 617 | if t.Name() == "!" { 618 | return NewCallable("$cut_to", NewInt64(id)) 619 | } 620 | case 2: 621 | switch t.Name() { 622 | case ",", ";": 623 | args := t.Arguments() 624 | t0 := resolveCuts(id, args[0].(Callable)) 625 | t1 := resolveCuts(id, args[1].(Callable)) 626 | if t0 == args[0] && t1 == args[1] { 627 | return t 628 | } 629 | return NewCallable(t.Name(), t0, t1) 630 | case "->": 631 | args := t.Arguments() 632 | t0 := args[0] // don't resolve cuts in Condition 633 | t1 := resolveCuts(id, args[1].(Callable)) 634 | if t1 == args[1] { // no changes. don't create a new term 635 | return t 636 | } 637 | return NewCallable(t.Name(), t0, t1) 638 | } 639 | } 640 | 641 | // leave any other cuts unresolved 642 | return t 643 | } 644 | --------------------------------------------------------------------------------