diff options
author | Himbeer <himbeer@disroot.org> | 2024-08-30 22:03:13 +0200 |
---|---|---|
committer | Himbeer <himbeer@disroot.org> | 2024-08-30 22:03:13 +0200 |
commit | 0e806f6a119f3f4ee31d3b52ec8eaf4415f2da4e (patch) | |
tree | 803c829390b5e517da98218ecd740882f4dff517 | |
parent | 11de07f6cc4c78169987670db5b5417a0176f2bb (diff) |
Add support for assignments to mutables
-rw-r--r-- | TYPE_SYSTEM.md | 5 | ||||
-rw-r--r-- | decl.go | 14 | ||||
-rw-r--r-- | expression.go | 14 | ||||
-rw-r--r-- | generate.go | 26 | ||||
-rw-r--r-- | generate_error.go | 18 | ||||
-rw-r--r-- | lex.go | 2 | ||||
-rw-r--r-- | parse.go | 37 | ||||
-rw-r--r-- | token.go | 3 |
8 files changed, 107 insertions, 12 deletions
diff --git a/TYPE_SYSTEM.md b/TYPE_SYSTEM.md index 56baa91..fb45232 100644 --- a/TYPE_SYSTEM.md +++ b/TYPE_SYSTEM.md @@ -17,8 +17,9 @@ SPDX-License-Identifier: CC-BY-NC-SA-4.0 # Declaration and mutability -* Variables and constants need to be declared before being used +* Variables need to be declared before being used * All declarations (including globals and implicit declarations from imports) must be above their first use * Redeclarations ("shadowing") are forbidden * Variables may be changed using the "assign" statement -* Constants cannot be changed +* Constants cannot be changed, but mutables can +* Local mutables are allocated on the stack, local constants are QBE temporaries @@ -8,7 +8,7 @@ var funcs = map[string]struct{}{} var localConsts = map[string]map[string]string{} var localMuts = map[string]map[string]struct{}{} -func isDeclared(name string, toplevel bool) bool { +func isDeclared(name string, toplevel bool) (ok bool, mutable bool) { if toplevel { return isDeclaredToplevel(name) } @@ -16,13 +16,13 @@ func isDeclared(name string, toplevel bool) bool { return isDeclaredLocal(currentFunc, name) } -func isDeclaredToplevel(name string) bool { - _, ok := funcs[name] - return ok +func isDeclaredToplevel(name string) (ok bool, mutable bool) { + _, ok = funcs[name] + return } -func isDeclaredLocal(function string, local string) bool { +func isDeclaredLocal(function string, local string) (ok bool, mutable bool) { _, constant := localConsts[function][local] - _, mutable := localMuts[function][local] - return constant || mutable + _, mutable = localMuts[function][local] + return constant || mutable, mutable } diff --git a/expression.go b/expression.go index 7693f79..285cac3 100644 --- a/expression.go +++ b/expression.go @@ -134,6 +134,20 @@ func (m *mutStmt) markDecl() {} func (m *mutStmt) line() int { return m.ln } +type assignStmt struct { + name string + value exprExpr + ln int +} + +func (a *assignStmt) markExpr() {} + +func (a *assignStmt) markStmt() {} + +func (a *assignStmt) markDecl() {} + +func (a *assignStmt) line() int { return a.ln } + type exprExpr interface { expression markExprExpr() diff --git a/generate.go b/generate.go index 511ce05..187ec8f 100644 --- a/generate.go +++ b/generate.go @@ -80,7 +80,7 @@ func generateToplevel(toplevel expression, w io.Writer) error { } func generateFunction(function *functionExpr, w io.Writer) error { - if isDeclared(function.name, true) { + if ok, _ := isDeclared(function.name, true); ok { return errAlreadyDeclared{name: function.name, line: function.line()} } @@ -126,6 +126,8 @@ func generateStatement(stmt statementExpr, w io.Writer) error { return generateConstStmt(s, false, w) case *mutStmt: return generateMutStmt(s, false, w) + case *assignStmt: + return generateAssignStmt(s, w) } return nil @@ -143,7 +145,7 @@ func generateReturnStmt(stmt *returnStmt, w io.Writer) error { } func generateConstStmt(stmt *constStmt, toplevel bool, w io.Writer) error { - if isDeclared(stmt.name, toplevel) { + if ok, _ := isDeclared(stmt.name, toplevel); ok { return errAlreadyDeclared{name: stmt.name, line: stmt.line()} } @@ -159,7 +161,7 @@ func generateConstStmt(stmt *constStmt, toplevel bool, w io.Writer) error { } func generateMutStmt(stmt *mutStmt, toplevel bool, w io.Writer) error { - if isDeclared(stmt.name, toplevel) { + if ok, _ := isDeclared(stmt.name, toplevel); ok { return errAlreadyDeclared{name: stmt.name, line: stmt.line()} } @@ -175,6 +177,24 @@ func generateMutStmt(stmt *mutStmt, toplevel bool, w io.Writer) error { return nil } +func generateAssignStmt(stmt *assignStmt, w io.Writer) error { + ok, mutable := isDeclared(stmt.name, false) + if !ok { + return errUndeclared{name: stmt.name, line: stmt.line()} + } + if !mutable { + return errImmutable{name: stmt.name, line: stmt.line()} + } + + value, err := stmt.value.generate(w) + if err != nil { + return err + } + + fmt.Fprintf(w, "storew %s, %%%s\n", value, stmt.name) + return nil +} + func (e *equalityExpr) generate(w io.Writer) (string, error) { lhs, err := e.lhs.generate(w) if err != nil { diff --git a/generate_error.go b/generate_error.go index 7884151..8dfd964 100644 --- a/generate_error.go +++ b/generate_error.go @@ -14,3 +14,21 @@ type errAlreadyDeclared struct { func (e errAlreadyDeclared) Error() string { return fmt.Sprintf("%d: redeclaration of %q\n", e.line, e.name) } + +type errUndeclared struct { + name string + line int +} + +func (e errUndeclared) Error() string { + return fmt.Sprintf("%d: undeclared variable %q\n", e.line, e.name) +} + +type errImmutable struct { + name string + line int +} + +func (e errImmutable) Error() string { + return fmt.Sprintf("%d: cannot assign to constant %q\n", e.line, e.name) +} @@ -300,6 +300,8 @@ func addKeywordOrIdentifier(name string, line int) token { return token{kind: constKeyword, line: line} case "mut": return token{kind: mut, line: line} + case "assign": + return token{kind: assign, line: line} default: return token{kind: identifier, value: name, line: line} } @@ -326,6 +326,14 @@ func parseDeclaration(toks *tokens) (declStmt, error) { return mutable, nil } + assignment, err := parseAssignStmt(toks) + if err != nil { + return nil, err + } + if assignment != nil { + return assignment, nil + } + return nil, nil } @@ -387,6 +395,35 @@ func parseMutStmt(toks *tokens) (*mutStmt, error) { return &mutStmt{name: nameTok.value, initial: expr, ln: line}, nil } +func parseAssignStmt(toks *tokens) (*assignStmt, error) { + ok, err := toks.match(assign) + if err != nil { + return nil, err + } + if !ok { + toks.unreadToken() + return nil, nil + } + + line := toks.current().line + + nameTok, ok := toks.consumeToken() + if !ok { + return nil, unexpectedEOF + } + + if err := toks.mustMatch(equals); err != nil { + return nil, err + } + + expr, err := parseExpression(toks) + if err != nil { + return nil, err + } + + return &assignStmt{name: nameTok.value, value: expr, ln: line}, nil +} + func parseExpression(toks *tokens) (exprExpr, error) { return parseEquality(toks) } @@ -104,6 +104,8 @@ func (k tokenKind) String() string { return "const" case mut: return "mut" + case assign: + return "assign" } return "invalid token" @@ -159,6 +161,7 @@ const ( ret constKeyword mut + assign ) type token struct { |