aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2019-04-01 10:05:03 -0700
committerChristine Dodrill <me@christine.website>2019-04-01 10:05:28 -0700
commitf06f021f402270951f849dde7bee3f3340b8a1d5 (patch)
treebaee337aab524f162b349d254d21c2d8f2716d44 /cmd
parentba91a17859267201b1d1f0e71da465b1464d940f (diff)
downloadx-f06f021f402270951f849dde7bee3f3340b8a1d5.tar.xz
x-f06f021f402270951f849dde7bee3f3340b8a1d5.zip
reorg
Diffstat (limited to 'cmd')
-rw-r--r--cmd/appsluggr/main.go153
-rw-r--r--cmd/dnsd/dnsd.conf7
-rw-r--r--cmd/dnsd/main.go128
-rw-r--r--cmd/ff-primitive/main.go55
-rw-r--r--cmd/johaus/build.go39
-rw-r--r--cmd/johaus/main.go72
-rw-r--r--cmd/la-baujmi/.gitignore2
-rw-r--r--cmd/la-baujmi/README.md66
-rw-r--r--cmd/la-baujmi/const.go5
-rw-r--r--cmd/la-baujmi/corpus.txt23
-rw-r--r--cmd/la-baujmi/fact.go210
-rw-r--r--cmd/la-baujmi/fact_test.go278
-rw-r--r--cmd/la-baujmi/main.go70
-rw-r--r--cmd/license/.gitignore3
-rw-r--r--cmd/license/licenses/licenses.go978
-rw-r--r--cmd/license/main.go125
-rw-r--r--cmd/mainsanow/main.go21
-rw-r--r--cmd/minipaas/main.go20
-rw-r--r--cmd/mkapp/main.go37
-rw-r--r--cmd/priorworkgen/.gitignore1
-rw-r--r--cmd/priorworkgen/main.go105
-rw-r--r--cmd/quickserv/.gitignore1
-rw-r--r--cmd/quickserv/main.go21
-rw-r--r--cmd/relayd/main.go68
-rw-r--r--cmd/thumber/in/XjScp8a.pngbin0 -> 292689 bytes
-rw-r--r--cmd/thumber/in/lB4ICS3.pngbin0 -> 369886 bytes
-rw-r--r--cmd/thumber/in/oaw79y9.jpgbin0 -> 67119 bytes
-rw-r--r--cmd/thumber/main.go57
-rw-r--r--cmd/within.website/.gitignore2
-rw-r--r--cmd/within.website/build.go39
-rw-r--r--cmd/within.website/main.go70
31 files changed, 2656 insertions, 0 deletions
diff --git a/cmd/appsluggr/main.go b/cmd/appsluggr/main.go
new file mode 100644
index 0000000..b4701a1
--- /dev/null
+++ b/cmd/appsluggr/main.go
@@ -0,0 +1,153 @@
+package main
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/otiai10/copy"
+ "github.com/Xe/x/internal"
+)
+
+var (
+ web = flag.String("web", "", "path to binary for web process")
+ webScale = flag.Int("web-scale", 1, "default scale for web process if defined")
+ worker = flag.String("worker", "", "path to binary for worker process")
+ workerScale = flag.Int("worker-scale", 1, "default scale for worker process if defined")
+ fname = flag.String("fname", "slug.tar.gz", "slug name")
+)
+
+func main() {
+ internal.HandleStartup()
+
+ fout, err := os.Create(*fname)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fout.Close()
+
+ gzw := gzip.NewWriter(fout)
+ defer gzw.Close()
+
+ tw := tar.NewWriter(gzw)
+ defer tw.Close()
+
+ dir, err := ioutil.TempDir("", "appsluggr")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ defer os.RemoveAll(dir) // clean up
+
+ os.MkdirAll(filepath.Join(dir, "bin"), 0777)
+ var procfile, scalefile string
+
+ copy.Copy("translations", filepath.Join(dir, "translations"))
+ if *web != "" {
+ procfile += "web: /app/bin/web\n"
+ scalefile += fmt.Sprintf("web=%d", *webScale)
+ Copy(*web, filepath.Join(dir, "bin", "web"))
+
+ }
+ if *worker != "" {
+ procfile += "worker: /app/bin/worker\n"
+ scalefile += fmt.Sprintf("worker=%d", *workerScale)
+ Copy(*worker, filepath.Join(dir, "bin", "worker"))
+ }
+
+ os.MkdirAll(filepath.Join(dir, ".config"), 0777)
+
+ err = ioutil.WriteFile(filepath.Join(dir, ".buildpacks"), []byte("https://github.com/ryandotsmith/null-buildpack"), 0666)
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = ioutil.WriteFile(filepath.Join(dir, "DOKKU_SCALE"), []byte(scalefile), 0666)
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = ioutil.WriteFile(filepath.Join(dir, "Procfile"), []byte(procfile), 0666)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ filepath.Walk(dir, func(file string, fi os.FileInfo, err error) error {
+ // return on any error
+ if err != nil {
+ log.Printf("got error on %s: %v", file, err)
+ return err
+ }
+
+ if fi.IsDir() {
+ return nil // not a file. ignore.
+ }
+
+ // create a new dir/file header
+ header, err := tar.FileInfoHeader(fi, fi.Name())
+ if err != nil {
+ return err
+ }
+
+ // update the name to correctly reflect the desired destination when untaring
+ header.Name = strings.TrimPrefix(strings.Replace(file, dir, "", -1), string(filepath.Separator))
+
+ // write the header
+ if err := tw.WriteHeader(header); err != nil {
+ return err
+ }
+
+ // return on non-regular files (thanks to [kumo](https://medium.com/@komuw/just-like-you-did-fbdd7df829d3) for this suggested update)
+ if !fi.Mode().IsRegular() {
+ return nil
+ }
+
+ // open files for taring
+ f, err := os.Open(file)
+ if err != nil {
+ return err
+ }
+
+ // copy file data into tar writer
+ if _, err := io.Copy(tw, f); err != nil {
+ return err
+ }
+
+ // manually close here after each file operation; defering would cause each file close
+ // to wait until all operations have completed.
+ f.Close()
+
+ return nil
+ })
+}
+
+// Copy the src file to dst. Any existing file will be overwritten and will not
+// copy file attributes.
+func Copy(src, dst string) error {
+ in, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+ st, err := in.Stat()
+ if err != nil {
+ return err
+ }
+
+ out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, st.Mode())
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ _, err = io.Copy(out, in)
+ if err != nil {
+ return err
+ }
+ return out.Close()
+}
diff --git a/cmd/dnsd/dnsd.conf b/cmd/dnsd/dnsd.conf
new file mode 100644
index 0000000..e490d13
--- /dev/null
+++ b/cmd/dnsd/dnsd.conf
@@ -0,0 +1,7 @@
+port 5900
+forward-server 1.1.1.1:53
+
+zone-url (
+ https://xena.greedo.xeserv.us/files/akua.zone
+ https://xena.greedo.xeserv.us/files/adblock.zone
+) \ No newline at end of file
diff --git a/cmd/dnsd/main.go b/cmd/dnsd/main.go
new file mode 100644
index 0000000..314c86d
--- /dev/null
+++ b/cmd/dnsd/main.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+ "bufio"
+ "flag"
+ "log"
+ "net/http"
+
+ "os"
+ "os/signal"
+ "syscall"
+
+ "github.com/Xe/x/internal"
+ "github.com/miekg/dns"
+ "github.com/mmikulicic/stringlist"
+)
+
+var (
+ port = flag.String("port", "53", "UDP port to listen on for DNS")
+ server = flag.String("forward-server", "1.1.1.1:53", "forward DNS server")
+
+ zoneURLs = stringlist.Flag("zone-url", "DNS zonefiles to load")
+)
+
+var (
+ defaultZoneURLS = []string{
+ "https://xena.greedo.xeserv.us/files/akua.zone",
+ "https://xena.greedo.xeserv.us/files/adblock.zone",
+ }
+)
+
+func main() {
+ internal.HandleStartup()
+
+ if len(*zoneURLs) == 0 {
+ *zoneURLs = defaultZoneURLS
+ }
+
+ for _, zurl := range *zoneURLs {
+ log.Printf("conf: -zone-url=%s", zurl)
+ }
+ log.Printf("conf: -port=%s", *port)
+ log.Printf("conf: -forward-server=%s", *server)
+
+ rrs := []dns.RR{}
+
+ for _, zurl := range *zoneURLs {
+ resp, err := http.Get(zurl)
+ if err != nil {
+ panic(err)
+ }
+
+ reader := bufio.NewReaderSize(resp.Body, 2048)
+
+ var i int
+ zp := dns.NewZoneParser(reader, "", zurl)
+ for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
+ rrs = append(rrs, rr)
+ i++
+ }
+
+ if zp.Err() != nil {
+ panic(zp.Err())
+ }
+
+ resp.Body.Close()
+
+ log.Printf("%s: %d records", zurl, i)
+ }
+
+ dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
+ m := new(dns.Msg)
+ m.SetReply(r)
+ m.Authoritative = true
+
+ for _, q := range r.Question {
+ answers := []dns.RR{}
+ for _, rr := range rrs {
+ rh := rr.Header()
+
+ if rh.Rrtype == dns.TypeCNAME && q.Name == rh.Name {
+ answers = append(answers, rr)
+
+ for _, a := range resolver("127.0.0.1:"+*port, rr.(*dns.CNAME).Target, q.Qtype) {
+ answers = append(answers, a)
+ }
+ }
+
+ if q.Name == rh.Name && q.Qtype == rh.Rrtype && q.Qclass == rh.Class {
+ answers = append(answers, rr)
+ }
+ }
+ if len(answers) == 0 && *server != "" {
+ for _, a := range resolver(*server, q.Name, q.Qtype) {
+ answers = append(answers, a)
+ }
+ }
+ for _, a := range answers {
+ m.Answer = append(m.Answer, a)
+ }
+ }
+ w.WriteMsg(m)
+ })
+
+ go func() {
+ srv := &dns.Server{Addr: ":" + *port, Net: "udp"}
+ if err := srv.ListenAndServe(); err != nil {
+ log.Fatalf("Failed to set udp listener %s\n", err.Error())
+ }
+ }()
+
+ sig := make(chan os.Signal)
+ signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
+ s := <-sig
+ log.Fatalf("Signal (%v) received, stopping\n", s)
+}
+
+func resolver(server, fqdn string, r_type uint16) []dns.RR {
+ m1 := new(dns.Msg)
+ m1.Id = dns.Id()
+ m1.SetQuestion(fqdn, r_type)
+
+ in, err := dns.Exchange(m1, server)
+ if err == nil {
+ return in.Answer
+ }
+ return []dns.RR{}
+}
diff --git a/cmd/ff-primitive/main.go b/cmd/ff-primitive/main.go
new file mode 100644
index 0000000..3ee0882
--- /dev/null
+++ b/cmd/ff-primitive/main.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "flag"
+ "image"
+ "log"
+ "os"
+ "runtime"
+ "runtime/pprof"
+
+ "github.com/Xe/x/internal"
+ "github.com/fogleman/primitive/primitive"
+ farbfeld "github.com/hullerob/go.farbfeld"
+)
+
+var (
+ shapeCount = flag.Int("count", 150, "number of shapes used")
+ repeatShapeCount = flag.Int("repeat-count", 0, "number of extra shapes drawn in each step")
+ alpha = flag.Int("alpha", 128, "alpha of all shapes")
+ cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
+)
+
+func stepImg(img image.Image, count int) image.Image {
+ bg := primitive.MakeColor(primitive.AverageImageColor(img))
+ model := primitive.NewModel(img, bg, 512, runtime.NumCPU())
+
+ for range make([]struct{}, count) {
+ model.Step(primitive.ShapeTypeTriangle, *alpha, *repeatShapeCount)
+ }
+
+ return model.Context.Image()
+}
+
+func main() {
+ internal.HandleStartup()
+
+ if *cpuprofile != "" {
+ f, err := os.Create(*cpuprofile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ pprof.StartCPUProfile(f)
+ defer pprof.StopCPUProfile()
+ }
+
+ img, err := farbfeld.Decode(os.Stdin)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = farbfeld.Encode(os.Stdout, stepImg(img, *shapeCount))
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/cmd/johaus/build.go b/cmd/johaus/build.go
new file mode 100644
index 0000000..07e8489
--- /dev/null
+++ b/cmd/johaus/build.go
@@ -0,0 +1,39 @@
+//+build ignore
+
+// Builds and deploys the application to minipaas.
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+
+ "github.com/Xe/x/internal/greedo"
+ "github.com/Xe/x/internal/minipaas"
+ "github.com/Xe/x/internal/yeet"
+)
+
+func main() {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ env := append(os.Environ(), []string{"CGO_ENABLED=0", "GOOS=linux"}...)
+ yeet.ShouldWork(ctx, env, yeet.WD, "vgo", "build", "-o=web")
+ yeet.ShouldWork(ctx, env, yeet.WD, "appsluggr", "-web=web")
+ fin, err := os.Open("slug.tar.gz")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fin.Close()
+
+ fname := "johaus-" + yeet.DateTag + ".tar.gz"
+ pubURL, err := greedo.CopyFile(fname, fin)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = minipaas.Exec("tar:from johaus " + pubURL)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/cmd/johaus/main.go b/cmd/johaus/main.go
new file mode 100644
index 0000000..5c9c59e
--- /dev/null
+++ b/cmd/johaus/main.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "io/ioutil"
+ "log"
+ "net/http"
+
+ "github.com/Xe/x/internal"
+ "within.website/johaus/parser"
+ _ "within.website/johaus/parser/camxes"
+ "within.website/johaus/pretty"
+)
+
+const dialect = "camxes"
+
+var (
+ port = flag.String("port", "9001", "TCP port to bind on for HTTP")
+)
+
+func main() {
+ internal.HandleStartup()
+
+ log.Printf("Listening on http://0.0.0.0:%s", *port)
+
+ http.DefaultServeMux.HandleFunc("/tree", tree)
+ http.DefaultServeMux.HandleFunc("/braces", braces)
+
+ http.ListenAndServe(":"+*port, nil)
+}
+
+func braces(w http.ResponseWriter, r *http.Request) {
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, "can't parse: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ tree, err := parser.Parse(dialect, string(data))
+ if err != nil {
+ http.Error(w, "can't parse: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ parser.RemoveMorphology(tree)
+ parser.AddElidedTerminators(tree)
+ parser.RemoveSpace(tree)
+ parser.CollapseLists(tree)
+
+ pretty.Braces(w, tree)
+}
+func tree(w http.ResponseWriter, r *http.Request) {
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, "can't parse: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ tree, err := parser.Parse(dialect, string(data))
+ if err != nil {
+ http.Error(w, "can't parse: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ parser.RemoveMorphology(tree)
+ parser.AddElidedTerminators(tree)
+ parser.RemoveSpace(tree)
+ parser.CollapseLists(tree)
+
+ json.NewEncoder(w).Encode(tree)
+}
diff --git a/cmd/la-baujmi/.gitignore b/cmd/la-baujmi/.gitignore
new file mode 100644
index 0000000..07b6fbf
--- /dev/null
+++ b/cmd/la-baujmi/.gitignore
@@ -0,0 +1,2 @@
+.env
+la-baujmi \ No newline at end of file
diff --git a/cmd/la-baujmi/README.md b/cmd/la-baujmi/README.md
new file mode 100644
index 0000000..10aa48d
--- /dev/null
+++ b/cmd/la-baujmi/README.md
@@ -0,0 +1,66 @@
+# la baujmi
+
+Combination of:
+
+> bangu
+>
+> x1 is a/the language/dialect used by x2 to express/communicate x3 (si'o/du'u, not quote).
+
+> jimpe
+>
+> x1 understands/comprehends fact/truth x2 (du'u) about subject x3; x1 understands (fi) x3.
+
+This is an attempt to create a tool that can understand language.
+
+At first, [Toki Pona](http://tokipona.net) will be used. At a high level a toki pona sentence consists of four main parts:
+
+- context phrase
+- subject + descriptors
+- verb + descriptors
+- object + descriptors
+
+You can describe a sentence as a form of predicate relation between those four parts. If you are told "Stacy purchased a tool for strange-plant", you can later then ask the program who purchased a tool for strange-plant.
+
+Because a Toki Pona sentence always matches the following form:
+
+```
+[<name> o,] [context la] <subject> [li <verb> [e <object>]]
+```
+
+And the particle `seme` fills in the part of a question that you don't know. So from this we can fill in the blanks with prolog.
+
+Consider the following:
+
+```
+jan Kesi li toki.
+Cadey is speaking
+toki(jan_Kesi).
+
+jan Kesi en jan Pola li toki.
+Cadey and Pola are speaking.
+toki(jan_Kesi).
+toki(jan_Pola).
+
+jan Kesi li toki e jan Pola.
+Cadey is talking about Pola
+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/cmd/la-baujmi/const.go b/cmd/la-baujmi/const.go
new file mode 100644
index 0000000..2b70d19
--- /dev/null
+++ b/cmd/la-baujmi/const.go
@@ -0,0 +1,5 @@
+package main
+
+const (
+ tokiPonaAPIURL = "https://us-central1-golden-cove-408.cloudfunctions.net/toki-pona-verb-marker"
+)
diff --git a/cmd/la-baujmi/corpus.txt b/cmd/la-baujmi/corpus.txt
new file mode 100644
index 0000000..5313317
--- /dev/null
+++ b/cmd/la-baujmi/corpus.txt
@@ -0,0 +1,23 @@
+sina li lukin e ni la sina pilin pona. sina li lukin e ni la pilin pona ni li suli suwi. sina li lukin e ni la sina li pona ale. sina li lukin e ni la sina li pali e ijo mute mute.
+
+sina li pona ale la sina pali ijo mute mute.
+
+sina li jan pi pali ijo mute mute. sina ken pali ijo mute mute.
+
+sina li wile pali e ilo suli la sina wile jo lukin wawa e tawa ala pi tenpo ni. lukin wawa e tawa ala pi tenpo ni li ilo sina kama e pali ijo pi tenpo pini. nasin ni li pilin sina ala. sina li kama pi toki lawa insa ala e pali ijo pi tenpo pini.
+
+tenpo ni li ni tenpo.
+
+tenpo pini li tenpo ni ala. tenpo ni la tenpo pini li suli ala.
+
+tenpo kama li tenpo ni ala. tenpo ni la tenpo kama li suli ala.
+
+tenpo ni li tawa ale. sina ken tawa ale e tawa ala pi tenpo ni.
+
+sina wile jo tawa ala pi tenpo ni la sina wile tawa ni:
+
+tenpo mute anu sina pilin ni la sijelo sina suli.
+tenpo mute anu sina pilin ni la sijelo sina lili.
+sina lukin e ijo mute la sina lukin wawa e nena insa.
+
+sina ken tawa ijo mute la sina kepeken tawa ala pi tenpo ni e sina. sina jo e ni la sina jo lukin wawa pona. sina jo lukin wawa mute en tawa ala pi tenpo ni ale li pali pona e ilo suli.
diff --git a/cmd/la-baujmi/fact.go b/cmd/la-baujmi/fact.go
new file mode 100644
index 0000000..a421d1e
--- /dev/null
+++ b/cmd/la-baujmi/fact.go
@@ -0,0 +1,210 @@
+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
+
+ var varCount byte
+
+ sb.WriteString("selbri(verb(")
+
+ if s.Predicate == "seme" {
+ sb.WriteByte(byte('A') + varCount)
+ varCount++
+ } else {
+ sb.WriteString(s.Predicate)
+ }
+ sb.WriteString("), ")
+
+ for i, arg := range s.Arguments {
+ if i != 0 {
+ sb.WriteString(", ")
+ }
+
+ switch arg {
+ case "subject(seme)", "object(seme)":
+ if strings.HasPrefix(arg, "subject") {
+ sb.WriteString("subject(")
+ } else {
+ sb.WriteString("object(")
+ }
+ sb.WriteByte(byte('A') + varCount)
+ varCount++
+ sb.WriteByte(')')
+ 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:
+ if strings.Contains(pt.Braces(), " en ") {
+ temp := strings.Split(strings.Join(pt.Tokens, " "), " en ")
+
+ for _, t := range temp {
+ subjects = append(subjects, "subject("+t+")")
+ }
+
+ continue
+ }
+
+ if len(pt.Parts) != 0 {
+ var sb strings.Builder
+ sb.WriteString("subject(")
+
+ for i, sp := range pt.Parts {
+ if i != 0 {
+ sb.WriteString(", ")
+ }
+ if sp.Sep != nil && *sp.Sep == "pi" {
+ sb.WriteString("pi(")
+ for j, tk := range sp.Tokens {
+ if j != 0 {
+ sb.WriteString(", ")
+ }
+
+ sb.WriteString(tk)
+ }
+ sb.WriteString(")")
+ } else {
+ sb.WriteString(strings.Join(sp.Tokens, "_"))
+ }
+ }
+
+ sb.WriteString(")")
+
+ subjects = append(objects, sb.String())
+ continue
+ }
+
+ subjects = append(subjects, "subject("+strings.Join(pt.Tokens, "_")+")")
+
+ case tokiponatokens.PartVerbMarker:
+ verbs = append(verbs, strings.Join(pt.Tokens, "_"))
+
+ case tokiponatokens.PartObjectMarker:
+ if len(pt.Parts) != 0 {
+ var sb strings.Builder
+ sb.WriteString("object(")
+
+ for i, sp := range pt.Parts {
+ if i != 0 {
+ sb.WriteString(", ")
+ }
+ if sp.Sep != nil && *sp.Sep == "pi" {
+ sb.WriteString("pi(")
+ for j, tk := range sp.Tokens {
+ if j != 0 {
+ sb.WriteString(", ")
+ }
+
+ sb.WriteString(tk)
+ }
+ sb.WriteString(")")
+ } else {
+ sb.WriteString(strings.Join(sp.Tokens, "_"))
+ }
+ }
+
+ sb.WriteString(")")
+
+ objects = append(objects, sb.String())
+ continue
+ }
+ objects = append(objects, "object("+strings.Join(pt.Tokens, "_")+")")
+
+ case tokiponatokens.PartPunctuation:
+ if len(pt.Tokens) == 1 {
+ switch pt.Tokens[0] {
+ case "la":
+ context = "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 {
+ for _, s := range subjects {
+ if len(objects) == 0 {
+ // sumti: x1 is a/the argument of predicate function x2 filling place x3 (kind/number)
+ var sumti []string
+ if context != "" {
+ sumti = append([]string{}, context)
+ }
+ sumti = append(sumti, s)
+
+ r := Selbri{
+ Predicate: v,
+ Arguments: sumti,
+ }
+
+ result = append(result, r)
+ }
+
+ for _, o := range objects {
+ // sumti: x1 is a/the argument of predicate function x2 filling place x3 (kind/number)
+ var sumti []string
+ if context != "" {
+ sumti = append([]string{}, context)
+ }
+ sumti = append(sumti, s)
+ sumti = append(sumti, o)
+
+ r := Selbri{
+ Predicate: v,
+ Arguments: sumti,
+ }
+
+ result = append(result, r)
+ }
+ }
+ }
+
+ return result, nil
+}
diff --git a/cmd/la-baujmi/fact_test.go b/cmd/la-baujmi/fact_test.go
new file mode 100644
index 0000000..43f7a4b
--- /dev/null
+++ b/cmd/la-baujmi/fact_test.go
@@ -0,0 +1,278 @@
+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 _, v := range a {
+ var has bool
+ for _, vv := range b {
+ if v == vv {
+ has = true
+ }
+ }
+ if !has {
+ return false
+ }
+ }
+ return true
+}
+
+func selbrisEqual(a, b []Selbri) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for _, v := range a {
+ var has bool
+ for _, vv := range b {
+ if v.Eq(vv) {
+ has = true
+ }
+ }
+
+ if !has {
+ 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{"subject(ona)", "object(mute)"},
+ },
+ },
+ wantFacts: []string{"selbri(verb(sona), subject(ona), object(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{"context(subject(tenpo_ni))", "subject(seme)"},
+ },
+ },
+ wantFacts: []string{"selbri(verb(ala), context(subject(tenpo_ni)), subject(A))."},
+ },
+ {
+ name: "pi_subject",
+ json: []byte(`[{"part":"subject","parts":[{"part":"subject","tokens":["ilo","mi"]},{"part":"subject","sep":"pi","tokens":["kasi","nasa"]}]},{"part":"verbMarker","sep":"li","tokens":["pona","ale"]}]`),
+ want: []Selbri{
+ {
+ Predicate: "pona_ale",
+ Arguments: []string{"subject(ilo_mi, pi(kasi, nasa))"},
+ },
+ },
+ wantFacts: []string{"selbri(verb(pona_ale), subject(ilo_mi, pi(kasi, nasa)))."},
+ },
+ {
+ name: "pi_object",
+ json: []byte(`[{"part":"subject","tokens":["mi"]},{"part":"verbMarker","sep":"li","tokens":["esun"]},{"part":"objectMarker","sep":"e","parts":[{"part":"objectMarker","tokens":["ilo"]},{"part":"objectMarker","sep":"pi","tokens":["kalama","musi"]}]},{"part":"punctuation","tokens":["period"]}]`),
+ want: []Selbri{
+ {
+ Predicate: "esun",
+ Arguments: []string{"subject(mi)", "object(ilo, pi(kalama, musi))"},
+ },
+ },
+ wantFacts: []string{"selbri(verb(esun), subject(mi), object(ilo, pi(kalama, musi)))."},
+ },
+ {
+ name: "multiple verbs",
+ json: []byte(`[{"part":"subject","tokens":["ona"]},{"part":"verbMarker","sep":"li","tokens":["sona"]},{"part":"verbMarker","sep":"li","tokens":["pona"]},{"part":"objectMarker","sep":"e","tokens":["mute"]},{"part":"punctuation","tokens":["period"]}]`),
+ want: []Selbri{
+ {
+ Predicate: "sona",
+ Arguments: []string{"subject(ona)", "object(mute)"},
+ },
+ {
+ Predicate: "pona",
+ Arguments: []string{"subject(ona)", "object(mute)"},
+ },
+ },
+ wantFacts: []string{
+ "selbri(verb(sona), subject(ona), object(mute)).",
+ "selbri(verb(pona), subject(ona), object(mute)).",
+ },
+ },
+ {
+ name: "multiple subjects and verbs",
+ json: []byte(`[{"part":"subject","tokens":["ona","en","sina","en","mi"]},{"part":"verbMarker","sep":"li","tokens":["sona"]},{"part":"verbMarker","sep":"li","tokens":["pona"]},{"part":"objectMarker","sep":"e","tokens":["mute"]},{"part":"punctuation","tokens":["period"]}]`),
+ want: []Selbri{
+ {
+ Predicate: "sona",
+ Arguments: []string{"subject(ona)", "object(mute)"},
+ },
+ {
+ Predicate: "pona",
+ Arguments: []string{"subject(ona)", "object(mute)"},
+ },
+ {
+ Predicate: "sona",
+ Arguments: []string{"subject(sina)", "object(mute)"},
+ },
+ {
+ Predicate: "pona",
+ Arguments: []string{"subject(sina)", "object(mute)"},
+ },
+ {
+ Predicate: "sona",
+ Arguments: []string{"subject(mi)", "object(mute)"},
+ },
+ {
+ Predicate: "pona",
+ Arguments: []string{"subject(mi)", "object(mute)"},
+ },
+ },
+ wantFacts: []string{
+ "selbri(verb(sona), subject(ona), object(mute)).",
+ "selbri(verb(sona), subject(sina), object(mute)).",
+ "selbri(verb(sona), subject(mi), object(mute)).",
+ "selbri(verb(pona), subject(ona), object(mute)).",
+ "selbri(verb(pona), subject(sina), object(mute)).",
+ "selbri(