Skip to content

Commit ab3f63a

Browse files
committed
go: goeval A new codegen for the Go language
Just like the existing `gen_go`, this new generator outputs everything needed to parse an input string into a single file. In fact, it generates a parser with almost exactly the same API as the existing mechanism. The exception is the method `Parser.SetCaptureSpaces`, which is currently not supported by the evaluator. This change also uncommented the Grammar AST enrichment steps in the `vm_test.go` file to do exactly what the `cmd/langlang/main.go` file is doing. At some point all that stuff is going to be moved into a nice API that's going to hide that away from callers. While outputting go code using `literalSanitizer`, it ended up failing with the replacement of single quotes because apparently go doesn't have an escape sequence for them.
1 parent f025cbc commit ab3f63a

File tree

4 files changed

+183
-19
lines changed

4 files changed

+183
-19
lines changed

go/cmd/langlang/main.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,6 @@ func main() {
134134
// If it's interactive, it will open a lil REPL shell
135135

136136
if *a.interactive {
137-
fmt.Println(ast.HighlightPrettyString())
138-
fmt.Println(asm.HighlightPrettyString())
139137
code := langlang.Encode(asm)
140138

141139
for {
@@ -165,9 +163,6 @@ func main() {
165163
// if there's an input path, just run the match right away
166164

167165
if *a.inputPath != "" {
168-
fmt.Println(ast.HighlightPrettyString())
169-
fmt.Println(asm.HighlightPrettyString())
170-
171166
text, err := os.ReadFile(*a.inputPath)
172167
if err != nil {
173168
log.Fatalf("Can't open input file: %s", err.Error())
@@ -195,7 +190,11 @@ func main() {
195190
PackageName: *a.goOptPackage,
196191
RemoveLib: *a.goOptRemoveLib,
197192
})
198-
193+
case "goeval":
194+
outputData, err = langlang.GenGoEval(asm, langlang.GenGoOptions{
195+
PackageName: *a.goOptPackage,
196+
RemoveLib: *a.goOptRemoveLib,
197+
})
199198
// case "python":
200199
// outputData, err = langlang.GenParserPython(ast)
201200
default:

go/gen_go_eval.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package langlang
2+
3+
import (
4+
"bytes"
5+
"embed"
6+
"fmt"
7+
"text/template"
8+
)
9+
10+
//go:embed vm.go vm_stack.go tree_printer.go errors.go value.go
11+
var goEvalContent embed.FS
12+
13+
func GenGoEval(asm *Program, opt GenGoOptions) (string, error) {
14+
g := newGoEvalEmitter(opt)
15+
g.writePrelude()
16+
g.writeParserProgram(Encode(asm))
17+
g.writeParserStruct()
18+
g.writeParserConstructor()
19+
g.writeParserMethods(asm)
20+
g.writeDeps()
21+
return g.output()
22+
}
23+
24+
type goEvalEmitter struct {
25+
options GenGoOptions
26+
parser *outputWriter
27+
}
28+
29+
func newGoEvalEmitter(opt GenGoOptions) *goEvalEmitter {
30+
return &goEvalEmitter{
31+
options: opt,
32+
parser: newOutputWriter("\t"),
33+
}
34+
}
35+
36+
func (g *goEvalEmitter) writePrelude() {
37+
g.parser.write("package ")
38+
g.parser.write(g.options.PackageName)
39+
g.parser.writel("\n")
40+
41+
g.parser.write("import (\n")
42+
g.parser.indent()
43+
g.parser.writeil(`"encoding/binary"`)
44+
g.parser.writeil(`"fmt"`)
45+
g.parser.writeil(`"strconv"`)
46+
g.parser.writeil(`"strings"`)
47+
48+
if !g.options.RemoveLib {
49+
g.parser.writeil(`"io"`)
50+
}
51+
52+
g.parser.unindent()
53+
g.parser.writel(")\n")
54+
}
55+
56+
func (g *goEvalEmitter) writeParserProgram(bt *Bytecode) {
57+
g.parser.writel("var parserProgram = &Bytecode{")
58+
g.parser.indent()
59+
60+
g.parser.writeil("code: []byte{")
61+
g.parser.indent()
62+
g.parser.writei("")
63+
for _, byte := range bt.code {
64+
g.parser.write(fmt.Sprintf("%d, ", byte))
65+
}
66+
g.parser.writel("")
67+
g.parser.unindent()
68+
g.parser.writeil("},")
69+
70+
g.parser.writeil("strs: []string{")
71+
g.parser.indent()
72+
g.parser.writei("")
73+
for _, s := range bt.strs {
74+
g.parser.write(fmt.Sprintf(`"%s", `, escapeLiteral(s)))
75+
}
76+
g.parser.writel("")
77+
g.parser.unindent()
78+
g.parser.writeil("},")
79+
80+
g.parser.writeil("rxps: map[int]int{")
81+
g.parser.indent()
82+
for k, v := range bt.rxps {
83+
g.parser.write(fmt.Sprintf("%d: %d,", k, v))
84+
}
85+
g.parser.unindent()
86+
g.parser.writeil("},")
87+
88+
g.parser.unindent()
89+
g.parser.writel("}")
90+
}
91+
92+
func (g *goEvalEmitter) writeParserStruct() {
93+
g.parser.writel("type Parser struct{")
94+
g.parser.indent()
95+
g.parser.writeil("input string")
96+
g.parser.writeil("errLabels map[string]string")
97+
g.parser.unindent()
98+
g.parser.writel("}")
99+
}
100+
101+
func (g *goEvalEmitter) writeParserConstructor() {
102+
g.parser.writel("func NewParser() *Parser { return &Parser{} }")
103+
}
104+
105+
func (g *goEvalEmitter) writeParserMethods(asm *Program) {
106+
var (
107+
cursor = 0
108+
addrmap = make(map[int]int, len(asm.identifiers))
109+
)
110+
for i, instruction := range asm.code {
111+
switch instruction.(type) {
112+
case ILabel:
113+
addrmap[i] = cursor
114+
default:
115+
cursor += instruction.SizeInBytes()
116+
}
117+
}
118+
for addr, strID := range asm.identifiers {
119+
name := asm.strings[strID]
120+
g.parser.write(fmt.Sprintf("func (p *Parser) Parse%s() (Value, error) { ", name))
121+
g.parser.write(fmt.Sprintf("return p.parseFn(%d)", addrmap[addr]))
122+
g.parser.writel(" }")
123+
}
124+
g.parser.writel("func (p *Parser) Parser() (Value, error) { return p.parseFn(5) }")
125+
g.parser.writel("func (p *Parser) SetInput(input string) { p.input = input }")
126+
g.parser.writel("func (p *Parser) SetLabelMessages(el map[string]string) { p.errLabels = el }")
127+
g.parser.writel("func (p *Parser) parseFn(addr uint16) (Value, error) {")
128+
g.parser.indent()
129+
g.parser.writeil("writeU16(parserProgram.code[1:], addr)")
130+
g.parser.writeil("vm := newVirtualMachine(parserProgram, p.errLabels)")
131+
g.parser.writeil("val, _, err := vm.Match(strings.NewReader(p.input))")
132+
g.parser.writeil("return val, err")
133+
g.parser.unindent()
134+
g.parser.writel("}")
135+
}
136+
137+
func (g *goEvalEmitter) writeDeps() {
138+
if g.options.RemoveLib {
139+
return
140+
}
141+
for _, file := range []string{
142+
"value.go", "tree_printer.go", "errors.go", "vm_stack.go", "vm.go",
143+
} {
144+
s, err := cleanGoModule(goEvalContent, file)
145+
if err != nil {
146+
panic(err.Error())
147+
}
148+
g.parser.write(s)
149+
}
150+
}
151+
152+
func (g *goEvalEmitter) output() (string, error) {
153+
parserTmpl, err := template.New("parser").Parse(g.parser.buffer.String())
154+
if err != nil {
155+
return "", err
156+
}
157+
var output bytes.Buffer
158+
vv := tmplRenderOpts{
159+
PackageName: g.options.PackageName,
160+
}
161+
if err = parserTmpl.Execute(&output, vv); err != nil {
162+
return "", err
163+
}
164+
return output.String(), nil
165+
}

go/tree_printer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func (tp *treePrinter[T]) pwrite(s string) {
5656

5757
var literalSanitizer = strings.NewReplacer(
5858
`"`, `\"`,
59-
`'`, `\'`,
59+
// `'`, `\'`,
6060
`\`, `\\`,
6161
string('\n'), `\n`,
6262
string('\r'), `\r`,

go/vm_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package langlang
22

33
import (
4-
"fmt"
4+
// "fmt"
55
"strings"
66
"testing"
77

@@ -312,28 +312,28 @@ func exec(expr, input string, optimize int) (Value, int, error) {
312312
if err != nil {
313313
panic(err)
314314
}
315-
// ast, err = InjectWhitespaces(ast)
316-
// if err != nil {
317-
// panic(err)
318-
// }
319-
// ast, err = AddBuiltins(ast)
320-
// if err != nil {
321-
// panic(err)
322-
// }
315+
ast, err = InjectWhitespaces(ast)
316+
if err != nil {
317+
panic(err)
318+
}
319+
ast, err = AddBuiltins(ast)
320+
if err != nil {
321+
panic(err)
322+
}
323323
ast, err = AddCaptures(ast)
324324
if err != nil {
325325
panic(err)
326326
}
327-
fmt.Printf("ast\n%s\n", ast.HighlightPrettyString())
327+
// fmt.Printf("ast\n%s\n", ast.HighlightPrettyString())
328328
asm, err := Compile(ast, CompilerConfig{Optimize: optimize})
329329
if err != nil {
330330
panic(err)
331331
}
332-
fmt.Printf("asm\n%s\n", asm.HighlightPrettyString())
332+
// fmt.Printf("asm\n%s\n", asm.HighlightPrettyString())
333333

334334
code := Encode(asm)
335335

336-
fmt.Printf("code\n%#v\n", code.code)
336+
// fmt.Printf("code\n%#v\n", code.code)
337337

338338
return code.Match(strings.NewReader(input))
339339
}

0 commit comments

Comments
 (0)