From e3e8ebbcf82f221d026385c5a85d0afde62b3e3d Mon Sep 17 00:00:00 2001 From: Xe Date: Fri, 30 Dec 2022 10:13:56 -0500 Subject: add old hlang draft Signed-off-by: Xe --- cmd/hlang/Dockerfile | 21 ++ cmd/hlang/LICENSE | 12 + cmd/hlang/README.md | 3 + cmd/hlang/compile.go | 105 +++++++ cmd/hlang/h/h.peg | 12 + cmd/hlang/h/h_gen.go | 789 ++++++++++++++++++++++++++++++++++++++++++++++++ cmd/hlang/h/parser.go | 42 +++ cmd/hlang/h/simplify.go | 59 ++++ cmd/hlang/http.go | 442 +++++++++++++++++++++++++++ cmd/hlang/main.go | 134 ++++++++ cmd/hlang/run.go | 73 +++++ cmd/hlang/version.go | 13 + 12 files changed, 1705 insertions(+) create mode 100644 cmd/hlang/Dockerfile create mode 100644 cmd/hlang/LICENSE create mode 100644 cmd/hlang/README.md create mode 100644 cmd/hlang/compile.go create mode 100644 cmd/hlang/h/h.peg create mode 100644 cmd/hlang/h/h_gen.go create mode 100644 cmd/hlang/h/parser.go create mode 100644 cmd/hlang/h/simplify.go create mode 100644 cmd/hlang/http.go create mode 100644 cmd/hlang/main.go create mode 100644 cmd/hlang/run.go create mode 100644 cmd/hlang/version.go (limited to 'cmd/hlang') 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 + +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 = ` + + The h Programming Language + + + + +
+ + +

The h Programming Language

+ +

A simple, fast, open-source, complete and safe language for developing modern software for the web

+ +
+ +

Example Program

+ +
h
+ +

Outputs:

+ +
h
+ +
+ +

Fast Compilation

+ +

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.

+ +
+ +

Safety

+ +

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.

+ +
    +
  • No null
  • +
  • Completely deterministic behavior
  • +
  • No mutable state
  • +
  • No persistence
  • +
  • All functions are pure functions
  • +
  • No sandboxing required
  • +
+ +
+ +

Zero* Dependencies

+ +

h generates WebAssembly, so every binary produced by the compiler is completely dependency free save a single system call: h.h. This allows for modern, future-proof code that will work on all platforms.

+ +
+ +

Simple

+ +

h has a simple grammar that gzips to 117 bytes. Creating a runtime environment for h is so trivial just about anyone can do it.

+ +
+ +

Platform Support

+ +

h supports the following platforms:

+ +
    +
  • Google Chrome
  • +
  • Electron
  • +
  • Chromium Embedded Framework
  • +
  • Microsoft Edge
  • +
  • Olin
  • +
+ +
+ +

International Out of the Box

+ +

h supports multiple written and spoken languages with true contextual awareness. It not only supports the Latin h as input, it also accepts the Lojbanic ' as well. This allows for full 100% internationalization into Lojban should your project needs require it.

+ +
+ +

Testimonials

+ +

Not convinced? Take the word of people we probably didn't pay for their opinion.

+ +
    +
  • I don't see the point of this.
  • +
  • This solves all my problems. All of them. Just not in the way I expected it to.
  • +
  • Yes.
  • +
  • Perfect.
  • +
  • h is the backbone of my startup.
  • +
+ +
+ +

Open-Source

+ +

The h compiler and default runtime are open-source free software sent out into the Public Domain. You can use h for any purpose at all with no limitations or restrictions.

+ +
+ + +
+ +` + +const docsTemplate = ` + + The h Programming Language - Docs + + + + +
+ + +

Documentation

+ +

Coming soon...

+ + +
+ + +
+ +` + +const faqTemplate = ` + + The h Programming Language - FAQ + + + + +
+ + +

Frequently Asked Questions

+ +

What are the instructions of h?

+ +

h supports the following instructions:

+
    +
  • h
  • +
  • '
  • +
+ +

All valid h instructions must be separated by a space (\0x20 or the spacebar on your computer). No other forms of whitespace are permitted. Any other characters will render your program gentoldra.

+ +

How do I install and use h?

+ +

With any computer running Go 1.11 or higher:

+ +
go get -u -v within.website/x/cmd/h
+ + Usage is simple: + +
Usage of h:
+  -config string
+        configuration file, if set (see flagconfyg(4))
+  -koan
+        if true, print the h koan and then exit
+  -license
+        show software licenses?
+  -manpage
+        generate a manpage template?
+  -max-playground-bytes int
+        how many bytes of data should users be allowed to
+        post to the playground? (default 75)
+  -o string
+        if specified, write the webassembly binary created
+        by -p here
+  -o-wat string
+        if specified, write the uncompiled webassembly
+        created by -p here
+  -p string
+        h program to compile/run
+  -port string
+        HTTP port to listen on
+  -v    if true, print the version of h and then exit
+ +

What version is h?

+ +

Version 1.0, this will hopefully be the only release.

+ +

What is the h koan?

+ +

And Jesus said unto the theologians, "Who do you say that I am?"

+ +

They replied: "You are the eschatological manifestation of the ground of our being, the kerygma of which we find the ultimate meaning in our interpersonal relationships."

+ +

And Jesus said "...What?"

+ +

Some time passed and one of them spoke "h".

+ +

Jesus was enlightened.

+ +

Why?

+ +

That's a good question. The following blogposts may help you understand this more:

+ + + +

Who wrote h?

+ +

Within

+ +
+ + +
+ +` + +const playgroundTemplate = ` + + The h Programming Language - Playground + + + + +
+ + +

Playground

+ +

Unfortunately, Javascript is required to use this page, sorry.

+ +

Program

+ + + + +

+ +

Output

+ +
+ +

WebAssembly Text Format

+ +
+ +

Gas used:

+

Execution time (nanoseconds):

+ +

AST

+ +
+ + + +
+ + +
+ +` diff --git a/cmd/hlang/main.go b/cmd/hlang/main.go new file mode 100644 index 0000000..faa2779 --- /dev/null +++ b/cmd/hlang/main.go @@ -0,0 +1,134 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + + "within.website/x/internal" +) + +var _ = func() error { log.SetFlags(log.LstdFlags | log.Llongfile); return nil }() + +var ( + program = flag.String("p", "", "h program to compile/run") + outFname = flag.String("o", "", "if specified, write the webassembly binary created by -p here") + watFname = flag.String("o-wat", "", "if specified, write the uncompiled webassembly created by -p here") + port = flag.String("port", "", "HTTP port to listen on") + sockpath = flag.String("sockpath", "", "Unix domain socket to listen on") + writeTao = flag.Bool("koan", false, "if true, print the h koan and then exit") + writeVersion = flag.Bool("v", false, "if true, print the version of h and then exit") +) + +const koan = `And Jesus said unto the theologians, "Who do you say that I am?" + +They replied: "You are the eschatological manifestation of the ground of our +being, the kerygma of which we find the ultimate meaning in our interpersonal +relationships." + +And Jesus said "...What?" + +Some time passed and one of them spoke "h". + +Jesus was enlightened.` + +func tao() { + fmt.Println(koan) + os.Exit(0) +} + +func oneOff() error { + log.Println("compiling...") + comp, err := compile(*program) + if err != nil { + return err + } + + log.Println("running...") + er, err := run(comp.Binary) + if err != nil { + return err + } + + log.Println("success!") + + log.Printf("gas used:\t%d", er.GasUsed) + log.Printf("exec time:\t%s", er.ExecTime) + log.Println("output:") + fmt.Print(er.Output) + + if *outFname != "" { + err := ioutil.WriteFile(*outFname, comp.Binary, 0666) + if err != nil { + return err + } + + log.Printf("wrote %d bytes to %s", len(comp.Binary), *outFname) + } + + if *watFname != "" { + err := ioutil.WriteFile(*watFname, []byte(comp.WebAssemblyText), 0666) + if err != nil { + return err + } + + log.Printf("write %d bytes of source to %s", len(comp.WebAssemblyText), *watFname) + } + + return nil +} + +func main() { + internal.HandleStartup() + + if *writeVersion { + dumpVersion() + } + + if *writeTao { + tao() + } + + if *program != "" { + err := oneOff() + if err != nil { + panic(err) + } + + return + } + + if *port != "" || *sockpath != "" { + err := doHTTP() + if err != nil { + panic(err) + } + + return + } +} + +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 39)) + {{ 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 "h" (func $h_main)) +)` diff --git a/cmd/hlang/run.go b/cmd/hlang/run.go new file mode 100644 index 0000000..fb55bb7 --- /dev/null +++ b/cmd/hlang/run.go @@ -0,0 +1,73 @@ +package main + +import ( + "errors" + "time" + + "github.com/perlin-network/life/compiler" + "github.com/perlin-network/life/exec" +) + +type Process struct { + 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 `json:"out"` + GasUsed uint64 `json:"gas"` + ExecTime time.Duration `json:"exec_duration"` +} + +func run(bin []byte) (*ExecResult, error) { + p := &Process{} + + var cfg exec.VMConfig + gp := &compiler.SimpleGasPolicy{GasPerInstruction: 1} + vm, err := exec.NewVirtualMachine(bin, cfg, p, gp) + if err != nil { + return nil, err + } + + mainFunc, ok := vm.GetFunctionExport("h") + if !ok { + return nil, errors.New("impossible state: no h 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/cmd/hlang/version.go b/cmd/hlang/version.go new file mode 100644 index 0000000..f321497 --- /dev/null +++ b/cmd/hlang/version.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "os" +) + +const version = "1.0.0" + +func dumpVersion() { + fmt.Println("h programming language version", version) + os.Exit(0) +} -- cgit v1.2.3