add file trace to parser, refactor, fix inner type
This commit is contained in:
parent
bcf0978b68
commit
c8660a6471
10 changed files with 131 additions and 51 deletions
42
cli/main.go
42
cli/main.go
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
|
|
@ -21,24 +22,28 @@ type RunCmd struct {
|
||||||
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
|
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WorkingDirectoryResolver resolves imports relative to the working directory
|
// DirectoryResolver resolves imports from several directories
|
||||||
type WorkingDirectoryResolver struct {
|
type DirectoryResolver struct {
|
||||||
workingDirectory string
|
// directories to search through, in decreasing priority.
|
||||||
|
directories []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *WorkingDirectoryResolver) Resolve(path string) (string, error) {
|
func (r *DirectoryResolver) Resolve(path string) (string, error) {
|
||||||
pth := filepath.Join(r.workingDirectory, path)
|
for _, d := range r.directories {
|
||||||
|
pth := filepath.Join(d, path)
|
||||||
f, err := os.ReadFile(pth)
|
f, err := os.ReadFile(pth)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
return string(f), nil
|
return string(f), nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *WorkingDirectoryResolver) IsSame(a, b string) bool {
|
return "", errors.New("couldn't resolve import")
|
||||||
apath := filepath.Clean(filepath.Join(r.workingDirectory, a))
|
}
|
||||||
bpath := filepath.Clean(filepath.Join(r.workingDirectory, b))
|
|
||||||
|
func (r *DirectoryResolver) IsSame(a, b string) bool {
|
||||||
|
apath := filepath.Clean(a)
|
||||||
|
bpath := filepath.Clean(b)
|
||||||
|
|
||||||
return apath == bpath
|
return apath == bpath
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +62,7 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
|
||||||
src := string(f)
|
src := string(f)
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initialized lexer")
|
log.Println("Initializing lexer")
|
||||||
}
|
}
|
||||||
l := core.NewLexer(src)
|
l := core.NewLexer(src)
|
||||||
|
|
||||||
|
|
@ -78,7 +83,7 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
|
||||||
log.Printf("Lexed %d tokens", len(tokens))
|
log.Printf("Lexed %d tokens", len(tokens))
|
||||||
|
|
||||||
}
|
}
|
||||||
p := core.NewParser(src, tokens)
|
p := core.NewParser(src, []string{fpath}, tokens)
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initialized parser")
|
log.Println("Initialized parser")
|
||||||
|
|
@ -110,9 +115,14 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
|
||||||
log.Println("Setting imports resolver")
|
log.Println("Setting imports resolver")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lib := os.Getenv("ANGLAIS_PATH")
|
||||||
|
dirs := strings.Split(lib, ":")
|
||||||
|
|
||||||
dir, _ := path.Split(fpath)
|
dir, _ := path.Split(fpath)
|
||||||
c.SetImportsResolver(&WorkingDirectoryResolver{
|
c.SetImportsResolver(&DirectoryResolver{
|
||||||
|
append([]string{
|
||||||
dir,
|
dir,
|
||||||
|
}, dirs...),
|
||||||
})
|
})
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
|
|
@ -231,7 +241,7 @@ type ReplCmd struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *ReplCmd) Run(ctx *Context) error {
|
func (cmd *ReplCmd) Run(ctx *Context) error {
|
||||||
c := core.NewCompiler([]rune(""))
|
c := core.NewCompiler([]rune{})
|
||||||
vm := core.NewVM(core.NewChunk([]core.Bytecode{}, []core.Value{}), 256, 256)
|
vm := core.NewVM(core.NewChunk([]core.Bytecode{}, []core.Value{}), 256, 256)
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
@ -249,7 +259,7 @@ func (cmd *ReplCmd) Run(ctx *Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
p := core.NewParser(src, tokens)
|
p := core.NewParser(src, []string{}, tokens)
|
||||||
prog, err := p.Parse("REPL")
|
prog, err := p.Parse("REPL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var e core.FormatedError
|
var e core.FormatedError
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ func TestAll(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log("Initializing parser")
|
t.Log("Initializing parser")
|
||||||
p := NewParser(tc.src, tokens)
|
p := NewParser(tc.src, []string{}, tokens)
|
||||||
|
|
||||||
t.Log("Parsing tokens")
|
t.Log("Parsing tokens")
|
||||||
tree, err := p.Parse(tc.src)
|
tree, err := p.Parse(tc.src)
|
||||||
|
|
@ -173,7 +173,7 @@ func BenchmarkAll(b *testing.B) {
|
||||||
l := NewLexer(tc.src)
|
l := NewLexer(tc.src)
|
||||||
tokens, _ := l.Tokenize()
|
tokens, _ := l.Tokenize()
|
||||||
|
|
||||||
p := NewParser(tc.src, tokens)
|
p := NewParser(tc.src, []string{}, tokens)
|
||||||
tree, _ := p.Parse(tc.src)
|
tree, _ := p.Parse(tc.src)
|
||||||
|
|
||||||
c := NewCompiler([]rune(tc.src))
|
c := NewCompiler([]rune(tc.src))
|
||||||
|
|
|
||||||
|
|
@ -393,6 +393,18 @@ func (c *Compiler) compile(tree Node) error {
|
||||||
return c.error(fmt.Sprintf("wrong argument count: function of signature %s got %d, requires %d", f, len(n.args), len(f.In)), n)
|
return c.error(fmt.Sprintf("wrong argument count: function of signature %s got %d, requires %d", f, len(n.args), len(f.In)), n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var innerType TypeSignature
|
||||||
|
if a, ok := n.source.(*AccessNode); ok {
|
||||||
|
isig, err := c.deduceSignature(a.source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isig.Type() == TypeList {
|
||||||
|
innerType = isig.(*ListSignature).Contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i, arg := range n.args {
|
for i, arg := range n.args {
|
||||||
sig, err := c.deduceSignature(arg)
|
sig, err := c.deduceSignature(arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -400,7 +412,11 @@ func (c *Compiler) compile(tree Node) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that arg type is as required
|
// check that arg type is as required
|
||||||
if !f.In[i].Matches(sig) {
|
fin := f.In[i]
|
||||||
|
if fin.Type() == TypeInner && innerType != nil {
|
||||||
|
fin = innerType
|
||||||
|
}
|
||||||
|
if !fin.Matches(sig) {
|
||||||
return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg)
|
return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,7 +614,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
|
||||||
n := tree.(*ListNode)
|
n := tree.(*ListNode)
|
||||||
|
|
||||||
contents := n.content
|
contents := n.content
|
||||||
// check for contents type
|
// check for content type
|
||||||
for _, v := range n.items {
|
for _, v := range n.items {
|
||||||
sig, err := c.deduceSignature(v)
|
sig, err := c.deduceSignature(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -761,7 +777,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
|
||||||
|
|
||||||
if f.Out.Type() == TypeInner {
|
if f.Out.Type() == TypeInner {
|
||||||
if innerType == nil {
|
if innerType == nil {
|
||||||
return nil, c.error(fmt.Sprintf("function source (%s) has no inner type", sig), n.source)
|
return nil, c.error(fmt.Sprintf("function source (%s) has no inner type (@ output)", sig), n.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
return innerType, nil
|
return innerType, nil
|
||||||
|
|
@ -1204,7 +1220,7 @@ func (c *Compiler) error(msg string, causer Node) CompilerError {
|
||||||
msg,
|
msg,
|
||||||
causer,
|
causer,
|
||||||
c.source,
|
c.source,
|
||||||
c.fileStack.items[0:c.fileStack.Current],
|
c.fileStack.Slice(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1223,7 +1239,7 @@ func (c *Compiler) resolveImport(path string) error {
|
||||||
// stop recursive imports
|
// stop recursive imports
|
||||||
for i := c.fileStack.Current - 1; i >= 0; i-- {
|
for i := c.fileStack.Current - 1; i >= 0; i-- {
|
||||||
if c.resolver.IsSame(path, c.fileStack.items[i]) {
|
if c.resolver.IsSame(path, c.fileStack.items[i]) {
|
||||||
return errors.New("recursive imports")
|
return errors.New("recursive import")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1238,7 +1254,7 @@ func (c *Compiler) resolveImport(path string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := NewParser(src, tokens)
|
parser := NewParser(src, append(c.fileStack.Slice(), path), tokens)
|
||||||
p, err := parser.Parse(path)
|
p, err := parser.Parse(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ func (t TokenType) String() string {
|
||||||
return "hexadecimal"
|
return "hexadecimal"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "UNDEFINED TOKENTYPE STRING CONVERSION"
|
panic("UNDEFINED TOKENTYPE STRING CONVERSION")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Lexer struct {
|
type Lexer struct {
|
||||||
|
|
@ -320,9 +320,9 @@ func (l *Lexer) NextToken() (Token, error) {
|
||||||
return l.makeToken(TokenString), nil
|
return l.makeToken(TokenString), nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if unicode.IsLetter(c) || c == '_' {
|
if l.isAlpha(c) {
|
||||||
// assemble variable
|
// assemble variable
|
||||||
for l.isAlpha(l.peek()) {
|
for l.isAlphaNumeric(l.peek()) {
|
||||||
l.advance()
|
l.advance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,7 +435,11 @@ func (l *Lexer) accept(c rune) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Lexer) isAlpha(c rune) bool {
|
func (l *Lexer) isAlpha(c rune) bool {
|
||||||
return unicode.IsLetter(c) || unicode.IsDigit(c) || c == '_'
|
return unicode.IsLetter(c) || c == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) isAlphaNumeric(c rune) bool {
|
||||||
|
return l.isAlpha(c) || unicode.IsDigit(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Lexer) advance() {
|
func (l *Lexer) advance() {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ type ParsingError struct {
|
||||||
Description string
|
Description string
|
||||||
Causer *Token
|
Causer *Token
|
||||||
Source string
|
Source string
|
||||||
|
Trace []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ParsingError) Error() string {
|
func (p ParsingError) Error() string {
|
||||||
|
|
@ -26,7 +27,7 @@ func (p ParsingError) Error() string {
|
||||||
// Format Print a rich and informative error
|
// Format Print a rich and informative error
|
||||||
func (p ParsingError) Format() string {
|
func (p ParsingError) Format() string {
|
||||||
src := []rune(p.Source)
|
src := []rune(p.Source)
|
||||||
builder := strings.Builder{}
|
b := strings.Builder{}
|
||||||
|
|
||||||
lineNumber := 1
|
lineNumber := 1
|
||||||
lineBeginning := 0
|
lineBeginning := 0
|
||||||
|
|
@ -46,39 +47,46 @@ func (p ParsingError) Format() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptor := fmt.Sprintf("%d:%d", lineNumber, int(p.Causer.Start)-lineBeginning+1)
|
descriptor := fmt.Sprintf("%d:%d", lineNumber, int(p.Causer.Start)-lineBeginning+1)
|
||||||
builder.WriteString(p.Description)
|
b.WriteString(p.Description)
|
||||||
builder.WriteRune('\n')
|
b.WriteRune('\n')
|
||||||
|
|
||||||
builder.WriteString(descriptor)
|
b.WriteString(descriptor)
|
||||||
builder.WriteString(" | ")
|
b.WriteString(" | ")
|
||||||
builder.WriteString(string(src[lineBeginning:lineEnd]))
|
b.WriteString(string(src[lineBeginning:lineEnd]))
|
||||||
|
|
||||||
builder.WriteString("\n")
|
b.WriteString("\n")
|
||||||
builder.WriteString(strings.Repeat(" ", len(descriptor)))
|
b.WriteString(strings.Repeat(" ", len(descriptor)))
|
||||||
builder.WriteString(" ")
|
b.WriteString(" ")
|
||||||
for i := lineBeginning; i <= int(p.Causer.Start); i++ {
|
for i := lineBeginning; i <= int(p.Causer.Start); i++ {
|
||||||
builder.WriteRune(' ')
|
b.WriteRune(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < int(p.Causer.Length); i++ {
|
for i := 0; i < int(p.Causer.Length); i++ {
|
||||||
builder.WriteRune('^')
|
b.WriteRune('^')
|
||||||
}
|
}
|
||||||
builder.WriteRune('\n')
|
b.WriteRune('\n')
|
||||||
|
b.WriteRune('\n')
|
||||||
|
|
||||||
return builder.String()
|
for i := len(p.Trace) - 1; i >= 0; i-- {
|
||||||
|
b.WriteString(fmt.Sprintf("[%d] %s\n", i, p.Trace[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
source string
|
source string
|
||||||
|
trace []string
|
||||||
tokens []Token
|
tokens []Token
|
||||||
prev *Token
|
prev *Token
|
||||||
curr *Token
|
curr *Token
|
||||||
pos Pos
|
pos Pos
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParser(source string, tokens []Token) *Parser {
|
func NewParser(source string, trace []string, tokens []Token) *Parser {
|
||||||
return &Parser{
|
return &Parser{
|
||||||
source: source,
|
source: source,
|
||||||
|
trace: trace,
|
||||||
tokens: tokens,
|
tokens: tokens,
|
||||||
pos: 0,
|
pos: 0,
|
||||||
}
|
}
|
||||||
|
|
@ -121,6 +129,7 @@ func (p *Parser) Parse(path string) (*Program, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
imports = append(imports, p.prev.Lexeme[1:len(p.prev.Lexeme)-1])
|
imports = append(imports, p.prev.Lexeme[1:len(p.prev.Lexeme)-1])
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := p.block(true)
|
b, err := p.block(true)
|
||||||
|
|
@ -190,6 +199,7 @@ func (p *Parser) error(error string, causer *Token) error {
|
||||||
Description: error,
|
Description: error,
|
||||||
Causer: causer,
|
Causer: causer,
|
||||||
Source: p.source,
|
Source: p.source,
|
||||||
|
Trace: p.trace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
func TestNewParser(t *testing.T) {
|
func TestNewParser(t *testing.T) {
|
||||||
tokens := make([]Token, 0)
|
tokens := make([]Token, 0)
|
||||||
|
|
||||||
p := NewParser("", tokens)
|
p := NewParser("", []string{}, tokens)
|
||||||
|
|
||||||
if p == nil {
|
if p == nil {
|
||||||
t.Fatal("parser should not be nil")
|
t.Fatal("parser should not be nil")
|
||||||
|
|
@ -34,7 +34,7 @@ func TestNewParser(t *testing.T) {
|
||||||
func BenchmarkNewParser(b *testing.B) {
|
func BenchmarkNewParser(b *testing.B) {
|
||||||
tokens := make([]Token, 0)
|
tokens := make([]Token, 0)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = NewParser("", tokens)
|
_ = NewParser("", []string{}, tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -910,7 +910,7 @@ func TestParser_Parse(t *testing.T) {
|
||||||
|
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Logf("Initializing parser")
|
t.Logf("Initializing parser")
|
||||||
p := NewParser("", data.tokens)
|
p := NewParser("", []string{}, data.tokens)
|
||||||
|
|
||||||
t.Logf("Parsing main")
|
t.Logf("Parsing main")
|
||||||
tree, err := p.Parse("")
|
tree, err := p.Parse("")
|
||||||
|
|
@ -931,7 +931,7 @@ func BenchmarkParser_Parse(b *testing.B) {
|
||||||
for name, data := range tokenData {
|
for name, data := range tokenData {
|
||||||
b.Run(name, func(b *testing.B) {
|
b.Run(name, func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
p := NewParser("", data.tokens)
|
p := NewParser("", []string{}, data.tokens)
|
||||||
|
|
||||||
_, _ = p.Parse("")
|
_, _ = p.Parse("")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,8 @@ func (s *Stack[T]) check() {
|
||||||
panic("stack underflow")
|
panic("stack underflow")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Slice gets a slice of the current items in use
|
||||||
|
func (s *Stack[T]) Slice() []T {
|
||||||
|
return s.items[:s.Current]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -467,6 +467,31 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
"put": {
|
||||||
|
"put",
|
||||||
|
&FunctionSignature{
|
||||||
|
[]TypeSignature{
|
||||||
|
&NumberSignature{}, &InnerSignature{},
|
||||||
|
},
|
||||||
|
&NilSignature{},
|
||||||
|
},
|
||||||
|
func(_ *VM, this Value, args []Value) (Value, error) {
|
||||||
|
l := this.(*ListValue)
|
||||||
|
i := int(args[0].(*NumberValue).Number)
|
||||||
|
v := args[1]
|
||||||
|
|
||||||
|
// bounds check
|
||||||
|
if i < 0 || i >= len(l.Items) {
|
||||||
|
return nil, errors.New(fmt.Sprintf("index %x out of range", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Items[i] = v
|
||||||
|
|
||||||
|
return &NilValue{}, nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
"length": {
|
"length": {
|
||||||
"length",
|
"length",
|
||||||
&FunctionSignature{
|
&FunctionSignature{
|
||||||
|
|
|
||||||
12
test_all.sh
12
test_all.sh
|
|
@ -1,9 +1,19 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo '=== Building WASM lib ==='
|
||||||
|
cd wasm || exit 1
|
||||||
|
if ! GOOS=js GOARCH=wasm go build .; then
|
||||||
|
echo "=x= Had error building WASM lib =x="
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "=+= Successfully built WASM lib =+="
|
||||||
|
fi
|
||||||
|
cd ..
|
||||||
|
|
||||||
echo '=== Building CLI ==='
|
echo '=== Building CLI ==='
|
||||||
cd cli || exit 1
|
cd cli || exit 1
|
||||||
if ! go build .; then
|
if ! go build .; then
|
||||||
echo "=== Had error building CLI ==="
|
echo "=x= Had error building CLI =x="
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echo "=+= Successfully built CLI =+="
|
echo "=+= Successfully built CLI =+="
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build wasm && go1.23
|
//go:build wasm && go1.24
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ func run(_ js.Value, args []js.Value) interface{} {
|
||||||
|
|
||||||
log.Printf("got tokens: %v", tokens)
|
log.Printf("got tokens: %v", tokens)
|
||||||
|
|
||||||
parser := core.NewParser(source, tokens)
|
parser := core.NewParser(source, []string{}, tokens)
|
||||||
|
|
||||||
tree, err := parser.Parse(source)
|
tree, err := parser.Parse(source)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue