diff options
author | Juan J. Martinez <jjm@usebox.net> | 2022-07-18 07:45:58 +0100 |
---|---|---|
committer | Juan J. Martinez <jjm@usebox.net> | 2022-07-18 07:45:58 +0100 |
commit | 8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642 (patch) | |
tree | c53977d1284347bb1d5963ddb4dc7723c40c6e55 /parser | |
download | micro-lang-8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642.tar.gz micro-lang-8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642.zip |
First public release
Diffstat (limited to 'parser')
-rw-r--r-- | parser/parser.go | 1168 | ||||
-rw-r--r-- | parser/parser_test.go | 250 |
2 files changed, 1418 insertions, 0 deletions
diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..65007b7 --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,1168 @@ +package parser + +import ( + "fmt" + "strconv" + + "usebox.net/lang/ast" + "usebox.net/lang/errors" + "usebox.net/lang/tokens" +) + +// XXX: duplicated +const runtime = "<rt>" + +type Env struct { + env map[string]ast.Type + parent *Env +} + +func NewEnv(parent *Env) *Env { + return &Env{make(map[string]ast.Type), parent} +} + +func (e *Env) Get(key string, local bool) (ast.Type, bool) { + if v, ok := e.env[key]; ok { + return v, true + } + if !local && e.parent != nil { + return e.parent.Get(key, local) + } + return ast.TypeNone, false +} + +func (e *Env) Set(key string, typ ast.Type) *Env { + n := e + if _, ok := e.Get(key, false); ok { + n = NewEnv(e) + } + n.env[key] = typ + return n +} + +type Parser struct { + tokens []tokens.Token + pos int + env *Env + structs map[string]*Env + currFuncRet *ast.Type + currLoop bool +} + +func NewParser(builtIns map[string]ast.Type) Parser { + global := NewEnv(nil) + for k, v := range builtIns { + // not shadowing any value + global.Set(k, v) + } + + return Parser{env: global, structs: make(map[string]*Env)} +} + +func (p *Parser) Parse(tokens []tokens.Token) ([]ast.Expr, error) { + var errs []error + var exprs []ast.Expr + + p.tokens = tokens + p.pos = 0 + + for !p.end() { + expr, err := p.statement() + if err != nil { + errs = append(errs, err) + p.recover() + continue + } + exprs = append(exprs, expr) + } + + if len(errs) > 0 { + return nil, errors.ErrorList{Errors: errs} + } + + return exprs, nil +} + +func (p *Parser) recover() { + p.next() + + for !p.end() { + if p.prev().Id == tokens.Semicolon { + return + } + if p.match([]tokens.TokenId{tokens.Def, tokens.Var, tokens.Const, tokens.If, tokens.Return, tokens.For}) { + p.back() + return + } + + p.next() + } +} + +func (p *Parser) next() tokens.Token { + if !p.end() { + p.pos++ + return p.prev() + } + return p.curr() +} + +func (p *Parser) back() { + if p.pos != 0 { + p.pos-- + } +} + +func (p *Parser) prev() tokens.Token { + return p.tokens[p.pos-1] +} + +func (p *Parser) curr() tokens.Token { + return p.tokens[p.pos] +} + +func (p *Parser) end() bool { + return p.curr().Id == tokens.Eof +} + +func (p *Parser) check(id tokens.TokenId) bool { + if p.end() { + return false + } + return p.curr().Id == id +} + +func (p *Parser) match(ids []tokens.TokenId) bool { + for _, t := range ids { + if p.check(t) { + p.next() + return true + } + } + return false +} + +func (p *Parser) statement() (ast.Expr, error) { + if p.match([]tokens.TokenId{tokens.Var, tokens.Const}) { + return p.declaration() + } + + if p.check(tokens.Break) { + p.next() + return p.breakStmt() + } + + if p.check(tokens.Continue) { + p.next() + return p.continueStmt() + } + + if p.check(tokens.Return) { + p.next() + return p.returnStmt() + } + + if p.check(tokens.For) { + p.next() + return p.forStmt() + } + + if p.check(tokens.If) { + return p.ifElse() + } + + if p.check(tokens.LBrace) { + return p.block() + } + + if p.check(tokens.Def) { + p.next() + return p.definition() + } + + expr, err := p.expr() + if err != nil { + return nil, err + } + if p.next().Id != tokens.Semicolon { + return nil, errors.NewError(errors.ExpectedSemicolon, p.prev().Loc, "';' expected after expression") + } + return expr, nil +} + +func (p *Parser) expr() (ast.Expr, error) { + return p.assign() +} + +func (p *Parser) typ() (*ast.Type, error) { + if p.check(tokens.LBracket) { + tok := p.next() + + size, err := p.primary() + if err != nil { + return nil, err + } + + if p.next().Id != tokens.RBracket { + return nil, errors.NewError(errors.ExpectedRBracket, p.prev().Loc, "']' expected in array definition") + } + + typ, err := p.typ() + if err != nil { + return nil, err + } + + if typ.Value.Id == tokens.TArray { + return nil, errors.NewError(errors.InvalidType, p.prev().Loc, "invalid array type", typ.String()) + } + + if size.Resolve().Compare(&ast.TypeNumber) { + if v, ok := size.(ast.Literal); ok { + tok.Id = tokens.TArray + intval := int(v.Numval) + return &ast.Type{Value: tok, Ret: typ, Len: &intval}, nil + } + } + + return nil, errors.NewError(errors.InvalidValue, p.prev().Loc, "invalid value for array size") + } + + if !p.match([]tokens.TokenId{tokens.TNumber, tokens.TBool, tokens.TString, tokens.TFunc, tokens.Ident}) { + return nil, errors.NewError(errors.ExpectedType, p.curr().Loc, "expected type") + } + typ := p.prev() + + if typ.Id == tokens.Ident { + typ, ok := p.env.Get(typ.Value, false) + if !ok || typ.Value.Id != tokens.TStruct { + return nil, errors.NewError(errors.ExpectedType, p.curr().Loc, "expected type") + } + if typ.Inst { + return nil, errors.NewError(errors.ExpectedType, p.curr().Loc, "expected type found instance of", typ.String()) + } + // this is an instance of the struct + typ.Inst = true + return &typ, nil + } + + if typ.Id != tokens.TFunc { + return &ast.Type{Value: typ}, nil + } + + if p.next().Id != tokens.LParen { + return nil, errors.NewError(errors.ExpectedLParen, p.prev().Loc, "'(' expected in function type") + } + + params := make([]ast.Type, 0, 16) + for !p.check(tokens.RParen) { + typ, err := p.typ() + if err != nil { + return nil, err + } + + params = append(params, *typ) + if !p.check(tokens.Comma) { + break + } + p.next() + } + p.next() + + var ret *ast.Type + if !p.check(tokens.Semicolon) && !p.check(tokens.Assign) && !p.check(tokens.RParen) && !p.check(tokens.LBrace) { + if typ, err := p.typ(); err != nil { + return nil, err + } else { + ret = typ + } + } + + return &ast.Type{Value: typ, Params: params, Ret: ret}, nil +} + +func (p *Parser) definition() (ast.Expr, error) { + name := p.next() + if name.Id != tokens.Ident { + return nil, errors.NewError(errors.ExpectedIdent, name.Loc, "expected identifier") + } + + if p.check(tokens.LBrace) { + return p.strctDecl(name) + } + + return p.funcDecl(name) +} + +func (p *Parser) strctDecl(name tokens.Token) (ast.Expr, error) { + if v, ok := p.env.Get(name.Value, true); ok { + loc := v.Value.Loc.String() + if v.Value.Loc.File == "" { + loc = runtime + } + return nil, errors.NewError(errors.AlreadyDeclared, name.Loc, "struct", name.String(), "already declared in", loc) + } + + p.env = p.env.Set(name.Value, *ast.NewStructType(name)) + pEnv := p.env + sEnv := NewEnv(pEnv) + p.env = sEnv + + expr, err := p.strctBlock() + if err != nil { + return nil, err + } + + if len(sEnv.env) == 0 { + return nil, errors.NewError(errors.InvalidValue, name.Loc, "empty struct") + } + + p.structs[name.Value] = sEnv + p.env = pEnv + + return ast.Struct{Name: name, Body: expr.(ast.Block)}, nil +} + +func (p *Parser) funcDecl(name tokens.Token) (ast.Expr, error) { + if p.next().Id != tokens.LParen { + return nil, errors.NewError(errors.ExpectedLParen, p.prev().Loc, "'(' expected in function declaration") + } + + params := make([]ast.Var, 0, 16) + paramTypes := make([]ast.Type, 0, 16) + seen := map[string]tokens.Location{} + for !p.check(tokens.RParen) { + pname := p.next() + if pname.Id != tokens.Ident { + return nil, errors.NewError(errors.ExpectedIdent, pname.Loc, "expected parameter name") + } + + typ, err := p.typ() + if err != nil { + return nil, err + } + + if prev, ok := seen[pname.Value]; ok { + return nil, errors.NewError(errors.AlreadyDeclared, pname.Loc, "parameter", pname.String(), "already declared in", prev.String()) + } + seen[pname.Value] = pname.Loc + + params = append(params, ast.Var{Name: pname, Type: *typ}) + paramTypes = append(paramTypes, *typ) + if !p.check(tokens.Comma) { + break + } + p.next() + } + p.next() + + var ret *ast.Type + if !p.check(tokens.LBrace) { + if typ, err := p.typ(); err != nil { + return nil, err + } else { + ret = typ + } + } + + if v, ok := p.env.Get(name.Value, true); ok { + loc := v.Value.Loc.String() + if v.Value.Loc.File == "" { + loc = runtime + } + return nil, errors.NewError(errors.AlreadyDeclared, name.Loc, "function", name.String(), "already declared in", loc) + } + p.env = p.env.Set(name.Value, *ast.NewFuncType(name, paramTypes, ret)) + + oldFuncRet := p.currFuncRet + if ret != nil { + p.currFuncRet = ret + } else { + p.currFuncRet = &ast.TypeNone + } + defer func() { + p.currFuncRet = oldFuncRet + }() + + for _, v := range params { + p.env = p.env.Set(v.Name.Value, v.Type) + } + + expr, err := p.block() + if err != nil { + return nil, err + } + + // return checks? + return ast.Func{Name: name, Params: params, Ret: ret, Body: expr.(ast.Block)}, nil +} + +func (p *Parser) exprList() (ast.Expr, error) { + if p.check(tokens.LBracket) { + p.next() + vals := make([]ast.Expr, 0, 16) + if !p.check(tokens.RBracket) { + var prev ast.Expr + for { + val, err := p.expr() + if err != nil { + return nil, err + } + if prev != nil && !prev.Resolve().Compare(val.Resolve()) { + return nil, errors.NewError(errors.TypeMismatch, p.prev().Loc, "type mismatch in expression list: found", val.Resolve().String(), "expected", prev.Resolve().String()) + } + prev = val + + vals = append(vals, val) + if !p.check(tokens.Comma) { + break + } + p.next() + } + if !p.check(tokens.RBracket) { + return nil, errors.NewError(errors.ExpectedRBracket, p.prev().Loc, "']' expected to end expression list") + } + } + p.next() + if len(vals) == 0 { + return nil, errors.NewError(errors.InvalidValue, p.prev().Loc, "empty expression list") + } + + return ast.ExprList{Exprs: vals}, nil + } + + return p.expr() +} + +func (p *Parser) decl(con bool) (ast.Expr, error) { + name := p.next() + if name.Id != tokens.Ident { + return nil, errors.NewError(errors.ExpectedIdent, name.Loc, "expected identifier") + } + + typ, err := p.typ() + if err != nil { + return nil, err + } + + if typ.Value.Id == tokens.TStruct { + if _, ok := p.structs[typ.Value.Value]; !ok { + return nil, errors.NewError(errors.RecursiveStruct, p.curr().Loc, "recursive definition of", typ.String()) + } + } + + var initv ast.Expr + if p.check(tokens.Assign) { + p.next() + var err error + initv, err = p.exprList() + if err != nil { + return nil, err + } + } + + if p.next().Id != tokens.Semicolon { + return nil, errors.NewError(errors.ExpectedSemicolon, p.prev().Loc, "';' expected after expression") + } + + if v, ok := p.env.Get(name.Value, true); ok { + loc := v.Value.Loc.String() + if v.Value.Loc.File == "" { + loc = runtime + } + return nil, errors.NewError(errors.AlreadyDeclared, name.Loc, name.String(), "already declared in", loc) + } + + if initv != nil { + if v, ok := initv.(ast.ExprList); ok { + if typ.Value.Id != tokens.TArray { + return nil, errors.NewError(errors.InvalidValue, name.Loc, "expression list to initialize a type", typ.String()) + } + if *typ.Len != len(v.Exprs) { + return nil, errors.NewError(errors.InvalidValue, name.Loc, "invalid number of elements in expression list to initialize a type", typ.String()) + } + if !typ.Ret.Compare(initv.Resolve()) { + return nil, errors.NewError(errors.TypeMismatch, name.Loc, "type mismatch expected list of", typ.Ret.String(), "found", initv.Resolve().String()) + } + } else if !typ.Compare(initv.Resolve()) { + return nil, errors.NewError(errors.TypeMismatch, name.Loc, "type mismatch expected", typ.String(), "found", initv.Resolve().String()) + } + if initv.Resolve().Value.Id == tokens.TArray || initv.Resolve().Value.Id == tokens.TStruct { + if !con && initv.Resolve().ResolveConst() { + return nil, errors.NewError(errors.TypeMismatch, name.Loc, "variable cannot be used as referece to constant") + } + typ.Ref = true + } + } + if typ.Value.Id == tokens.TStruct { + // it is an instance + p.structs[name.Value] = p.structs[typ.Value.Value] + typ.Inst = true + } + if con { + if initv == nil { + return nil, errors.NewError(errors.ExpectedAssign, name.Loc, "expected assignation in constant declaration") + } + if typ.Value.Id == tokens.TArray { + // allow changing reference but not the values of the array + typ.Ret.Const = true + } else { + typ.Const = true + } + } + + p.env = p.env.Set(name.Value, *typ) + return ast.Var{Name: name, Type: *typ, Initv: initv}, nil +} + +func (p *Parser) declaration() (ast.Expr, error) { + if p.prev().Id == tokens.Const { + return p.decl(true) + } + return p.decl(false) +} + +func (p *Parser) continueStmt() (ast.Expr, error) { + if p.next().Id != tokens.Semicolon { + return nil, errors.NewError(errors.ExpectedSemicolon, p.prev().Loc, "';' expected after expression") + } + + if !p.currLoop { + return nil, errors.NewError(errors.InvalidOperation, p.curr().Loc, "continue without loop") + } + + return ast.Continue{Loc: p.curr().Loc}, nil +} + +func (p *Parser) breakStmt() (ast.Expr, error) { + if p.next().Id != tokens.Semicolon { + return nil, errors.NewError(errors.ExpectedSemicolon, p.prev().Loc, "';' expected after expression") + } + + if !p.currLoop { + return nil, errors.NewError(errors.InvalidOperation, p.curr().Loc, "break without loop") + } + + return ast.Break{Loc: p.curr().Loc}, nil +} + +func (p *Parser) returnStmt() (ast.Expr, error) { + if p.currFuncRet == nil { + return nil, errors.NewError(errors.InvalidOperation, p.prev().Loc, "return without function call") + } + + var isError bool + if p.check(tokens.TagE) { + p.next() + isError = true + } + + if p.check(tokens.Semicolon) { + p.next() + if p.currFuncRet.Value.Id != tokens.None { + return nil, errors.NewError(errors.TypeMismatch, p.prev().Loc, "type mismatch in return value, expected", p.currFuncRet.String()) + } + return ast.Return{Loc: p.curr().Loc, Error: isError}, nil + } + + expr, err := p.expr() + if err != nil { + return nil, err + } + + if p.next().Id != tokens.Semicolon { + return nil, errors.NewError(errors.ExpectedSemicolon, p.prev().Loc, "';' expected after expression") + } + + exprRet := expr.Resolve() + if exprRet == nil { + exprRet = &ast.TypeNone + } else { + if exprRet.Value.Id == tokens.TArray || exprRet.Value.Id == tokens.TStruct { + if v, ok := expr.(ast.Variable); ok { + if val, ok := p.env.Get(v.Name.Value, true); ok && !val.Ref { + return nil, errors.NewError(errors.InvalidOperation, p.prev().Loc, "returning a local value of type", exprRet.String()) + } + } + } + } + + if !p.currFuncRet.Compare(exprRet) { + return nil, errors.NewError(errors.TypeMismatch, p.prev().Loc, "type mismatch in return value, expected", p.currFuncRet.String(), "found", exprRet.String()) + } + + return ast.Return{Value: expr, Loc: p.prev().Loc, Error: isError}, nil +} + +func (p *Parser) forBody() (ast.Expr, error) { + oldCurrLoop := p.currLoop + p.currLoop = true + defer func() { + p.currLoop = oldCurrLoop + }() + + return p.block() +} + +func (p *Parser) forStmt() (ast.Expr, error) { + var expr ast.Expr + var err error + + if !p.check(tokens.LBrace) { + if p.check(tokens.Ident) { + ident := p.next() + if p.check(tokens.In) { + p.next() + expr, err = p.expr() + if err != nil { + return nil, err + } + if expr.Resolve().Value.Id != tokens.TArray { + return nil, errors.NewError(errors.TypeMismatch, p.next().Loc, "expected array in for-in statement found", expr.Resolve().String()) + } + + pEnv := p.env + p.env = NewEnv(pEnv) + defer func() { + p.env = pEnv + }() + p.env = p.env.Set(ident.Value, *expr.Resolve().Ret) + + stmts, err := p.forBody() + if err != nil { + return nil, err + } + return ast.ForIn{Name: ident, Expr: expr, Stmts: stmts}, nil + } + p.back() + } + + expr, err = p.expr() + if err != nil { + return nil, err + } + } + + stmts, err := p.forBody() + if err != nil { + return nil, err + } + return ast.For{Cond: expr, Stmts: stmts}, nil +} + +func (p *Parser) ifElse() (ast.Expr, error) { + ifTok := p.next() + expr, err := p.expr() + if err != nil { + return nil, err + } + ifBlock, err := p.block() + if err != nil { + return nil, err + } + + var elseBlock ast.Expr + if p.check(tokens.Else) { + p.next() + elseBlock, err = p.block() + if err != nil { + return nil, err + } + } else { + elseBlock = ast.Literal{Value: tokens.Token{Id: tokens.None, Loc: ifTok.Loc}} + } + return ast.IfElse{Cond: expr, True: ifBlock, False: elseBlock}, nil +} + +func (p *Parser) strctBlock() (ast.Expr, error) { + definitions := make([]ast.Expr, 0, 256) + + if p.next().Id != tokens.LBrace { + return nil, errors.NewError(errors.ExpectedLBrace, p.prev().Loc, "'{' expected to open block") + } + + for !p.check(tokens.RBrace) && !p.end() { + if p.check(tokens.Def) { + p.next() + decl, err := p.definition() + if err != nil { + return nil, err + } + definitions = append(definitions, decl) + continue + } + if p.match([]tokens.TokenId{tokens.Var, tokens.Const}) { + decl, err := p.declaration() + if err != nil { + return nil, err + } + definitions = append(definitions, decl) + continue + } + return nil, errors.NewError(errors.ExpectedDefinition, p.next().Loc, "expected definition in a struct block") + } + + next := p.next() + if next.Id != tokens.RBrace { + // MUST be EOF + return nil, errors.NewError(errors.ExpectedRBrace, next.Loc, "'}' expected to close block, end of file found") + } + + return ast.Block{Stmts: definitions}, nil +} + +func (p *Parser) block() (ast.Expr, error) { + pEnv := p.env + p.env = NewEnv(pEnv) + defer func() { + p.env = pEnv + }() + + statements := make([]ast.Expr, 0, 256) + + if p.next().Id != tokens.LBrace { + return nil, errors.NewError(errors.ExpectedLBrace, p.prev().Loc, "'{' expected to open block") + } + + for !p.check(tokens.RBrace) && !p.end() { + decl, err := p.statement() + if err != nil { + return nil, err + } + statements = append(statements, decl) + } + + next := p.next() + if next.Id != tokens.RBrace { + // MUST be EOF + return nil, errors.NewError(errors.ExpectedRBrace, next.Loc, "'}' expected to close block, end of file found") + } + + return ast.Block{Stmts: statements}, nil +} + +func (p *Parser) assign() (ast.Expr, error) { + expr, err := p.or() + if err != nil { + return nil, err + } + + if p.check(tokens.Assign) { + tok := p.next() + value, err := p.expr() + if err != nil { + return nil, err + } + + left := expr + env := p.env + + if obj, ok := expr.(ast.GetExpr); ok { + oType := obj.Object.Resolve() + sEnv, ok := p.structs[oType.Value.Value] + if !ok { + // shouldn't happen + return nil, errors.NewError(errors.Unexpected, oType.Value.Loc, "undeclared struct", oType.String()) + } + env = sEnv + expr = obj.Expr + } + + v, ok := expr.(ast.Variable) + if !ok { + return nil, errors.NewError(errors.InvalidTarget, tok.Loc, "invalid assignment target") + } + + typ, ok := env.Get(v.Name.Value, false) + if !ok { + return nil, errors.NewError(errors.UndefinedIdent, v.Name.Loc, "undeclared variable", v.String()) + } + if typ.Value.Id == tokens.TArray && v.Index != nil { + typ = *typ.Ret + } + if !typ.Compare(value.Resolve()) { + return nil, errors.NewError(errors.InvalidValue, tok.Loc, "invalid value in assignation, expected", typ.String(), "found", value.Resolve().String()) + } + if typ.Value.Id == tokens.TArray || typ.Value.Id == tokens.TStruct { + if !typ.Ref { + return nil, errors.NewError(errors.TypeMismatch, v.Name.Loc, "variable cannot be converted to a reference", v.String()) + } + if !typ.ResolveConst() && value.Resolve().ResolveConst() { + return nil, errors.NewError(errors.TypeMismatch, v.Name.Loc, "variable cannot be used as referece to constant") + } + } + if typ.Const { + return nil, errors.NewError(errors.AssignConst, v.Name.Loc, "value cannot be assigned to constant") + } + return ast.Assign{Left: left, Right: value, Loc: tok.Loc}, nil + } + + return expr, nil +} + +func (p *Parser) or() (ast.Expr, error) { + expr, err := p.and() + if err != nil { + return nil, err + } + + if p.check(tokens.Or) { + op := p.next() + right, err := p.and() + if err != nil { + return nil, err + } + expr = ast.Binary{Left: expr, Op: op, Right: right, Type: &ast.TypeBool} + } + + return expr, nil +} + +func (p *Parser) and() (ast.Expr, error) { + expr, err := p.equality() + if err != nil { + return nil, err + } + + if p.check(tokens.And) { + op := p.next() + right, err := p.equality() + if err != nil { + return nil, err + } + expr = ast.Binary{Left: expr, Op: op, Right: right, Type: &ast.TypeBool} + } + + return expr, nil +} + +func (p *Parser) equality() (ast.Expr, error) { + expr, err := p.comparison() + if err != nil { + return nil, err + } + + for p.match([]tokens.TokenId{tokens.Eq, tokens.Ne}) { + op := p.prev() + right, err := p.comparison() + if err != nil { + return nil, err + } + + if !expr.Resolve().Compare(right.Resolve()) { + return nil, errors.NewError(errors.TypeMismatch, op.Loc, "type mistmach expected", expr.Resolve().String(), "found", right.Resolve().String()) + } + + expr = ast.Binary{Left: expr, Op: op, Right: right, Type: &ast.TypeBool} + } + + return expr, nil +} + +func (p *Parser) comparison() (ast.Expr, error) { + expr, err := p.bitterm() + if err != nil { + return nil, err + } + + for p.match([]tokens.TokenId{tokens.Gt, tokens.Ge, tokens.Lt, tokens.Le}) { + op := p.prev() + right, err := p.bitterm() + if err != nil { + return nil, err + } + + if !expr.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", expr.Resolve().String()) + } + if !right.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", right.Resolve().String()) + } + + expr = ast.Binary{Left: expr, Op: op, Right: right, Type: &ast.TypeBool} + } + + return expr, nil +} + +func (p *Parser) bitterm() (ast.Expr, error) { + expr, err := p.term() + if err != nil { + return nil, err + } + + for p.match([]tokens.TokenId{tokens.BitShl, tokens.BitShr, tokens.BitOr, tokens.BitXor}) { + op := p.prev() + right, err := p.term() + if err != nil { + return nil, err + } + + if !expr.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", expr.Resolve().String()) + } + if !right.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", right.Resolve().String()) + } + + expr = ast.Binary{Left: expr, Op: op, Right: right} + } + + return expr, nil +} + +func (p *Parser) term() (ast.Expr, error) { + expr, err := p.bitfactor() + if err != nil { + return nil, err + } + + for p.match([]tokens.TokenId{tokens.Sub, tokens.Add}) { + op := p.prev() + right, err := p.bitfactor() + if err != nil { + return nil, err + } + + if !expr.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", expr.Resolve().String()) + } + if !right.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", right.Resolve().String()) + } + + expr = ast.Binary{Left: expr, Op: op, Right: right} + } + + return expr, nil +} + +func (p *Parser) bitfactor() (ast.Expr, error) { + expr, err := p.factor() + if err != nil { + return nil, err + } + + if p.check(tokens.BitAnd) { + op := p.next() + right, err := p.factor() + if err != nil { + return nil, err + } + + if !expr.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", expr.Resolve().String()) + } + if !right.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", right.Resolve().String()) + } + + expr = ast.Binary{Left: expr, Op: op, Right: right} + } + + return expr, nil +} + +func (p *Parser) factor() (ast.Expr, error) { + expr, err := p.unary() + if err != nil { + return nil, err + } + + for p.match([]tokens.TokenId{tokens.Div, tokens.Mul, tokens.Mod}) { + op := p.prev() + right, err := p.unary() + if err != nil { + return nil, err + } + + if !expr.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", expr.Resolve().String()) + } + if !right.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", right.Resolve().String()) + } + + expr = ast.Binary{Left: expr, Op: op, Right: right} + } + + return expr, nil +} + +func (p *Parser) unary() (ast.Expr, error) { + if p.match([]tokens.TokenId{tokens.TestE, tokens.Not, tokens.Neg, tokens.Sub}) { + op := p.prev() + right, err := p.unary() + if err != nil { + return nil, err + } + if (op.Id == tokens.Sub || op.Id == tokens.Neg) && right.Resolve().Value.Id != tokens.TNumber { + return nil, errors.NewError(errors.InvalidOperation, op.Loc, "invalid operation for type", right.Resolve().String()) + } + return ast.Unary{Op: op, Right: right}, nil + } + + return p.call() +} + +func (p *Parser) call() (ast.Expr, error) { + expr, err := p.primary() + if err != nil { + return nil, err + } + + for { + if p.check(tokens.LBracket) { + loc := p.next().Loc + + if expr.Resolve().Value.Id != tokens.TArray { + return nil, errors.NewError(errors.NotIndexable, loc, "value is not indexable") + } + + index, err := p.expr() + if err != nil { + return nil, err + } + if !index.Resolve().Compare(&ast.TypeNumber) { + return nil, errors.NewError(errors.InvalidValue, p.prev().Loc, "invalid index value") + } + + if p.next().Id != tokens.RBracket { + return nil, errors.NewError(errors.ExpectedRBracket, p.prev().Loc, "']' expected to close index") + } + + switch v := expr.(type) { + case ast.Variable: + v.Index = index + expr = v + case ast.GetExpr: + val := v.Expr.(ast.Variable) + val.Index = index + v.Expr = val + expr = v + default: + // shouldn't happen + return nil, errors.NewError(errors.Unexpected, loc, "value is not indexable") + } + continue + } + + if p.check(tokens.Dot) { + loc := p.next().Loc + prop := p.next() + if prop.Id != tokens.Ident { + return nil, errors.NewError(errors.ExpectedIdent, prop.Loc, "expected identifier") + } + sType := expr.Resolve() + if sType.Value.Id != tokens.TStruct { + return nil, errors.NewError(errors.NotStruct, loc, "property get on a non struct value") + } + + sEnv, ok := p.structs[sType.Value.Value] + if !ok { + // shouldn't happen + return nil, errors.NewError(errors.Unexpected, sType.Value.Loc, "undeclared struct", sType.String()) + } + pType, ok := sEnv.Get(prop.Value, true) + if !ok { + return nil, errors.NewError(errors.UndefinedIdent, loc, "property", prop.String(), "not defined in", sType.String()) + } + + expr = ast.GetExpr{Object: expr, Expr: ast.Variable{Name: prop, Type: pType}} + continue + } + + if p.check(tokens.LParen) { + p.next() + args := make([]ast.Expr, 0, 16) + if !p.check(tokens.RParen) { + for { + arg, err := p.expr() + if err != nil { + return nil, err + } + args = append(args, arg) + if !p.check(tokens.Comma) { + break + } + p.next() + } + if !p.check(tokens.RParen) { + return nil, errors.NewError(errors.ExpectedRParen, p.prev().Loc, "')' expected to end function call") + } + } + typ := expr.Resolve() + if typ.Value.Id != tokens.TFunc { + return nil, errors.NewError(errors.NotCallable, p.next().Loc, "value is not callable") + } + + // mostly for println that doesn't have type checks + // and will consume any number of arguments + if typ.Params != nil { + if len(typ.Params) != len(args) { + return nil, errors.NewError(errors.CallError, p.next().Loc, fmt.Sprintf("invalid number of arguments in function call: %d expected %d found", len(typ.Params), len(args))) + } + for n, arg := range args { + if !typ.Params[n].Compare(arg.Resolve()) { + return nil, errors.NewError(errors.TypeMismatch, p.next().Loc, fmt.Sprintf("type mismatch in function call argument %d: expected %s found %s", n+1, typ.Params[n].String(), arg.Resolve().String())) + } + } + } + expr = ast.Call{Callee: expr, Loc: p.next().Loc, Args: args} + continue + } + break + } + return expr, nil +} + +func (p *Parser) primary() (ast.Expr, error) { + if p.match([]tokens.TokenId{tokens.True, tokens.False}) { + return ast.Literal{Value: p.prev(), Type: ast.TypeBool}, nil + } else if p.check(tokens.String) { + return ast.Literal{Value: p.next(), Type: ast.TypeString}, nil + } else if p.check(tokens.Ident) { + value := p.next() + + typc, ok := p.env.Get(value.Value, false) + if !ok { + return nil, errors.NewError(errors.UndefinedIdent, value.Loc, "undeclared variable", value.String()) + } + if typc.Value.Id == tokens.TStruct && !typc.Inst { + return nil, errors.NewError(errors.InvalidValue, value.Loc, value.String(), "is not an instance") + } + + return ast.Variable{Name: value, Type: typc}, nil + } else if p.check(tokens.Char) { + value := p.next() + value.Id = tokens.Number + nval := byte(value.Value[0]) + return ast.Literal{Value: value, Numval: ast.Number(nval), Type: ast.TypeNumber}, nil + } else if p.check(tokens.Number) { + value := p.next() + nval, err := strconv.ParseInt(value.Value, 0, 64) + if err != nil { + return nil, errors.Error{ + Code: errors.FailedNumeric, + Message: fmt.Sprintf("[%s] failed to parse numeric value", value.Loc), + Err: err, + } + } + return ast.Literal{Value: value, Numval: ast.Number(nval), Type: ast.TypeNumber}, nil + } else if p.check(tokens.LParen) { + p.next() + expr, err := p.expr() + if err != nil { + return nil, err + } + next := p.next() + if next.Id != tokens.RParen { + found := fmt.Sprintf("'%v'", next.Value) + if next.Id == tokens.Eof { + found = "end of file" + } + return nil, errors.NewError(errors.ExpectedRParen, next.Loc, "')' expected after expression,", found, "found") + } + return ast.Group{Expr: expr}, nil + } + return nil, errors.NewError(errors.ExpectedExpr, p.curr().Loc, "expression expected") +} diff --git a/parser/parser_test.go b/parser/parser_test.go new file mode 100644 index 0000000..8e97146 --- /dev/null +++ b/parser/parser_test.go @@ -0,0 +1,250 @@ +package parser + +import ( + "strings" + "testing" + + "usebox.net/lang/ast" + "usebox.net/lang/errors" + "usebox.net/lang/tokenizer" +) + +func parse(input string) ([]ast.Expr, error) { + tr := tokenizer.NewTokenizer("-", strings.NewReader(input)) + toks, err := tr.Scan() + if err != nil { + return nil, err + } + p := NewParser(nil) + return p.Parse(toks) +} + +func expectError(t *testing.T, input string, code errors.ErrCode) { + _, errs := parse(input) + if errs == nil { + t.Errorf("expected error and it didn't happen (input: %s)", input) + } + el, ok := errs.(errors.ErrorList) + if !ok { + t.Errorf("error list expected (input: %s)", input) + } else if len(el.Errors) == 0 { + t.Errorf("error list is empty (input: %s)", input) + } else { + if first, ok := el.Errors[0].(errors.Error); !ok { + t.Errorf("error in error list not an Error (input: %s)", input) + } else { + if first.Code != code { + t.Errorf("error %d expected, got %d -> %s (input: %s)", code, first.Code, first, input) + } + } + } +} + +func TestErrorFailedNumeric(t *testing.T) { + expectError(t, "var n number = 18446744073709551616;", errors.FailedNumeric) +} + +func TestErrorExpectedRParen(t *testing.T) { + expectError(t, "var n number = (1 + 2 ;", errors.ExpectedRParen) +} + +func TestErrorExpectedExpr(t *testing.T) { + expectError(t, ";", errors.ExpectedExpr) +} + +func TestErrorExpectedVarName(t *testing.T) { + expectError(t, "var number = 1;", errors.ExpectedIdent) + expectError(t, "const false;", errors.ExpectedIdent) +} + +func TestErrorExpectedType(t *testing.T) { + expectError(t, "var n = 1;", errors.ExpectedType) + expectError(t, "const n = 1;", errors.ExpectedType) + expectError(t, "def fn(n) { }", errors.ExpectedType) + expectError(t, "def fn() { }\nvar a number = fn();", errors.TypeMismatch) +} + +func TestErrorDeclareUndefined(t *testing.T) { + expectError(t, "var a number = a;", errors.UndefinedIdent) +} + +func TestErrorExpectedSemiColon(t *testing.T) { + expectError(t, "var n number = 1", errors.ExpectedSemicolon) + expectError(t, "1 + 1", errors.ExpectedSemicolon) +} + +func TestErrorExpectedFunc(t *testing.T) { + expectError(t, "def ;", errors.ExpectedIdent) + expectError(t, "def fn;", errors.ExpectedLParen) + expectError(t, "def fn(number);", errors.ExpectedIdent) + expectError(t, "def fn(a number, number);", errors.ExpectedIdent) + expectError(t, "def fn(a, b);", errors.ExpectedType) + expectError(t, "def fn(a bool, b);", errors.ExpectedType) + expectError(t, "def fn() id;", errors.ExpectedType) + expectError(t, "def fn(a number) id;", errors.ExpectedType) + expectError(t, "def fn()", errors.ExpectedType) + expectError(t, "def fn() {", errors.ExpectedRBrace) + expectError(t, "def fn() number {", errors.ExpectedRBrace) + expectError(t, "fn(;", errors.UndefinedIdent) + expectError(t, "fn(0;", errors.UndefinedIdent) + expectError(t, "fn()", errors.UndefinedIdent) + expectError(t, "def fn() bool { return 1; }", errors.TypeMismatch) + expectError(t, "def fn() { }\ndef fn() { }", errors.AlreadyDeclared) +} + +func TestErrorReturn(t *testing.T) { + expectError(t, "return", errors.InvalidOperation) + expectError(t, "return true", errors.InvalidOperation) + expectError(t, "def fn() { return }", errors.ExpectedExpr) + expectError(t, "def fn() { return true }", errors.ExpectedSemicolon) + expectError(t, ` + def fn() [5]number { + var local [5]number; + return local; + } + `, errors.InvalidOperation) + expectError(t, ` + def A { var p number; } + def fn() A { + var local A; + return local; + } + `, errors.InvalidOperation) + expectError(t, ` + def A { var p number; } + var g A; + def fn() A { + var local A; + local = g; + return local; + } + "OK"; + `, errors.TypeMismatch) + expectError(t, ` + var g [3]bool; + def fn() [3]bool { + var local [3]bool; + local = g; + return local; + } + "OK"; + `, errors.TypeMismatch) + expectError(t, ` + const a [3]number = [1, 2, 3]; + var b [3]number = a; + `, errors.TypeMismatch) + expectError(t, ` + const a [3]number = [1, 2, 3]; + var b [3]number; + var c [3]number = b; + c = a; + `, errors.TypeMismatch) +} + +func TestErrorExpectedAssign(t *testing.T) { + expectError(t, "a = ", errors.UndefinedIdent) + expectError(t, "1 = 1;", errors.InvalidTarget) + expectError(t, "const a number = 0; a = 1;", errors.AssignConst) + expectError(t, "const a [2]number = [0, 1]; a[0] = 1;", errors.AssignConst) + expectError(t, ` + def fn() { } + const a func () = fn; + a = fn; + `, errors.AssignConst) +} + +func TestErrorLoops(t *testing.T) { + expectError(t, "continue;", errors.InvalidOperation) + expectError(t, "break;", errors.InvalidOperation) +} + +func TestErrorArray(t *testing.T) { + expectError(t, "var a [false]number;", errors.InvalidValue) + expectError(t, "var a [1]number = 1;", errors.TypeMismatch) + expectError(t, "var a []number = [1, 2, 3];", errors.ExpectedExpr) + expectError(t, "var a [2]number; var b [5]number = a;", errors.TypeMismatch) +} + +func TestErrorExprList(t *testing.T) { + expectError(t, "var a number = [1];", errors.InvalidValue) + expectError(t, "var a [1]number = [];", errors.InvalidValue) + expectError(t, "var a [2]number = [1, false];", errors.TypeMismatch) + expectError(t, "var a [2]number = [1, 2, 3];", errors.InvalidValue) + expectError(t, "var a [1]number = [false];", errors.TypeMismatch) + expectError(t, "const a number = [1];", errors.InvalidValue) + expectError(t, "const a [1]number = [];", errors.InvalidValue) + expectError(t, "const a [2]number = [1, false];", errors.TypeMismatch) + expectError(t, "const a [2]number = [1, 2, 3];", errors.InvalidValue) + expectError(t, "const a [1]number = [false];", errors.TypeMismatch) +} + +func TestErrorIndex(t *testing.T) { + expectError(t, "var a [10]number; a[false];", errors.InvalidValue) + expectError(t, "var a [10]number; a[\"bad\"];", errors.InvalidValue) + expectError(t, "var i bool; var a [10]number; a[i];", errors.InvalidValue) + expectError(t, "def fn() { } var a [10]number; a[fn];", errors.InvalidValue) + expectError(t, "def fn() bool { return false; } var a [10]number; a[fn()];", errors.InvalidValue) + expectError(t, "false[0];", errors.NotIndexable) + expectError(t, "10[0];", errors.NotIndexable) + expectError(t, "false[0] = 10;", errors.NotIndexable) +} + +func TestErrorFor(t *testing.T) { + expectError(t, "for a { }", errors.UndefinedIdent) + expectError(t, "for a != 0 { }", errors.UndefinedIdent) + expectError(t, "for a in { }", errors.ExpectedExpr) + expectError(t, "for a in a { }", errors.UndefinedIdent) + expectError(t, "var a number; for i in a { }", errors.TypeMismatch) +} + +func TestErrorStruct(t *testing.T) { + expectError(t, "def A { }", errors.InvalidValue) + expectError(t, "var a number; a.b;", errors.NotStruct) + expectError(t, "def A { var a number; }\nvar a A; a.1;", errors.ExpectedIdent) + expectError(t, "def A { var a number; }\ndef A { }", errors.AlreadyDeclared) + expectError(t, "def A { var a A; }", errors.RecursiveStruct) + expectError(t, "def A { var a number; }\nvar a A;\na.x;", errors.UndefinedIdent) + expectError(t, "def A { var a number; }\nA.a;", errors.InvalidValue) + expectError(t, "def A { var a number; }\nvar a number; a = A;", errors.InvalidValue) + expectError(t, "def A { var a number; }\nvar a A; var b number = a;", errors.TypeMismatch) + expectError(t, "def A { var a number; }\nvar a A; var b a;", errors.ExpectedType) + expectError(t, ` + def A { var a number; } + def B { var a number; } + var a A; + var b B = a; + `, errors.TypeMismatch) + expectError(t, ` + def fn() { + def A { + return; + } + } + `, errors.ExpectedDefinition) + expectError(t, ` + for { + def A { + break; + } + } + `, errors.ExpectedDefinition) + expectError(t, ` + for { + def A { + continue; + } + } + `, errors.ExpectedDefinition) + expectError(t, ` + def A { + var p number; + p = 10; + } + `, errors.ExpectedDefinition) + expectError(t, ` + def A { + var p number; + println("error"); + } + `, errors.ExpectedDefinition) +} |