diff --git a/README.md b/README.md index 63cec47..fde6954 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,21 @@ get translated to during compilation. Comparisons and operations on strings are and quite buggy, but you are free to use string literals for prints or simple variable declarations with no issues. Variables cannot be declared with type `void`. +The `string` type actually behaves similarly to how a class would. While using the variable directly +gives you access to the actual string (which in C corresponds to the `char*`), you can use `.len` to +get the length of the string. Note that `len` is an immutable member, so you cannot maually set it. + +``` +let mystring string = "this is my string"; +print(mystring, mystring.len) +``` + +produces this output: + +``` +this is my string 17 +``` + --- ### Functions @@ -133,6 +148,26 @@ let myuser User = User{17, "uan", false} Class types can be used anywhere primitive types can, such as function arguments or other classes' members. +Class members can be defined as immutable with the `immutable` keyword before the member name. +This is checked at compile time and prevents that member's value to be changed after instantiation. +For example, this code would trigger a parse error, `as u.id = 0` attempts to change an immutable +member's value. + +``` +class User { + age int + name string + mail_verified bool + immutable id int +} + +fn change_values(u User) { + u.age = 10; + u.name = "new name"; + u.id = 0; +} +``` + --- ### Print diff --git a/generator.v b/generator.v index 7d28ac7..693a630 100644 --- a/generator.v +++ b/generator.v @@ -179,7 +179,15 @@ fn (mut g Generator) gen_expr(expr Expr) { g.out.write_string(mangle_var(expr.name)) } UnaryExpr { - g.out.write_string('(${mangle_var(expr.ident)}${expr.op})') + if expr.op_on_left { + g.out.write_string('(${expr.op}') + g.gen_expr(expr.expr) + g.out.write_string(')') + } else { + g.out.write_string('(') + g.gen_expr(expr.expr) + g.out.write_string('${expr.op})') + } } BinaryExpr { g.out.write_string('(') diff --git a/one b/one index bc63d64..e817f6f 100755 Binary files a/one and b/one differ diff --git a/parser.v b/parser.v index 87fd000..680aa95 100644 --- a/parser.v +++ b/parser.v @@ -5,12 +5,13 @@ import term // ------------------------------------------- Precedence enum Precedence { - lowest + base assignment // = , +=, -= comparison // ==, !=, <, > sum // +, - product // *, / prefix // -x, !x + suffix // ++, -- call // function() access // . } @@ -22,7 +23,8 @@ fn (p Parser) get_precedence(tok_type TokenType) Precedence { .plus, .minus { .sum } .star, .slash { .product } .dot { .access } - else { .lowest } + .increment, .decrement { .suffix } + else { .base} } } @@ -52,7 +54,7 @@ mut: structs map[string]ClassSymbolInfo } -fn (mut s SymbolTable) define_var(name string, typ string) { +fn (mut s SymbolTable) define_var(name string, typ string, is_const bool) { $if debug { dump(s.variable_scopes.len) } @@ -67,6 +69,7 @@ fn (mut s SymbolTable) define_var(name string, typ string) { s.variable_scopes[s.variable_scopes.len-1][name] = VarSymbolInfo{ type: typ + is_immutable: is_const } } @@ -124,8 +127,9 @@ type Expr = VoidExpr | UnaryExpr | BinaryExpr | IntegerLiteral | RealLiteral | B struct VoidExpr {} struct UnaryExpr { - ident string + expr Expr op string + op_on_left bool } struct BinaryExpr { @@ -281,7 +285,6 @@ fn (mut p Parser) expect(type TokenType) { p.next() } - fn (mut p Parser) expect_ident_is_class(ident string) { if !p.is_ident_class(ident) {panic("Expected type or class type but got identifier")} } @@ -316,15 +319,16 @@ fn (mut p Parser) parse_primary() Expr { p.dump_token() return match token.type { - .integer {IntegerLiteral{token.text.int()}} - .real {RealLiteral{token.text.f32()}} - .boolean {BoolLiteral{token.text == 'true'}} - .string {StringLiteral{token.text}} - .kw_fn {Function{token.text}} - .identifier {p.parse_ident(token.text)} - .type {p.parse_type(token.text)} - .lparen {p.parse_paren()} - .kw_print {p.parse_print()} + .integer {IntegerLiteral{token.text.int()}} + .real {RealLiteral{token.text.f32()}} + .boolean {BoolLiteral{token.text == 'true'}} + .string {StringLiteral{token.text}} + .kw_fn {Function{token.text}} + .identifier {p.parse_ident(token.text)} + .type {p.parse_type(token.text)} + .lparen {p.parse_paren()} + .kw_print {p.parse_print()} + .plus, .minus {p.parse_unary_left(token.text)} else {parse_error("Unexpected Token")} } } @@ -332,15 +336,13 @@ fn (mut p Parser) parse_primary() Expr { fn (mut p Parser) parse_expr(prec Precedence) Expr { mut expr := p.parse_primary() - - for int(prec) < int(p.get_precedence(p.peek().type)) { op_tok := p.next() - if op_tok.type == .dot { - expr = p.parse_member_access(expr) - } else { - expr = p.parse_binary(expr, op_tok.text, p.get_precedence(op_tok.type)) + expr = match op_tok.type { + .dot {p.parse_member_access(expr)} + .increment, .decrement {p.parse_unary_right(expr, op_tok.text)} + else {p.parse_binary(expr, op_tok.text, p.get_precedence(op_tok.type))} } } @@ -356,7 +358,6 @@ fn (mut p Parser) parse_ident(ident string) Expr { } return match p.peek().type { - .increment, .decrement {UnaryExpr {ident: ident, op: p.next().text}} .lparen {p.parse_call(ident)} else {Variable{ident}} } @@ -401,7 +402,7 @@ fn (mut p Parser) parse_class_inst(name string) ClassInstantiation { if p.peek().type != .rbracket { for { - member_values << p.parse_expr(.lowest) + member_values << p.parse_expr(.base) if p.peek().type == .comma { p.next() } else { @@ -419,7 +420,7 @@ fn (mut p Parser) parse_call(name string) FnCall { if p.peek().type != .rparen { for { - args << p.parse_expr(.lowest) + args << p.parse_expr(.base) if p.peek().type == .comma { p.next() } else { @@ -436,7 +437,7 @@ fn (mut p Parser) parse_print() PrintExpr { mut exprs := []Expr{} mut types := []string{} for p.peek().type != .rparen { - expr := p.parse_expr(.lowest) + expr := p.parse_expr(.base) exprs << expr types << p.get_expr_type(expr) if p.peek().type == .comma { @@ -449,18 +450,26 @@ fn (mut p Parser) parse_print() PrintExpr { return PrintExpr{exprs: exprs, types: types} } +fn (mut p Parser) parse_unary_right(expr Expr, op string) UnaryExpr { + if p.get_expr_is_immutable(expr) { + parse_error("Cannot perform operation ${op} on immutable expression ${expr}") + } + if !p.is_op_valid_for_type(p.get_expr_type(expr), op) { + parse_error("Invalid operation ${op} for type ${p.get_expr_type(expr)}") + } + return UnaryExpr {expr: expr, op: op, op_on_left: false} +} + +fn (mut p Parser) parse_unary_left(op string) UnaryExpr { + expr := p.parse_expr(.prefix) + return UnaryExpr{expr: expr, op: op, op_on_left: true} +} + fn (mut p Parser) parse_binary(left Expr, op string, prec Precedence) BinaryExpr { if op in ['=', '+=', '-=', '*=', '/='] { - if left is MemberAccess { - from_type := p.get_expr_type(left.from) - if class_info := p.symbols.lookup_class(from_type) { - if member_info := class_info.members_info[left.member] { - if member_info.is_immutable { - parse_error("Cannot assign to immutable member ${left.member} in class ${from_type}") - } - } - } + if p.get_expr_is_immutable(left) { + parse_error("Cannot assign to immutable expression ${left}") } } @@ -478,7 +487,7 @@ fn (mut p Parser) parse_type(type string) Expr { if p.peek().type == .lparen { p.next() - expr := p.parse_expr(.lowest) + expr := p.parse_expr(.base) p.expect(.rparen) return TypeCast { @@ -491,7 +500,7 @@ fn (mut p Parser) parse_type(type string) Expr { } fn (mut p Parser) parse_paren() ParenExpr { - expr := p.parse_expr(.lowest) + expr := p.parse_expr(.base) p.expect(.rparen) return ParenExpr{expr: expr} } @@ -508,6 +517,20 @@ fn (mut p Parser) is_ident_class(ident string) bool { return p.symbols.lookup_class(ident) != none } +fn (mut p Parser) get_expr_is_immutable(expr Expr) bool { + return match expr { + ParenExpr {p.get_expr_is_immutable(expr.expr)} + UnaryExpr {p.get_expr_is_immutable(expr.expr)} + TypeCast {p.get_expr_is_immutable(expr.expr)} + MemberAccess { + classinfo := p.symbols.lookup_class(p.get_expr_type(expr.from)) or {parse_error("Invalid class")} + memberinfo := classinfo.members_info[expr.member] or {parse_error("Undefined member ${expr.member}")} + memberinfo.is_immutable + } + else {true} + } +} + fn (mut p Parser) get_expr_type(expr Expr) string { return match expr { ParenExpr {p.get_expr_type(expr.expr)} @@ -517,6 +540,7 @@ fn (mut p Parser) get_expr_type(expr Expr) string { StringLiteral {'string'} VoidExpr {'void'} TypeExpr {expr.name} + UnaryExpr {p.get_expr_type(expr.expr)} BinaryExpr { p.check_binary_expr_types(expr) left_t := p.get_expr_type(expr.left) @@ -548,7 +572,7 @@ fn (mut p Parser) get_expr_type(expr Expr) string { fn (mut p Parser) is_op_valid_for_type(type string, op string) bool { global := ['=', '==', '!='] mut legal_ops := match type { - 'int', 'real' {['+', '-', '*', '/', '<', '>', '<=', '>=', '++', '--', '+=', '-=', '*=', '/=']} + 'int', 'real' {['+', '-', '*', '/', '<', '>', '<=', '>=', '++', '--', '+=', '-=', '*=', '/=', '++', '--']} 'bool' {['=']} else {[]} } @@ -605,7 +629,7 @@ fn (mut p Parser) parse_var_decl(is_const bool) VarDecl { } p.expect(.equals) - val := p.parse_expr(.lowest) + val := p.parse_expr(.base) if type_tok.text == 'void' { parse_error("Cannot declare a variable of type void") @@ -615,7 +639,7 @@ fn (mut p Parser) parse_var_decl(is_const bool) VarDecl { } p.expect(.semicolon) - p.symbols.define_var(name_tok.text, type_tok.text) + p.symbols.define_var(name_tok.text, type_tok.text, is_const) return VarDecl { name: name_tok.text @@ -659,7 +683,7 @@ fn (mut p Parser) parse_func_decl() FuncDecl { } p.next() params << Param{p_name, p_type} - p.symbols.define_var(p_name, p_type) + p.symbols.define_var(p_name, p_type, false) if p.peek().type == .comma { p.next() @@ -725,7 +749,7 @@ fn (mut p Parser) parse_class() ClassDecl { fn (mut p Parser) parse_return_stmt() ReturnStmt { p.expect(.kw_return) - expr := p.parse_expr(.lowest) + expr := p.parse_expr(.base) p.expect(.semicolon) @@ -736,7 +760,7 @@ fn (mut p Parser) parse_return_stmt() ReturnStmt { fn (mut p Parser) parse_if() IfStmt { p.expect(.kw_if) - cond := p.parse_expr(.lowest) + cond := p.parse_expr(.base) if p.get_expr_type(cond) != 'bool' { parse_error('If condition must be of type bool') } @@ -754,7 +778,7 @@ fn (mut p Parser) parse_else() ElseStmt { fn (mut p Parser) parse_elif() ElifStmt { p.expect(.kw_elif) - cond := p.parse_expr(.lowest) + cond := p.parse_expr(.base) if p.get_expr_type(cond) != 'bool' { parse_error('If condition must be of type bool') } @@ -764,7 +788,7 @@ fn (mut p Parser) parse_elif() ElifStmt { } fn (mut p Parser) parse_expr_stmt() ExprStmt { - expr := p.parse_expr(.lowest) + expr := p.parse_expr(.base) p.expect(.semicolon) return ExprStmt {