Skip to content

Commit

Permalink
Initial implementation of Value Enums
Browse files Browse the repository at this point in the history
To not break existing enums, these are temporarily declared with
`venum` keyword. They have a bunch of limitations currently, but some
basic examples work.

In the future, ideally all enums withh be replaced with Value Enums,
since they can also behave like regular ones if no fields are specified
for any of the variants. The only issue is that we should probably
codegen a native C enum in this case for easier comparisons etc.
  • Loading branch information
mustafaquraish committed Oct 31, 2024
1 parent 58d8bd7 commit 1e347fc
Show file tree
Hide file tree
Showing 43 changed files with 3,183 additions and 1,347 deletions.
3,502 changes: 2,202 additions & 1,300 deletions bootstrap/stage0.c

Large diffs are not rendered by default.

56 changes: 54 additions & 2 deletions compiler/ast/nodes.oc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,50 @@ def Enum::get_field(&this, name: str): &Variable {
return null
}

// ValueEnum implementation miscellaneous todos:
// TODO: Handle docs for value enums / their variants
// TODO: Check for duplicate variants in value enums
// TODO: Struct-like variants with named fields (currently only tuple-like)
// TODO: In codegen, store match expression in separate variable to avoid side effects in multiple branches
// TODO: Need to include value enums in struct topological sorting and ordering to ensure correct codegen
struct ValueEnum {
sym: &Symbol
span: Span
variants: &Vector<&ValueEnumVariant>
type: &Type
is_dead: bool
}

def ValueEnum::get_variant(&this, name: str): &ValueEnumVariant {
for variant : .variants.iter() {
if variant.sym.name.eq(name) return variant
}
return null
}

def ValueEnum::new(span: Span): &ValueEnum {
let venom = mem::alloc<ValueEnum>()
venom.variants = Vector<&ValueEnumVariant>::new()
venom.span = span
return venom
}

struct ValueEnumVariant {
sym: &Symbol

// TODO: Named fields? For now variables just have an empty name / symbol
fields: &Vector<&Variable>
parent: &ValueEnum

span: Span
}

def ValueEnumVariant::new(span: Span): &ValueEnumVariant {
let variant = mem::alloc<ValueEnumVariant>()
variant.fields = Vector<&Variable>::new()
return variant
}

