aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2019-01-09 22:42:05 -0800
committerChristine Dodrill <me@christine.website>2019-01-09 22:42:05 -0800
commitab3fc4f8a33f3f036ce856b8fa67a2e295bf81d4 (patch)
treede369a2814e9e72f8f4e629ed7ec7d710cd1a128
parentb230a9fbe98edf1e2398454561fcd7833d9a6ff6 (diff)
downloadx-ab3fc4f8a33f3f036ce856b8fa67a2e295bf81d4.tar.xz
x-ab3fc4f8a33f3f036ce856b8fa67a2e295bf81d4.zip
toki pona sentences to logical facts
-rw-r--r--la-baujmi/README.md9
-rw-r--r--la-baujmi/fact.go105
-rw-r--r--la-baujmi/fact_test.go119
-rw-r--r--la-baujmi/main.go6
4 files changed, 236 insertions, 3 deletions
diff --git a/la-baujmi/README.md b/la-baujmi/README.md
index 9db108f..10aa48d 100644
--- a/la-baujmi/README.md
+++ b/la-baujmi/README.md
@@ -48,16 +48,19 @@ toki(jan_Kesi, jan_Pola).
jan Kesi li toki e toki pona.
Cadey is talking about toki pona.
toki(jan_Kesi, toki_pona).
+
+ilo Kesi o, toki e jan Kesi.
+Robo-Cadey: talk about Cadey.
+command(ilo_Kesi, toki(ziho, jan_Kesi)). % ziho -> nothing in lojban (zi'o)
```
And then we can ask prolog questions about this sentence:
```
seme li toki?
-```
-
-```
> toki(X).
toki(jan_Kesi).
jan Kesi li toki.
+toki(jan_Pola).
+jan Pola li toki.
```
diff --git a/la-baujmi/fact.go b/la-baujmi/fact.go
new file mode 100644
index 0000000..4199e26
--- /dev/null
+++ b/la-baujmi/fact.go
@@ -0,0 +1,105 @@
+package main
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/Xe/x/web/tokiponatokens"
+)
+
+// Selbri is a predicate relationship between its arguments. The name comes from
+// the lojban term selbri: http://jbovlaste.lojban.org/dict/selbri.
+type Selbri struct {
+ Predicate string
+ Arguments []string
+}
+
+// Fact converts a selbri into a prolog fact.
+func (s Selbri) Fact() string {
+ var sb strings.Builder
+
+ sb.WriteString(s.Predicate + "(")
+
+ var varCount byte
+
+ for i, arg := range s.Arguments {
+ if i != 0 {
+ sb.WriteString(", ")
+ }
+
+ if arg == "seme" {
+ sb.WriteByte(byte('A') + varCount)
+ varCount++
+ continue
+ }
+ sb.WriteString(arg)
+ }
+
+ sb.WriteString(").")
+ return sb.String()
+}
+
+var (
+ // ErrNoPredicate is raised when the given sentence does not have a verb.
+ // This is a side effect of the way we are potentially misusing prolog
+ // here.
+ ErrNoPredicate = errors.New("la-baujmi: sentence must have a verb to function as the logical predicate")
+)
+
+// SentenceToSelbris creates logical facts derived from toki pona sentences.
+// This is intended to be the first step in loading them into prolog.
+func SentenceToSelbris(s tokiponatokens.Sentence) ([]Selbri, error) {
+ var (
+ subjects []string
+ verbs []string
+ objects []string
+ context []string
+ )
+
+ for _, pt := range s {
+ switch pt.Type {
+ case tokiponatokens.PartSubject:
+ subjects = append(subjects, strings.Join(pt.Tokens, "_"))
+
+ case tokiponatokens.PartVerbMarker:
+ verbs = append(verbs, strings.Join(pt.Tokens, "_"))
+
+ case tokiponatokens.PartObjectMarker:
+ objects = append(objects, strings.Join(pt.Tokens, "_"))
+
+ case tokiponatokens.PartPunctuation:
+ if pt.Sep != nil {
+ switch *pt.Sep {
+ case "la":
+ context = append(context, subjects[len(subjects)-1])
+ subjects = subjects[:len(subjects)-1]
+
+ case tokiponatokens.PunctComma:
+ return nil, errors.New("please avoid commas in this function")
+ }
+ }
+ }
+ }
+
+ if len(verbs) == 0 {
+ return nil, ErrNoPredicate
+ }
+
+ var result []Selbri
+
+ for _, v := range verbs {
+ // sumti: x1 is a/the argument of predicate function x2 filling place x3 (kind/number)
+ sumti := append([]string{}, context...)
+ sumti = append(sumti, subjects...)
+ sumti = append(sumti, objects...)
+
+ r := Selbri{
+ Predicate: v,
+ Arguments: sumti,
+ }
+
+ result = append(result, r)
+ }
+
+ return result, nil
+}
diff --git a/la-baujmi/fact_test.go b/la-baujmi/fact_test.go
new file mode 100644
index 0000000..0f38634
--- /dev/null
+++ b/la-baujmi/fact_test.go
@@ -0,0 +1,119 @@
+package main
+
+import (
+ "encoding/json"
+ "log"
+ "testing"
+
+ "github.com/Xe/x/web/tokiponatokens"
+ "github.com/kr/pretty"
+)
+
+// equal tells whether a and b contain the same elements.
+// A nil argument is equivalent to an empty slice.
+func equal(a, b []string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, v := range a {
+ if v != b[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func selbrisEqual(a, b []Selbri) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i := range a {
+ if !a[i].Eq(b[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// Eq checks two Selbri instances for equality.
+func (lhs Selbri) Eq(rhs Selbri) bool {
+ switch {
+ case lhs.Predicate != rhs.Predicate:
+ return false
+ case len(lhs.Arguments) != len(rhs.Arguments):
+ return false
+ case len(lhs.Arguments) == len(rhs.Arguments):
+ return equal(lhs.Arguments, rhs.Arguments)
+ }
+
+ return true
+}
+
+func TestSentenceToSelbris(t *testing.T) {
+ cases := []struct {
+ name string
+ json []byte
+ want []Selbri
+ wantFacts []string
+ }{
+ {
+ name: "basic",
+ json: []byte(`[{"part":"subject","tokens":["ona"]},{"part":"verbMarker","sep":"li","tokens":["sona"]},{"part":"objectMarker","sep":"e","tokens":["mute"]},{"part":"punctuation","tokens":["period"]}]`),
+ want: []Selbri{
+ {
+ Predicate: "sona",
+ Arguments: []string{"ona", "mute"},
+ },
+ },
+ wantFacts: []string{"sona(ona, mute)."},
+ },
+ {
+ name: "zen",
+ json: []byte(`[{"part":"subject","tokens":["tenpo","ni"]},{"part":"punctuation","tokens":["la"]},{"part":"subject","tokens":["seme"]},{"part":"verbMarker","sep":"li","tokens":["ala"]}]`),
+ want: []Selbri{
+ {
+ Predicate: "ala",
+ Arguments: []string{"tenpo_ni", "seme"},
+ },
+ },
+ wantFacts: []string{"ala(tenpo_ni, A)."},
+ },
+ }
+
+ for _, cs := range cases {
+ t.Run(cs.name, func(t *testing.T) {
+ var s tokiponatokens.Sentence
+ err := json.Unmarshal(cs.json, &s)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sb, err := SentenceToSelbris(s)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !selbrisEqual(cs.want, sb) {
+ log.Println("want:")
+ pretty.Println(cs.want)
+ log.Println("got:")
+ pretty.Println(sb)
+
+ t.Fatal("see logs")
+ }
+
+ var facts []string
+ for _, s := range sb {
+ facts = append(facts, s.Fact())
+ }
+
+ t.Run("facts", func(t *testing.T) {
+ if !equal(cs.wantFacts, facts) {
+ t.Logf("wanted: %v", cs.wantFacts)
+ t.Logf("got: %v", facts)
+ t.Fatal("see -v")
+ }
+ })
+ })
+ }
+}
diff --git a/la-baujmi/main.go b/la-baujmi/main.go
index 1b7183f..4751353 100644
--- a/la-baujmi/main.go
+++ b/la-baujmi/main.go
@@ -12,6 +12,7 @@ toki(jan_Kesi).
toki(jan_Pola).
toki(jan_Kesi, jan_Pola).
toki(jan_Kesi, toki_pona).
+command(ilo_Kesi, toki(ziho, jan_Kesi)).
`)
if m.CanProve(`toki(jan_Kesi).`) {
log.Printf("toki(jan_Kesi). -> jan Kesi li toki.")
@@ -21,4 +22,9 @@ toki(jan_Kesi, toki_pona).
for _, solution := range solutions {
log.Printf("jan_Kesi li toki e %s", solution.ByName_("X").String())
}
+
+ solutions = m.ProveAll(`command(X, toki(ziho, jan_Kesi)).`)
+ for _, solution := range solutions {
+ log.Printf("%s o, toki e jan_Kesi", solution.ByName_("X").String())
+ }
}