diff options
| author | Christine Dodrill <me@christine.website> | 2019-01-09 22:42:05 -0800 |
|---|---|---|
| committer | Christine Dodrill <me@christine.website> | 2019-01-09 22:42:05 -0800 |
| commit | ab3fc4f8a33f3f036ce856b8fa67a2e295bf81d4 (patch) | |
| tree | de369a2814e9e72f8f4e629ed7ec7d710cd1a128 | |
| parent | b230a9fbe98edf1e2398454561fcd7833d9a6ff6 (diff) | |
| download | x-ab3fc4f8a33f3f036ce856b8fa67a2e295bf81d4.tar.xz x-ab3fc4f8a33f3f036ce856b8fa67a2e295bf81d4.zip | |
toki pona sentences to logical facts
| -rw-r--r-- | la-baujmi/README.md | 9 | ||||
| -rw-r--r-- | la-baujmi/fact.go | 105 | ||||
| -rw-r--r-- | la-baujmi/fact_test.go | 119 | ||||
| -rw-r--r-- | la-baujmi/main.go | 6 |
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()) + } } |
