aboutsummaryrefslogtreecommitdiff
path: root/cmd/hlang
diff options
context:
space:
mode:
authorXe <me@christine.website>2022-12-30 10:13:56 -0500
committerXe <me@christine.website>2022-12-30 10:13:56 -0500
commite3e8ebbcf82f221d026385c5a85d0afde62b3e3d (patch)
tree1ab8a5811db5a64ed14d3112e47730d0fe97325a /cmd/hlang
parent9bdbd3156177640afaf18ca2c4ad783138004e4a (diff)
downloadx-e3e8ebbcf82f221d026385c5a85d0afde62b3e3d.tar.xz
x-e3e8ebbcf82f221d026385c5a85d0afde62b3e3d.zip
add old hlang draft
Signed-off-by: Xe <me@christine.website>
Diffstat (limited to 'cmd/hlang')
-rw-r--r--cmd/hlang/Dockerfile21
-rw-r--r--cmd/hlang/LICENSE12
-rw-r--r--cmd/hlang/README.md3
-rw-r--r--cmd/hlang/compile.go105
-rw-r--r--cmd/hlang/h/h.peg12
-rw-r--r--cmd/hlang/h/h_gen.go789
-rw-r--r--cmd/hlang/h/parser.go42
-rw-r--r--cmd/hlang/h/simplify.go59
-rw-r--r--cmd/hlang/http.go442
-rw-r--r--cmd/hlang/main.go134
-rw-r--r--cmd/hlang/run.go73
-rw-r--r--cmd/hlang/version.go13
12 files changed, 1705 insertions, 0 deletions
diff --git a/cmd/hlang/Dockerfile b/cmd/hlang/Dockerfile
new file mode 100644
index 0000000..86267a9
--- /dev/null
+++ b/cmd/hlang/Dockerfile
@@ -0,0 +1,21 @@
+FROM xena/go:1.13.3 AS build
+WORKDIR /hlang
+COPY . .
+RUN GOBIN=/usr/local/bin go install .
+
+FROM xena/alpine AS wasm
+WORKDIR /wabt
+RUN apk --no-cache add build-base cmake git python \
+ && git clone --recursive https://github.com/WebAssembly/wabt /wabt \
+ && mkdir build \
+ && cd build \
+ && cmake .. \
+ && make && make install
+RUN ldd $(which wat2wasm)
+
+FROM xena/alpine
+COPY --from=wasm /usr/local/bin/wat2wasm /usr/local/bin/wat2wasm
+COPY --from=build /usr/local/bin/hlang /usr/local/bin/hlang
+ENV PORT 5000
+RUN apk --no-cache add libstdc++
+CMD /usr/local/bin/hlang
diff --git a/cmd/hlang/LICENSE b/cmd/hlang/LICENSE
new file mode 100644
index 0000000..87df039
--- /dev/null
+++ b/cmd/hlang/LICENSE
@@ -0,0 +1,12 @@
+Copyright (c) 2019 Christine Dodrill <me@christine.website>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file
diff --git a/cmd/hlang/README.md b/cmd/hlang/README.md
new file mode 100644
index 0000000..843d30a
--- /dev/null
+++ b/cmd/hlang/README.md
@@ -0,0 +1,3 @@
+# h
+
+https://h.christine.website
diff --git a/cmd/hlang/compile.go b/cmd/hlang/compile.go
new file mode 100644
index 0000000..46e7239
--- /dev/null
+++ b/cmd/hlang/compile.go
@@ -0,0 +1,105 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "text/template"
+ "time"
+
+ "github.com/eaburns/peggy/peg"
+ "within.website/x/cmd/hlang/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"`
+ AST string `json:"ast"`
+}
+
+func compile(source string) (*CompiledProgram, error) {
+ tree, err := h.Parse(source)
+ if err != nil {
+ return nil, err
+ }
+
+ var sb strings.Builder
+ err = peg.PrettyWrite(&sb, tree)
+
+ result := CompiledProgram{
+ Source: source,
+ AST: sb.String(),
+ }
+
+ 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/hlang/h/h.peg b/cmd/hlang/h/h.peg
new file mode 100644
index 0000000..542a887
--- /dev/null
+++ b/cmd/hlang/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/cmd/hlang/h/h_gen.go b/cmd/hlang/h/h_gen.go
new file mode 100644
index 0000000..365b630
--- /dev/null
+++ b/cmd/hlang/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/cmd/hlang/h/parser.go b/cmd/hlang/h/parser.go
new file mode 100644
index 0000000..4daa833
--- /dev/null
+++ b/cmd/hlang/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 fe li %s", namcu.Lerfu(perr))
+ }
+
+ tree := p.ParseTree()
+ RemoveSpace(tree)
+ CollapseLists(tree)
+
+ return tree, nil
+}
diff --git a/cmd/hlang/h/simplify.go b/cmd/hlang/h/simplify.go
new file mode 100644
index 0000000..36013df
--- /dev/null
+++ b/cmd/hlang/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/cmd/hlang/http.go b/cmd/hlang/http.go
new file mode 100644
index 0000000..f096e3f
--- /dev/null
+++ b/cmd/hlang/http.go
@@ -0,0 +1,442 @@
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "os"
+
+ "github.com/rs/cors"
+ "within.website/ln/ex"
+)
+
+var (
+ maxBytes = flag.Int64("max-playground-bytes", 75, "how many bytes of data should users be allowed to post to the playground?")
+)
+
+func doHTTP() error {
+ http.Handle("/", doTemplate(indexTemplate))
+ http.Handle("/docs", doTemplate(docsTemplate))
+ http.Handle("/faq", doTemplate(faqTemplate))
+ http.Handle("/play", doTemplate(playgroundTemplate))
+ http.HandleFunc("/api/playground", runPlayground)
+
+ srv := &http.Server{
+ Addr: ":" + *port,
+ Handler: ex.HTTPLog(cors.Default().Handler(http.DefaultServeMux)),
+ }
+
+ if *sockpath != "" {
+ os.RemoveAll(*sockpath)
+
+ l, err := net.Listen("unix", *sockpath)
+ if err != nil {
+ return fmt.Errorf("can't listen on %s: %v", *sockpath, err)
+ }
+ defer l.Close()
+
+ return srv.Serve(l)
+ } else {
+ return srv.ListenAndServe()
+ }
+}
+
+func httpError(w http.ResponseWriter, err error, code int) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ json.NewEncoder(w).Encode(struct {
+ Error string `json:"err"`
+ }{
+ Error: err.Error(),
+ })
+}
+
+func runPlayground(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.NotFound(w, r)
+ return
+ }
+
+ rc := http.MaxBytesReader(w, r.Body, *maxBytes)
+ defer rc.Close()
+
+ data, err := ioutil.ReadAll(rc)
+ if err != nil {
+ httpError(w, err, http.StatusBadGateway)
+ return
+ }
+
+ comp, err := compile(string(data))
+ if err != nil {
+ httpError(w, fmt.Errorf("compliation error: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ er, err := run(comp.Binary)
+ if err != nil {
+ httpError(w, fmt.Errorf("runtime error: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(struct {
+ Program *CompiledProgram `json:"prog"`
+ Results *ExecResult `json:"res"`
+ }{
+ Program: comp,
+ Results: er,
+ })
+}
+
+func doTemplate(body string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+ fmt.Fprintln(w, body)
+ })
+}
+
+const indexTemplate = `<html>
+ <head>
+ <title>The h Programming Language</title>
+ <link rel="stylesheet" href="https://within.website/static/gruvbox.css">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ </head>
+ <body>
+ <main>
+ <nav>
+ <a href="/">The h Programming Language</a> -
+ <a href="/docs">Docs</a> -
+ <a href="/play">Playground</a> -
+ <a href="/faq">FAQ</a>
+ </nav>
+
+ <h1>The h Programming Language</h1>
+
+ <p><big>A simple, fast, open-source, complete and safe language for developing modern software for the web</big></p>
+
+ <hr />
+
+ <h2>Example Program</h2>
+
+ <code><pre>h</pre></code>
+
+ <p>Outputs:</p>
+
+ <code><pre>h</pre></code>
+
+ <hr />
+
+ <h2>Fast Compilation</h2>
+
+ <p>h compiles hundreds of characters of source per second. I didn't really test how fast it is, but when I was testing it the speed was fast enough that I didn't care to profile it.</p>
+
+ <hr />
+
+ <h2>Safety</h2>
+
+ <p>h is completely memory safe with no garbage collector or heap allocations. It does not allow memory leaks to happen, nor do any programs in h have the possibility to allocate memory.</p>
+
+ <ul>
+ <li>No null</li>
+ <li>Completely deterministic behavior</li>
+ <li>No mutable state</li>
+ <li>No persistence</li>
+ <li>All funct