struct Function {
sym: &Symbol
params: &Vector<&Variable>
Expand Down Expand Up @@ -194,6 +238,12 @@ def Argument::new(expr: &AST, label_token: &Token = null): &Argument {
return arg
}

enum CallType {
Normal
StructConstructor
ValueEnumConstructor
}

struct FuncCall {
callee: &AST
args: &Vector<&Argument>
Expand All @@ -202,7 +252,7 @@ struct FuncCall {
open_paren_span: Span
close_paren_span: Span

is_constructor: bool
call_type: CallType
is_function_pointer: bool
}

Expand Down Expand Up @@ -341,11 +391,13 @@ struct FormatString {
exprs: &Vector<&AST>
}


struct MatchCase {
cond: &AST
body: &AST
cmp_fn: &Function

// For value enums
args: &Vector<&Variable>
}

def MatchCase::new(cond: &AST, body: &AST): &MatchCase {
Expand Down
8 changes: 7 additions & 1 deletion compiler/ast/program.oc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import std::mem
import std::setjmp::{ ErrorContext }
import std::sv::{ SV }
import @ast::scopes::{ Symbol, Scope }
import @ast::nodes::{ AST, Function, Structure, Enum }
import @ast::nodes::{ AST, Function, Structure, Enum, ValueEnum }
import @ast::operators::{ OperatorOverload }
import @errors::Error
import @types::{ Type, BaseType }
Expand All @@ -22,6 +22,7 @@ struct Namespace {
functions: &Vector<&Function>
structs: &Vector<&Structure>
enums: &Vector<&Enum>
venoms: &Vector<&ValueEnum>
constants: &Vector<&AST>
variables: &Vector<&AST>
imports: &Vector<&AST>
Expand Down Expand Up @@ -63,6 +64,7 @@ def Namespace::new(parent: &Namespace, path: str): &Namespace {
ns.functions = Vector<&Function>::new()
ns.structs = Vector<&Structure>::new()
ns.enums = Vector<&Enum>::new()
ns.venoms = Vector<&ValueEnum>::new()
ns.constants = Vector<&AST>::new()
ns.variables = Vector<&AST>::new()
ns.typedefs = Map<str, &Type>::new()
Expand Down Expand Up @@ -133,6 +135,10 @@ def Namespace::find_importable_symbol(&this, name: str): &Symbol {
if enum_.sym.name.eq(name) return enum_.sym
}

for enum_ : .venoms.iter() {
if enum_.sym.name.eq(name) return enum_.sym
}

if .exported_symbols.contains(name) {
return .exported_symbols.at(name)
}
Expand Down
13 changes: 13 additions & 0 deletions compiler/ast/scopes.oc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import std::map::Map
import std::vector::Vector

import @ast::nodes::{ AST, Structure, Enum, Variable, Function }
import @ast::nodes::{ ValueEnum, ValueEnumVariant }
import @ast::program::Namespace
import @types::Type

Expand All @@ -18,6 +19,10 @@ enum SymbolType {
Namespace
Variable
Constant

ValueEnum
ValueEnumVariant
ValueEnumField
}

struct TemplateInstance {
Expand Down Expand Up @@ -70,13 +75,21 @@ struct Reference {
span: Span
}

struct ValueEnumField {
variant: &ValueEnumVariant
idx: u32
}

union SymbolUnion {
func: &Function
struc: &Structure
enum_: &Enum
ns: &Namespace
type_def: &Type
var: &Variable
venom: &ValueEnum
venom_var: &ValueEnumVariant
venom_field: ValueEnumField
}

struct Symbol {
Expand Down
19 changes: 18 additions & 1 deletion compiler/lsp/finder.oc
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def Finder::find_in_identifier(&this, node: &AST): bool {
}

def Finder::find_in_var(&this, var: &Variable, node: &AST): bool {
if var.sym.span.contains_loc(.loc) {
if var.sym? and var.sym.span.contains_loc(.loc) {
return .set_usage(var.sym, node)
}
if var.parsed_type? and .find_in_type(var.parsed_type) {
Expand Down Expand Up @@ -246,6 +246,11 @@ def Finder::find_in_expression(&this, node: &AST): bool {
for let i = 0; i < stmt.cases.size; i += 1 {
let case_ = stmt.cases.at(i) as &MatchCase
if .find_in_expression(case_.cond) return true
if case_.args? {
for arg in case_.args.iter() {
if .find_in_var(arg, node) return true
}
}
if case_.body? and .find_in_statement(case_.body) return true
}
if stmt.defolt? and .find_in_statement(stmt.defolt) return true
Expand Down Expand Up @@ -421,6 +426,18 @@ def Finder::find_in_program(&this, ns: &Namespace): bool {
}
}

for venom : ns.venoms.iter() {
if venom.sym.span.contains_loc(.loc) return .set_usage(venom.sym, node: null)
for variant in venom.variants.iter() {
if variant.sym.span.contains_loc(.loc) return .set_usage(variant.sym, node: null)
if variant.fields? {
for field : variant.fields.iter() {
if .find_in_var(field, node: null) return true
}
}
}
}

for enm : ns.enums.iter() {
if enm.sym.span.contains_loc(.loc) return .set_usage(enm.sym, node: null)
for field : enm.fields.iter() {
Expand Down
23 changes: 22 additions & 1 deletion compiler/lsp/utils.oc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def gen_type_string(type: &Type, full: bool = true): str => match type.base {
else => `&{gen_type_string(type.u.ptr, full)}`
}
Array => `&{gen_type_string(type.u.arr.elem_type, full)}`
Structure | Enum | Alias => {
Structure | Enum | Alias | ValueEnum => {
if not type.template_instance? return match full {
true => type.sym.display
false => type.sym.name
Expand Down Expand Up @@ -192,6 +192,26 @@ def gen_hover_string(sym: &Symbol): str => match sym.type {
Enum | EnumVariant => `enum {sym.display}`
Structure => `struct {sym.display}`
Namespace => `namespace {sym.display}`
ValueEnum => `venum {sym.display}`
ValueEnumVariant => {
let variant = sym.u.venom_var
let buf = Buffer::make()
buf <<= `venum {variant.sym.display}`
if variant.fields.size > 0 {
buf += "("
for let i = 0; i < variant.fields.size; i += 1 {
if i > 0 then buf += ", "
let field = variant.fields[i]
buf += gen_type_string(field.type, full: false)
}
buf += ")"
}
yield buf.str()
}
ValueEnumField => {
let field = sym.u.venom_field
yield `venum {field.variant.sym.display}.{field.idx}`
}
}

def get_symbol_typedef(sym: &Symbol): &Type => match sym.type {
Expand All @@ -203,6 +223,7 @@ def get_symbol_typedef(sym: &Symbol): &Type => match sym.type {
Enum => sym.u.enum_.type
Structure => sym.u.struc.type
Namespace => null
else => std::panic(f"get_symbol_typedef: unhandled symbol type: {sym.type}")
}

def gen_error_json(err: &Error): &Value {
Expand Down
76 changes: 73 additions & 3 deletions compiler/parser.oc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import std::fs
import @ast::nodes::{ * }
import @ast::program::{ Namespace, Program }
import @ast::operators::{ Operator }
import @ast::scopes::{ Symbol, SymbolType, Template }
import @ast::scopes::{ Symbol, SymbolType, Template, ValueEnumField }
import @attributes::{ Attribute, AttributeType }
import @errors::Error
import @lexer::Lexer
Expand Down Expand Up @@ -141,7 +141,6 @@ def Parser::consume(&this, type: TokenType): &Token {
let tok = .token()
if not .consume_if(type) {
.error_msg(`Expected TokenType::{type.str()}`)
println(f"Error context size: {.program.err_jmp_stack.size}")
.program.get_error_context().jump_back(1)
}
return tok
Expand Down Expand Up @@ -494,6 +493,23 @@ def Parser::parse_match(&this): &AST {

} else {
let cond = .parse_atom(TokenType::Line)

let args: &Vector<&Variable> = null
if .consume_if(OpenParen) {
args = Vector<&Variable>::new()
while not .token_is_eof_or(CloseParen) {
let name = .consume(Identifier)
let var = Variable::new(null)
var.sym = Symbol::from_local_variable(name.text, var, name.span)
args.push(var)

if not .consume_if(Comma) {
break
}
}
.consume(CloseParen)
}

let body = null as &AST
if not .consume_if(TokenType::Line) {
if not .consume_if(TokenType::FatArrow) {
Expand All @@ -505,6 +521,7 @@ def Parser::parse_match(&this): &AST {
}
}
let _case = MatchCase::new(cond, body)
_case.args = args
cases.push(_case)
}

Expand Down Expand Up @@ -1936,6 +1953,46 @@ def Parser::parse_struct(&this): &Structure {
return struc
}

def Parser::parse_venum(&this): &ValueEnum {
let start = .consume(.token().type) // FIXME: Should be TokenType::ValueEnum
// assert start.text == "venum"

let name = .consume(Identifier)

let venom = ValueEnum::new(start.span)
let sym = Symbol::new_with_parent(ValueEnum, .ns, .ns.sym, name.text, name.span)
sym.u.venom = venom
venom.sym = sym
.add_doc_comment(sym, start)

.consume(OpenCurly)
while not .token_is_eof_or(CloseCurly) {
let name = .consume(Identifier)

let variant = ValueEnumVariant::new(name.span)
let vsym = Symbol::new_with_parent(ValueEnumVariant, .ns, sym, name.text, name.span)
vsym.u.venom_var = variant
variant.sym = vsym
variant.parent = venom
.add_doc_comment(vsym, name)

if .consume_if(OpenParen) {
while not .token_is_eof_or(CloseParen) {
let typ = .parse_type()
let var = Variable::new(typ)
variant.fields.push(var)
.consume_if(Comma)
}
.consume(CloseParen)
}

venom.variants += variant
.consume_if(Comma)
}
.consume(CloseCurly)
return venom
}

def Parser::parse_enum(&this): &Enum {
let start = .consume(TokenType::Enum)
let name = .consume(TokenType::Identifier)
Expand Down Expand Up @@ -2040,7 +2097,6 @@ def Parser::parse_namespace_until(&this, end_type: TokenType) {

let ctx = .program.add_error_context()
if ctx.set_jump_point() > 0 {
println("Error in namespace parse context")
// Presumably, we've seen some error while parsing this file. Try to sync
// to the next top-level declaration.
.sync_toplevel()
Expand Down Expand Up @@ -2118,6 +2174,20 @@ def Parser::parse_namespace_until(&this, end_type: TokenType) {
if con? then .ns.constants.push(con)
}
AtSign => .parse_compiler_option()
Identifier => match .token().text {
"venum" => {
let venom = .parse_venum()
if venom? then .ns.venoms.push(venom)
}
else => {
.error(Error::new(.token().span, `Unexpected token in Parser: {.token().type}`))
.curr += 1
}
}
else => {
.error(Error::new(.token().span, `Unexpected token in Parser: {.token().type}`))
.curr += 1
}
else => {
.error(Error::new(.token().span, `Unexpected token in Parser: {.token().type}`))
.curr += 1
Expand Down
Loading

0 comments on commit 1e347fc

Please sign in to comment.