add file trace to parser, refactor, fix inner type

This commit is contained in:
Neemek 2025-09-30 14:09:05 +02:00
parent bcf0978b68
commit c8660a6471
Signed by: neemek
GPG key ID: 84FFE4D7D40AB25E
10 changed files with 131 additions and 51 deletions

View file

@ -9,6 +9,7 @@ import (
"os"
"path"
"path/filepath"
"strings"
)
type Context struct {
@ -21,24 +22,28 @@ type RunCmd struct {
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
}
// WorkingDirectoryResolver resolves imports relative to the working directory
type WorkingDirectoryResolver struct {
workingDirectory string
// DirectoryResolver resolves imports from several directories
type DirectoryResolver struct {
// directories to search through, in decreasing priority.
directories []string
}
func (r *WorkingDirectoryResolver) Resolve(path string) (string, error) {
pth := filepath.Join(r.workingDirectory, path)
f, err := os.ReadFile(pth)
if err != nil {
return "", err
func (r *DirectoryResolver) Resolve(path string) (string, error) {
for _, d := range r.directories {
pth := filepath.Join(d, path)
f, err := os.ReadFile(pth)
if err == nil {
return string(f), nil
}
}
return string(f), nil
return "", errors.New("couldn't resolve import")
}
func (r *WorkingDirectoryResolver) IsSame(a, b string) bool {
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
}
@ -57,7 +62,7 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
src := string(f)
if ctx.Debug {
log.Println("Initialized lexer")
log.Println("Initializing lexer")
}
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))
}
p := core.NewParser(src, tokens)
p := core.NewParser(src, []string{fpath}, tokens)
if ctx.Debug {
log.Println("Initialized parser")
@ -110,9 +115,14 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
log.Println("Setting imports resolver")
}
lib := os.Getenv("ANGLAIS_PATH")
dirs := strings.Split(lib, ":")
dir, _ := path.Split(fpath)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
c.SetImportsResolver(&DirectoryResolver{
append([]string{
dir,
}, dirs...),
})
if ctx.Debug {
@ -231,7 +241,7 @@ type ReplCmd struct {
}
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)
reader := bufio.NewReader(os.Stdin)
@ -249,7 +259,7 @@ func (cmd *ReplCmd) Run(ctx *Context) error {
continue
}
p := core.NewParser(src, tokens)
p := core.NewParser(src, []string{}, tokens)
prog, err := p.Parse("REPL")
if err != nil {
var e core.FormatedError

View file

@ -130,7 +130,7 @@ func TestAll(t *testing.T) {
}
t.Log("Initializing parser")
p := NewParser(tc.src, tokens)
p := NewParser(tc.src, []string{}, tokens)
t.Log("Parsing tokens")
tree, err := p.Parse(tc.src)
@ -173,7 +173,7 @@ func BenchmarkAll(b *testing.B) {
l := NewLexer(tc.src)
tokens, _ := l.Tokenize()
p := NewParser(tc.src, tokens)
p := NewParser(tc.src, []string{}, tokens)
tree, _ := p.Parse(tc.src)
c := NewCompiler([]rune(tc.src))

View file

@ -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)
}
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 {
sig, err := c.deduceSignature(arg)
if err != nil {
@ -400,7 +412,11 @@ func (c *Compiler) compile(tree Node) error {
}
// 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)
}
@ -598,7 +614,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
n := tree.(*ListNode)
contents := n.content
// check for contents type
// check for content type
for _, v := range n.items {
sig, err := c.deduceSignature(v)
if err != nil {
@ -761,7 +777,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
if f.Out.Type() == TypeInner {
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
@ -1204,7 +1220,7 @@ func (c *Compiler) error(msg string, causer Node) CompilerError {
msg,
causer,
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
for i := c.fileStack.Current - 1; i >= 0; 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
}
parser := NewParser(src, tokens)
parser := NewParser(src, append(c.fileStack.Slice(), path), tokens)
p, err := parser.Parse(path)
if err != nil {
return err

View file

@ -164,7 +164,7 @@ func (t TokenType) String() string {
return "hexadecimal"
}
return "UNDEFINED TOKENTYPE STRING CONVERSION"
panic("UNDEFINED TOKENTYPE STRING CONVERSION")
}
type Lexer struct {
@ -320,9 +320,9 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenString), nil
default:
if unicode.IsLetter(c) || c == '_' {
if l.isAlpha(c) {
// assemble variable
for l.isAlpha(l.peek()) {
for l.isAlphaNumeric(l.peek()) {
l.advance()
}
@ -435,7 +435,11 @@ func (l *Lexer) accept(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() {

View file

@ -17,6 +17,7 @@ type ParsingError struct {
Description string
Causer *Token
Source string
Trace []string
}
func (p ParsingError) Error() string {
@ -26,7 +27,7 @@ func (p ParsingError) Error() string {
// Format Print a rich and informative error
func (p ParsingError) Format() string {
src := []rune(p.Source)
builder := strings.Builder{}
b := strings.Builder{}
lineNumber := 1
lineBeginning := 0
@ -46,39 +47,46 @@ func (p ParsingError) Format() string {
}
descriptor := fmt.Sprintf("%d:%d", lineNumber, int(p.Causer.Start)-lineBeginning+1)
builder.WriteString(p.Description)
builder.WriteRune('\n')
b.WriteString(p.Description)
b.WriteRune('\n')
builder.WriteString(descriptor)
builder.WriteString(" | ")
builder.WriteString(string(src[lineBeginning:lineEnd]))
b.WriteString(descriptor)
b.WriteString(" | ")
b.WriteString(string(src[lineBeginning:lineEnd]))
builder.WriteString("\n")
builder.WriteString(strings.Repeat(" ", len(descriptor)))
builder.WriteString(" ")
b.WriteString("\n")
b.WriteString(strings.Repeat(" ", len(descriptor)))
b.WriteString(" ")
for i := lineBeginning; i <= int(p.Causer.Start); i++ {
builder.WriteRune(' ')
b.WriteRune(' ')
}
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 {
source string
trace []string
tokens []Token
prev *Token
curr *Token
pos Pos
}
func NewParser(source string, tokens []Token) *Parser {
func NewParser(source string, trace []string, tokens []Token) *Parser {
return &Parser{
source: source,
trace: trace,
tokens: tokens,
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])
continue
}
b, err := p.block(true)
@ -190,6 +199,7 @@ func (p *Parser) error(error string, causer *Token) error {
Description: error,
Causer: causer,
Source: p.source,
Trace: p.trace,
}
}

View file

@ -10,7 +10,7 @@ import (
func TestNewParser(t *testing.T) {
tokens := make([]Token, 0)
p := NewParser("", tokens)
p := NewParser("", []string{}, tokens)
if p == nil {
t.Fatal("parser should not be nil")
@ -34,7 +34,7 @@ func TestNewParser(t *testing.T) {
func BenchmarkNewParser(b *testing.B) {
tokens := make([]Token, 0)
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.Logf("Initializing parser")
p := NewParser("", data.tokens)
p := NewParser("", []string{}, data.tokens)
t.Logf("Parsing main")
tree, err := p.Parse("")
@ -931,7 +931,7 @@ func BenchmarkParser_Parse(b *testing.B) {
for name, data := range tokenData {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
p := NewParser("", data.tokens)
p := NewParser("", []string{}, data.tokens)
_, _ = p.Parse("")
}

View file

@ -56,3 +56,8 @@ func (s *Stack[T]) check() {
panic("stack underflow")
}
}
// Slice gets a slice of the current items in use
func (s *Stack[T]) Slice() []T {
return s.items[:s.Current]
}

View file

@ -467,6 +467,31 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
nil,
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",
&FunctionSignature{

View file

@ -1,9 +1,19 @@
#!/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 ==='
cd cli || exit 1
if ! go build .; then
echo "=== Had error building CLI ==="
echo "=x= Had error building CLI =x="
exit 1
else
echo "=+= Successfully built CLI =+="

View file

@ -1,4 +1,4 @@
//go:build wasm && go1.23
//go:build wasm && go1.24
package main
@ -58,7 +58,7 @@ func run(_ js.Value, args []js.Value) interface{} {
log.Printf("got tokens: %v", tokens)
parser := core.NewParser(source, tokens)
parser := core.NewParser(source, []string{}, tokens)
tree, err := parser.Parse(source)