aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2019-06-25 22:59:06 +0000
committerChristine Dodrill <me@christine.website>2019-06-25 22:59:06 +0000
commit96a073ecddf7eee051050347a09786dc38b3a8ac (patch)
tree44644398eaf655e5be51cdbd8bab25330bc0bd58
parentab2f1f58d4de28aa708cdbc1602c5ae1a941fce6 (diff)
downloadx-96a073ecddf7eee051050347a09786dc38b3a8ac.tar.xz
x-96a073ecddf7eee051050347a09786dc38b3a8ac.zip
h
-rw-r--r--cmd/h/compile.go98
-rw-r--r--cmd/h/main.go59
-rw-r--r--cmd/h/run.go76
-rw-r--r--go.mod5
-rw-r--r--go.sum6
-rw-r--r--h/h.peg12
-rw-r--r--h/h_gen.go789
-rw-r--r--h/parser.go42
-rw-r--r--h/simplify.go59
-rw-r--r--jbo/namcu/namcu.go4
10 files changed, 1149 insertions, 1 deletions
diff --git a/cmd/h/compile.go b/cmd/h/compile.go
new file mode 100644
index 0000000..7b4d6e3
--- /dev/null
+++ b/cmd/h/compile.go
@@ -0,0 +1,98 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "text/template"
+ "time"
+
+ "within.website/x/h"
+)
+
+var (
+ wat2wasmLoc string
+ wasmTemplateObj *template.Template
+)
+
+func init() {
+ loc, err := exec.LookPath("wat2wasm")
+ if err != nil {
+ panic(err)
+ }
+
+ wat2wasmLoc = loc
+ wasmTemplateObj = template.Must(template.New("h.wast").Parse(wasmTemplate))
+}
+
+// CompiledProgram is a fully parsed and compiled h program.
+type CompiledProgram struct {
+ Source string `json:"src"`
+ WebAssemblyText string `json:"wat"`
+ Binary []byte `json:"bin"`
+}
+
+func compile(source string) (*CompiledProgram, error) {
+ tree, err := h.Parse(source)
+ if err != nil {
+ return nil, err
+ }
+
+ result := CompiledProgram{
+ Source: source,
+ }
+
+ dir, err := ioutil.TempDir("", "h")
+ if err != nil {
+ return nil, err
+ }
+ defer os.RemoveAll(dir)
+
+ fout, err := os.Create(filepath.Join(dir, "h.wast"))
+ if err != nil {
+ return nil, err
+ }
+
+ buf := bytes.NewBuffer(nil)
+
+ err = wasmTemplateObj.Execute(buf, []byte(tree.Text))
+ if err != nil {
+ return nil, err
+ }
+
+ result.WebAssemblyText = buf.String()
+ _, err = fout.WriteString(result.WebAssemblyText)
+ if err != nil {
+ return nil, err
+ }
+
+ fname := fout.Name()
+
+ err = fout.Close()
+ if err != nil {
+ return nil, err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+ defer cancel()
+
+ cmd := exec.CommandContext(ctx, wat2wasmLoc, fname, "-o", filepath.Join(dir, "h.wasm"))
+ cmd.Dir = dir
+
+ err = cmd.Run()
+ if err != nil {
+ return nil, err
+ }
+
+ data, err := ioutil.ReadFile(filepath.Join(dir, "h.wasm"))
+ if err != nil {
+ return nil, err
+ }
+
+ result.Binary = data
+
+ return &result, nil
+}
diff --git a/cmd/h/main.go b/cmd/h/main.go
new file mode 100644
index 0000000..f7e064e
--- /dev/null
+++ b/cmd/h/main.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+
+ "within.website/x/internal"
+)
+
+var (
+ program = flag.String("p", "h", "h program to compile/run")
+)
+
+func main() {
+ internal.HandleStartup()
+
+ log.Println("compiling...")
+ comp, err := compile(*program)
+ if err != nil {
+ panic(err)
+ }
+
+ log.Println("running...")
+ er, err := run(*comp)
+ if err != nil {
+ panic(err)
+ }
+
+ log.Println("success!")
+
+ log.Printf("gas used:\t%d", er.GasUsed)
+ log.Printf("exec time:\t%s", er.ExecTime)
+ log.Println("output:")
+ fmt.Println(er.Output)
+}
+
+const wasmTemplate = `(module
+ (import "h" "h" (func $h (param i32)))
+ (func $h_main
+ (local i32 i32 i32)
+ (local.set 0 (i32.const 10))
+ (local.set 1 (i32.const 104))
+ (local.set 2 (i32.const 37))
+ {{ range . -}}
+ {{ if eq . 32 -}}
+ (call $h (get_local 0))
+ {{ end -}}
+ {{ if eq . 104 -}}
+ (call $h (get_local 1))
+ {{ end -}}
+ {{ if eq . 39 -}}
+ (call $h (get_local 2))
+ {{ end -}}
+ {{ end -}}
+ (call $h (get_local 0))
+ )
+ (export "main" (func $h_main))
+)`
diff --git a/cmd/h/run.go b/cmd/h/run.go
new file mode 100644
index 0000000..d07e51f
--- /dev/null
+++ b/cmd/h/run.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+ "errors"
+ "time"
+
+ "github.com/perlin-network/life/compiler"
+ "github.com/perlin-network/life/exec"
+)
+
+type Process struct {
+ Source CompiledProgram
+ Output []byte
+}
+
+// ResolveGlobal does nothing, currently.
+func (p *Process) ResolveGlobal(module, field string) int64 { return 0 }
+
+// ResolveFunc resolves h's ABI and importable function.
+func (p *Process) ResolveFunc(module, field string) exec.FunctionImport {
+ switch module {
+ case "h":
+ switch field {
+ case "h":
+ return func(vm *exec.VirtualMachine) int64 {
+ frame := vm.GetCurrentFrame()
+ data := frame.Locals[0]
+ p.Output = append(p.Output, byte(data))
+
+ return 0
+ }
+
+ default:
+ panic("impossible state")
+ }
+
+ default:
+ panic("impossible state")
+ }
+}
+
+type ExecResult struct {
+ Output string
+ GasUsed uint64
+ ExecTime time.Duration
+}
+
+func run(cp CompiledProgram) (*ExecResult, error) {
+ p := &Process{
+ Source: cp,
+ }
+
+ var cfg exec.VMConfig
+ gp := &compiler.SimpleGasPolicy{GasPerInstruction: 1}
+ vm, err := exec.NewVirtualMachine(cp.Binary, cfg, p, gp)
+ if err != nil {
+ return nil, err
+ }
+
+ mainFunc, ok := vm.GetFunctionExport("main")
+ if !ok {
+ return nil, errors.New("impossible state: no main function exposed")
+ }
+
+ t0 := time.Now()
+ _, err = vm.Run(mainFunc)
+ if err != nil {
+ return nil, err
+ }
+
+ return &ExecResult{
+ Output: string(p.Output),
+ GasUsed: vm.Gas,
+ ExecTime: time.Since(t0),
+ }, nil
+}
diff --git a/go.mod b/go.mod
index c2c05c4..44bf1e7 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
github.com/dgryski/go-failure v0.0.0-20151001134759-4963dbd58fd0
github.com/dgryski/go-onlinestats v0.0.0-20170612111826-1c7d19468768 // indirect
github.com/disintegration/imaging v1.6.0
- github.com/eaburns/peggy v0.0.0-20190420135231-b61cdde6efe6 // indirect
+ github.com/eaburns/peggy v0.0.0-20190420135231-b61cdde6efe6
github.com/eaburns/pretty v0.0.0-20190404101635-2e1d2550ef0b // indirect
github.com/elazarl/go-bindata-assetfs v1.0.0
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51
@@ -46,6 +46,7 @@ require (
github.com/otiai10/copy v1.0.1
github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 // indirect
github.com/pborman/uuid v1.2.0
+ github.com/perlin-network/life v0.0.0-20190625155037-103174020946
github.com/peterh/liner v1.1.0
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pkg/errors v0.8.1
@@ -79,3 +80,5 @@ require (
within.website/johaus v1.1.0
within.website/ln v0.6.0
)
+
+replace github.com/go-interpreter/wagon v0.0.0 => github.com/perlin-network/wagon v0.3.1-0.20180825141017-f8cb99b55a39
diff --git a/go.sum b/go.sum
index 9d43538..6903a5d 100644
--- a/go.sum
+++ b/go.sum
@@ -114,6 +114,10 @@ github.com/otiai10/mint v1.2.4 h1:DxYL0itZyPaR5Z9HILdxSoHx+gNs6Yx+neOGS3IVUk0=
github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/perlin-network/life v0.0.0-20190625155037-103174020946 h1:qA5Q7rgctaRZKDiTKbEJrazx1M4Pbiwb8Gfr3WiuVTg=
+github.com/perlin-network/life v0.0.0-20190625155037-103174020946/go.mod h1:z/EH0mO9zbeuTT5NX4u2VqVSG8y2vDQXz6iDKxikW2I=
+github.com/perlin-network/wagon v0.3.1-0.20180825141017-f8cb99b55a39 h1:CYHXy6CWxxL7ugjvCbTELOm2j5iRLEWGPl3AQYvretw=
+github.com/perlin-network/wagon v0.3.1-0.20180825141017-f8cb99b55a39/go.mod h1:zHOMvbitcZek8oshsMO5VpyBjWjV9X8cn8WTZwdebpM=
github.com/peterh/liner v1.1.0 h1:f+aAedNJA6uk7+6rXsYBnhdo4Xux7ESLe+kcuVUF5os=
github.com/peterh/liner v1.1.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
@@ -150,6 +154,8 @@ github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef h1:7D6Nm4D6f0ci9yttWaKjM1TMAXrH5Su72dojqYGntFY=
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef/go.mod h1:WLFStEdnJXpjK8kd4qKLwQKX/1vrDzp5BcDyiZJBHJM=
github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89/go.mod h1:ejwOYCjnDMyO5LXFXRARQJGBZ6xQJZ3rgAHE5drSuMM=
+github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
+github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
diff --git a/h/h.peg b/h/h.peg
new file mode 100644
index 0000000..542a887
--- /dev/null
+++ b/h/h.peg
@@ -0,0 +1,12 @@
+{
+package h
+
+import (
+ "github.com/eaburns/peggy/peg"
+)
+}
+
+sep <- space+h
+space <- ' '
+h <- 'h' / "'"
+H <- h+sep+ / h
diff --git a/h/h_gen.go b/h/h_gen.go
new file mode 100644
index 0000000..365b630
--- /dev/null
+++ b/h/h_gen.go
@@ -0,0 +1,789 @@
+package h
+
+import (
+ "github.com/eaburns/peggy/peg"
+)
+
+type _Parser struct {
+ text string
+ deltaPos []_Rules
+ deltaErr []_Rules
+ node map[_key]*peg.Node
+ fail map[_key]*peg.Fail
+ lastFail int
+ data interface{}
+}
+
+type _key struct {
+ start int
+ name string
+}
+
+func _NewParser(text string) *_Parser {
+ return &_Parser{
+ text: text,
+ deltaPos: make([]_Rules, len(text)+1),
+ deltaErr: make([]_Rules, len(text)+1),
+ node: make(map[_key]*peg.Node),
+ fail: make(map[_key]*peg.Fail),
+ }
+}
+
+type _Rules struct {
+ sep int32
+ space int32
+ h int32
+ H int32
+}
+
+func _max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func _next(parser *_Parser, pos int) (rune, int) {
+ r, w := peg.DecodeRuneInString(parser.text[pos:])
+ return r, w
+}
+
+func _node(name string) *peg.Node {
+ return &peg.Node{Name: name}
+}
+
+func _sub(parser *_Parser, start, end int, kids []*peg.Node) *peg.Node {
+ node := &peg.Node{
+ Text: parser.text[start:end],
+ Kids: make([]*peg.Node, len(kids)),
+ }
+ copy(node.Kids, kids)
+ return node
+}
+
+func _leaf(parser *_Parser, start, end int) *peg.Node {
+ return &peg.Node{Text: parser.text[start:end]}
+}
+
+func _sepAccepts(parser *_Parser, start int) (deltaPos, deltaErr int) {
+ if dp := parser.deltaPos[start].sep; dp != 0 {
+ de := parser.deltaErr[start].sep - 1
+ if dp > 0 {
+ dp--
+ }
+ return int(dp), int(de)
+ }
+ pos, perr := start, -1
+ // space+ h
+ // space+
+ // space
+ if dp, de := _spaceAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ for {
+ pos1 := pos
+ // space
+ if dp, de := _spaceAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail2
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ continue
+ fail2:
+ pos = pos1
+ break
+ }
+ // h
+ if dp, de := _hAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ parser.deltaPos[start].sep = int32(pos-start) + 1
+ parser.deltaErr[start].sep = int32(perr-start) + 1
+ parser.lastFail = perr
+ return pos - start, perr - start
+fail:
+ parser.deltaPos[start].sep = -1
+ parser.deltaErr[start].sep = int32(perr-start) + 1
+ parser.lastFail = perr
+ return -1, perr - start
+}
+
+func _sepNode(parser *_Parser, start int) (int, *peg.Node) {
+ dp := parser.deltaPos[start].sep
+ if dp < 0 {
+ return -1, nil
+ }
+ key := _key{start: start, name: "sep"}
+ node := parser.node[key]
+ if node != nil {
+ return start + int(dp-1), node
+ }
+ pos := start
+ node = _node("sep")
+ // space+ h
+ // space+
+ // space
+ if p, kid := _spaceNode(parser, pos); kid == nil {
+ goto fail
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ for {
+ nkids0 := len(node.Kids)
+ pos1 := pos
+ // space
+ if p, kid := _spaceNode(parser, pos); kid == nil {
+ goto fail2
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ continue
+ fail2:
+ node.Kids = node.Kids[:nkids0]
+ pos = pos1
+ break
+ }
+ // h
+ if p, kid := _hNode(parser, pos); kid == nil {
+ goto fail
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ node.Text = parser.text[start:pos]
+ parser.node[key] = node
+ return pos, node
+fail:
+ return -1, nil
+}
+
+func _sepFail(parser *_Parser, start, errPos int) (int, *peg.Fail) {
+ if start > parser.lastFail {
+ return -1, &peg.Fail{}
+ }
+ dp := parser.deltaPos[start].sep
+ de := parser.deltaErr[start].sep
+ if start+int(de-1) < errPos {
+ if dp > 0 {
+ return start + int(dp-1), &peg.Fail{}
+ }
+ return -1, &peg.Fail{}
+ }
+ key := _key{start: start, name: "sep"}
+ failure := parser.fail[key]
+ if dp < 0 && failure != nil {
+ return -1, failure
+ }
+ if dp > 0 && failure != nil {
+ return start + int(dp-1), failure
+ }
+ pos := start
+ failure = &peg.Fail{
+ Name: "sep",
+ Pos: int(start),
+ }
+ // space+ h
+ // space+
+ // space
+ {
+ p, kid := _spaceFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail
+ }
+ pos = p
+ }
+ for {
+ pos1 := pos
+ // space
+ {
+ p, kid := _spaceFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail2
+ }
+ pos = p
+ }
+ continue
+ fail2:
+ pos = pos1
+ break
+ }
+ // h
+ {
+ p, kid := _hFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail
+ }
+ pos = p
+ }
+ parser.fail[key] = failure
+ return pos, failure
+fail:
+ parser.fail[key] = failure
+ return -1, failure
+}
+
+func _spaceAccepts(parser *_Parser, start int) (deltaPos, deltaErr int) {
+ if dp := parser.deltaPos[start].space; dp != 0 {
+ de := parser.deltaErr[start].space - 1
+ if dp > 0 {
+ dp--
+ }
+ return int(dp), int(de)
+ }
+ pos, perr := start, -1
+ // " "
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != " " {
+ perr = _max(perr, pos)
+ goto fail
+ }
+ pos++
+ parser.deltaPos[start].space = int32(pos-start) + 1
+ parser.deltaErr[start].space = int32(perr-start) + 1
+ parser.lastFail = perr
+ return pos - start, perr - start
+fail:
+ parser.deltaPos[start].space = -1
+ parser.deltaErr[start].space = int32(perr-start) + 1
+ parser.lastFail = perr
+ return -1, perr - start
+}
+
+func _spaceNode(parser *_Parser, start int) (int, *peg.Node) {
+ dp := parser.deltaPos[start].space
+ if dp < 0 {
+ return -1, nil
+ }
+ key := _key{start: start, name: "space"}
+ node := parser.node[key]
+ if node != nil {
+ return start + int(dp-1), node
+ }
+ pos := start
+ node = _node("space")
+ // " "
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != " " {
+ goto fail
+ }
+ node.Kids = append(node.Kids, _leaf(parser, pos, pos+1))
+ pos++
+ node.Text = parser.text[start:pos]
+ parser.node[key] = node
+ return pos, node
+fail:
+ return -1, nil
+}
+
+func _spaceFail(parser *_Parser, start, errPos int) (int, *peg.Fail) {
+ if start > parser.lastFail {
+ return -1, &peg.Fail{}
+ }
+ dp := parser.deltaPos[start].space
+ de := parser.deltaErr[start].space
+ if start+int(de-1) < errPos {
+ if dp > 0 {
+ return start + int(dp-1), &peg.Fail{}
+ }
+ return -1, &peg.Fail{}
+ }
+ key := _key{start: start, name: "space"}
+ failure := parser.fail[key]
+ if dp < 0 && failure != nil {
+ return -1, failure
+ }
+ if dp > 0 && failure != nil {
+ return start + int(dp-1), failure
+ }
+ pos := start
+ failure = &peg.Fail{
+ Name: "space",
+ Pos: int(start),
+ }
+ // " "
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != " " {
+ if pos >= errPos {
+ failure.Kids = append(failure.Kids, &peg.Fail{
+ Pos: int(pos),
+ Want: "\" \"",
+ })
+ }
+ goto fail
+ }
+ pos++
+ parser.fail[key] = failure
+ return pos, failure
+fail:
+ parser.fail[key] = failure
+ return -1, failure
+}
+
+func _hAccepts(parser *_Parser, start int) (deltaPos, deltaErr int) {
+ if dp := parser.deltaPos[start].h; dp != 0 {
+ de := parser.deltaErr[start].h - 1
+ if dp > 0 {
+ dp--
+ }
+ return int(dp), int(de)
+ }
+ pos, perr := start, -1
+ // "h"/"'"
+ {
+ pos2 := pos
+ // "h"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "h" {
+ perr = _max(perr, pos)
+ goto fail3
+ }
+ pos++
+ goto ok0
+ fail3:
+ pos = pos2
+ // "'"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "'" {
+ perr = _max(perr, pos)
+ goto fail4
+ }
+ pos++
+ goto ok0
+ fail4:
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ parser.deltaPos[start].h = int32(pos-start) + 1
+ parser.deltaErr[start].h = int32(perr-start) + 1
+ parser.lastFail = perr
+ return pos - start, perr - start
+fail:
+ parser.deltaPos[start].h = -1
+ parser.deltaErr[start].h = int32(perr-start) + 1
+ parser.lastFail = perr
+ return -1, perr - start
+}
+
+func _hNode(parser *_Parser, start int) (int, *peg.Node) {
+ dp := parser.deltaPos[start].h
+ if dp < 0 {
+ return -1, nil
+ }
+ key := _key{start: start, name: "h"}
+ node := parser.node[key]
+ if node != nil {
+ return start + int(dp-1), node
+ }
+ pos := start
+ node = _node("h")
+ // "h"/"'"
+ {
+ pos2 := pos
+ nkids1 := len(node.Kids)
+ // "h"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "h" {
+ goto fail3
+ }
+ node.Kids = append(node.Kids, _leaf(parser, pos, pos+1))
+ pos++
+ goto ok0
+ fail3:
+ node.Kids = node.Kids[:nkids1]
+ pos = pos2
+ // "'"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "'" {
+ goto fail4
+ }
+ node.Kids = append(node.Kids, _leaf(parser, pos, pos+1))
+ pos++
+ goto ok0
+ fail4:
+ node.Kids = node.Kids[:nkids1]
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ node.Text = parser.text[start:pos]
+ parser.node[key] = node
+ return pos, node
+fail:
+ return -1, nil
+}
+
+func _hFail(parser *_Parser, start, errPos int) (int, *peg.Fail) {
+ if start > parser.lastFail {
+ return -1, &peg.Fail{}
+ }
+ dp := parser.deltaPos[start].h
+ de := parser.deltaErr[start].h
+ if start+int(de-1) < errPos {
+ if dp > 0 {
+ return start + int(dp-1), &peg.Fail{}
+ }
+ return -1, &peg.Fail{}
+ }
+ key := _key{start: start, name: "h"}
+ failure := parser.fail[key]
+ if dp < 0 && failure != nil {
+ return -1, failure
+ }
+ if dp > 0 && failure != nil {
+ return start + int(dp-1), failure
+ }
+ pos := start
+ failure = &peg.Fail{
+ Name: "h",
+ Pos: int(start),
+ }
+ // "h"/"'"
+ {
+ pos2 := pos
+ // "h"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "h" {
+ if pos >= errPos {
+ failure.Kids = append(failure.Kids, &peg.Fail{
+ Pos: int(pos),
+ Want: "\"h\"",
+ })
+ }
+ goto fail3
+ }
+ pos++
+ goto ok0
+ fail3:
+ pos = pos2
+ // "'"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "'" {
+ if pos >= errPos {
+ failure.Kids = append(failure.Kids, &peg.Fail{
+ Pos: int(pos),
+ Want: "\"'\"",
+ })
+ }
+ goto fail4
+ }
+ pos++
+ goto ok0
+ fail4:
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ parser.fail[key] = failure
+ return pos, failure
+fail:
+ parser.fail[key] = failure
+ return -1, failure
+}
+
+func _HAccepts(parser *_Parser, start int) (deltaPos, deltaErr int) {
+ if dp := parser.deltaPos[start].H; dp != 0 {
+ de := parser.deltaErr[start].H - 1
+ if dp > 0 {
+ dp--
+ }
+ return int(dp), int(de)
+ }
+ pos, perr := start, -1
+ // h+ sep+/h
+ {
+ pos2 := pos
+ // h+ sep+
+ // h+
+ // h
+ if dp, de := _hAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail3
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ for {
+ pos5 := pos
+ // h
+ if dp, de := _hAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail6
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ continue
+ fail6:
+ pos = pos5
+ break
+ }
+ // sep+
+ // sep
+ if dp, de := _sepAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail3
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ for {
+ pos8 := pos
+ // sep
+ if dp, de := _sepAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail9
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ continue
+ fail9:
+ pos = pos8
+ break
+ }
+ goto ok0
+ fail3:
+ pos = pos2
+ // h
+ if dp, de := _hAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail10
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ goto ok0
+ fail10:
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ parser.deltaPos[start].H = int32(pos-start) + 1
+ parser.deltaErr[start].H = int32(perr-start) + 1
+ parser.lastFail = perr
+ return pos - start, perr - start
+fail:
+ parser.deltaPos[start].H = -1
+ parser.deltaErr[start].H = int32(perr-start) + 1
+ parser.lastFail = perr
+ return -1, perr - start
+}
+
+func _HNode(parser *_Parser, start int) (int, *peg.Node) {
+ dp := parser.deltaPos[start].H
+ if dp < 0 {
+ return -1, nil
+ }
+ key := _key{start: start, name: "H"}
+ node := parser.node[key]
+ if node != nil {
+ return start + int(dp-1), node
+ }
+ pos := start
+ node = _node("H")
+ // h+ sep+/h
+ {
+ pos2 := pos
+ nkids1 := len(node.Kids)
+ // h+ sep+
+ // h+
+ // h
+ if p, kid := _hNode(parser, pos); kid == nil {
+ goto fail3
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ for {
+ nkids4 := len(node.Kids)
+ pos5 := pos
+ // h
+ if p, kid := _hNode(parser, pos); kid == nil {
+ goto fail6
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ continue
+ fail6:
+ node.Kids = node.Kids[:nkids4]
+ pos = pos5
+ break
+ }
+ // sep+
+ // sep
+ if p, kid := _sepNode(parser, pos); kid == nil {
+ goto fail3
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ for {
+ nkids7 := len(node.Kids)
+ pos8 := pos
+ // sep
+ if p, kid := _sepNode(parser, pos); kid == nil {
+ goto fail9
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ continue
+ fail9:
+ node.Kids = node.Kids[:nkids7]
+ pos = pos8
+ break
+ }
+ goto ok0
+ fail3:
+ node.Kids = node.Kids[:nkids1]
+ pos = pos2
+ // h
+ if p, kid := _hNode(parser, pos); kid == nil {
+ goto fail10
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ goto ok0
+ fail10:
+ node.Kids = node.Kids[:nkids1]
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ node.Text = parser.text[start:pos]
+ parser.node[key] = node
+ return pos, node
+fail:
+ return -1, nil
+}
+
+func _HFail(parser *_Parser, start, errPos int) (int, *peg.Fail) {
+ if start > parser.lastFail {
+ return -1, &peg.Fail{}
+ }
+ dp := parser.deltaPos[start].H
+ de := parser.deltaErr[start].H
+ if start+int(de-1) < errPos {
+ if dp > 0 {
+ return start + int(dp-1), &peg.Fail{}
+ }
+ return -1, &peg.Fail{}
+ }
+ key := _key{start: start, name: "H"}
+ failure := parser.fail[key]
+ if dp < 0 && failure != nil {
+ return -1, failure
+ }
+ if dp > 0 && failure != nil {
+ return start + int(dp-1), failure
+ }
+ pos := start
+ failure = &peg.Fail{
+ Name: "H",
+ Pos: int(start),
+ }
+ // h+ sep+/h
+ {
+ pos2 := pos
+ // h+ sep+
+ // h+
+ // h
+ {
+ p, kid := _hFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail3
+ }
+ pos = p
+ }
+ for {
+ pos5 := pos
+ // h
+ {
+ p, kid := _hFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail6
+ }
+ pos = p
+ }
+ continue
+ fail6:
+ pos = pos5
+ break
+ }
+ // sep+
+ // sep
+ {
+ p, kid := _sepFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail3
+ }
+ pos = p
+ }
+ for {
+ pos8 := pos
+ // sep
+ {
+ p, kid := _sepFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail9
+ }
+ pos = p
+ }
+ continue
+ fail9:
+ pos = pos8
+ break
+ }
+ goto ok0
+ fail3:
+ pos = pos2
+ // h
+ {
+ p, kid := _hFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail10
+ }
+ pos = p
+ }
+ goto ok0
+ fail10:
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ parser.fail[key] = failure
+ return pos, failure
+fail:
+ parser.fail[key] = failure
+ return -1, failure
+}
diff --git a/h/parser.go b/h/parser.go
new file mode 100644
index 0000000..f061ec7
--- /dev/null
+++ b/h/parser.go
@@ -0,0 +1,42 @@
+package h
+
+//go:generate peggy -o h_gen.go h.peg
+
+import (
+ "fmt"
+
+ "github.com/eaburns/peggy/peg"
+ "within.website/x/jbo/namcu"
+)
+
+func (p *_Parser) Parse() (int, bool) {
+ pos, perr := _HAccepts(p, 0)
+ return perr, pos >= 0
+}
+
+func (p *_Parser) ErrorTree(minPos int) *peg.Fail {
+ p.fail = make(map[_key]*peg.Fail) // reset fail memo table
+ _, tree := _HFail(p, 0, minPos)
+ return tree
+}
+
+func (p *_Parser) ParseTree() *peg.Node {
+ _, tree := _HNode(p, 0)
+ return tree
+}
+
+// Parse parses h.
+// On success, the parseTree is returned.
+// On failure, both the word-level and the raw, morphological errors are returned.
+func Parse(text string) (*peg.Node, error) {
+ p := _NewParser(text)
+ if perr, ok := p.Parse(); !ok {
+ return nil, fmt.Errorf("h: gentoldra fi'o zvati zo %s", namcu.Lerfu(perr))
+ }
+
+ tree := p.ParseTree()
+ RemoveSpace(tree)
+ CollapseLists(tree)
+
+ return tree, nil
+}
diff --git a/h/simplify.go b/h/simplify.go
new file mode 100644
index 0000000..36013df
--- /dev/null
+++ b/h/simplify.go
@@ -0,0 +1,59 @@
+package h
+
+import (
+ "strings"
+
+ "github.com/eaburns/peggy/peg"
+)
+
+// RemoveSpace removes whitespace-only nodes.
+func RemoveSpace(n *peg.Node) { removeSpace(n) }
+
+func removeSpace(n *peg.Node) bool {
+ if whitespace(n.Text) {
+ return false
+ }
+ if len(n.Kids) == 0 {
+ return true
+ }
+ var kids []*peg.Node
+ for _, k := range n.Kids {
+ if removeSpace(k) {
+ kids = append(kids, k)
+ }
+ }
+ n.Kids = kids
+ return len(n.Kids) > 0
+}
+
+// SpaceChars is the string of all whitespace characters.
+const SpaceChars = "\x20"
+
+func whitespace(s string) bool {
+ for _, r := range s {
+ if !strings.ContainsRune(SpaceChars, r) {
+ return false
+ }
+ }
+ return true
+}
+
+// CollapseLists collapses chains of single-kid nodes.
+func CollapseLists(n *peg.Node) {
+ if collapseLists(n) == 1 {
+ n.Kids = n.Kids[0].Kids
+ }
+}
+
+func collapseLists(n *peg.Node) int {
+ var kids []*peg.Node
+ for _, k := range n.Kids {
+ if gk := collapseLists(k); gk == 1 {
+ kids = append(kids, k.Kids[0])
+ } else {
+ kids = append(kids, k)
+ }
+ }
+ n.Kids = kids
+ return len(n.Kids)
+}
diff --git a/jbo/namcu/namcu.go b/jbo/namcu/namcu.go
index 7742dde..1943c21 100644
--- a/jbo/namcu/namcu.go
+++ b/jbo/namcu/namcu.go
@@ -14,6 +14,10 @@ const (
Nine = "so"
)
+func Lerfu(i int) string {
+ return lojbanDigit(i)
+}
+
func lojbanDigit(i int) string {
switch i {
case 0: