aboutsummaryrefslogtreecommitdiff
path: root/parser
diff options
context:
space:
mode:
authorJuan J. Martinez <jjm@usebox.net>2022-07-18 07:45:58 +0100
committerJuan J. Martinez <jjm@usebox.net>2022-07-18 07:45:58 +0100
commit8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642 (patch)
treec53977d1284347bb1d5963ddb4dc7723c40c6e55 /parser
downloadmicro-lang-8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642.tar.gz
micro-lang-8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642.zip
First public release
Diffstat (limited to 'parser')
-rw-r--r--parser/parser.go1168
-rw-r--r--parser/parser_test.go250
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)
+}