Skip to content

Commit

Permalink
Add new concept of "compiler operations" + @new
Browse files Browse the repository at this point in the history
"Compiler Operations" are prefixed by @, they are not functions and
just compile-time syntax sugar for common operations. In the instance
of @new, it is a shorthand for taking the expression, creating enough
space in the heap to put in the result, and storing the result there,
and returning that pointer.
  • Loading branch information
mustafaquraish committed Nov 7, 2024
1 parent e2e52a4 commit 794fc08
Show file tree
Hide file tree
Showing 13 changed files with 4,004 additions and 3,710 deletions.
7,570 changes: 3,867 additions & 3,703 deletions bootstrap/stage0.c

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions compiler/ast/nodes.oc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ enum ASTType {
Specialization
ArrayLiteral

// Used for heap-allocating the result of an expression
// @new XYZ => { let a = mem::alloc<typeof(XYZ)>(); *a = XYZ; yield a }
CreateNew

UnaryOp
BinaryOp
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/ast/program.oc
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ def Program::get_type_by_name(&this, name: str, span: Span): &Type {

struct CachedSymbols {
fmt_string_fn: &Symbol
mem_alloc_fn: &Symbol
mem_allocator: &Symbol
}

def Program::iter_namespaces(&this): NSIterator {
Expand Down
13 changes: 11 additions & 2 deletions compiler/lexer.oc
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,21 @@ def Lexer::inc(&this) {
}

def Lexer::peek(&this, offset: u32 = 1): char {
if .cur() == '\0' {
return .cur()
if .i + offset >= .source_len {
return '\0'
}
return .source[.i + offset]
}

def Lexer::starts_with(&this, s: str): bool {
for let i = 0; i < s.len(); i += 1 {
if .peek(i) != s[i] {
return false
}
}
return true
}

def Lexer::lex_char_literal(&this) {
let start_loc = .loc
let start = .i + 1
Expand Down
1 change: 1 addition & 0 deletions compiler/lsp/finder.oc
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ def Finder::find_in_expression(&this, node: &AST): bool {
IntLiteral | FloatLiteral | BoolLiteral |
StringLiteral | CharLiteral |
Null => return .find_in_literal(node)
CreateNew => return .find_in_expression(node.u.child)

FormatStringLiteral => {
let fmt = &node.u.fmt_str
Expand Down
21 changes: 21 additions & 0 deletions compiler/parser.oc
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,27 @@ def Parser::parse_prefix(&this, end_type: TokenType): &AST {
node.u.size_of_type = type
return node
}
// Special compiler operations
TokenType::AtSign => {
let atsign = .consume(TokenType::AtSign)
if not .token_is(TokenType::Identifier) {
.error(Error::new(.token().span, "Expected compiler operation after @"))
return AST::new(Error, atsign.span)
}
let ident = .consume(TokenType::Identifier)
match ident.text {
"new" => {
let expr = .parse_prefix(end_type)
let node = AST::new(CreateNew, atsign.span.join(expr.span))
node.u.child = expr
return node
}
else => {
.error(Error::new(ident.span, "Unknown compiler operation"))
return AST::new(Error, atsign.span)
}
}
}
TokenType::Star => {
let star = .consume(TokenType::Star)
let expr = .parse_prefix(end_type)
Expand Down
27 changes: 27 additions & 0 deletions compiler/passes/code_generator.oc
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,32 @@ def CodeGenerator::gen_constructor(&this, node: &AST, struc: &Structure) {
.out += "}"
}

def CodeGenerator::gen_create_new(&this, node: &AST) {
assert node.type == CreateNew, `Expected CreateNew, got {node.type}`
let child = node.u.child
let var_name = `_new_{.uid++}`

// @new XYZ => {
// let a = alloc_fn(allocator, sizeof(typeof(XYZ)))
// *a = XYZ
// yield a
// }
.out += "({"
.gen_type_and_name(node.etype, var_name)
.out += " = "
.out += .o.program.cached_symbols.mem_alloc_fn.out_name()
.out += "("
.out += .o.program.cached_symbols.mem_allocator.out_name()
.out += ", sizeof("
.gen_type(child.etype)
.out <<= `)); *{var_name} = `
.gen_expression(child)
.out <<= `; {var_name}; `
.out += "})"

var_name.free()
}

//! Generate an expression. If `is_top_level` is true, don't put parens around it.
def CodeGenerator::gen_expression(&this, node: &AST, is_top_level: bool = false) {
match node.type {
Expand Down Expand Up @@ -566,6 +592,7 @@ def CodeGenerator::gen_expression(&this, node: &AST, is_top_level: bool = false)
let bool_lit = node.u.bool_literal
.out += if bool_lit then "true" else "false"
}
CreateNew => .gen_create_new(node)
UnaryOp => match node.u.unary.op {
Address => {
let expr = node.u.unary.expr
Expand Down
2 changes: 1 addition & 1 deletion compiler/passes/mark_dead_code.oc
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def MarkDeadCode::mark(&this, node: &AST) {
Import | Break | Continue | IntLiteral | FloatLiteral |
BoolLiteral | StringLiteral | CharLiteral | Null => {}

Yield | Defer => .mark(node.u.child)
Yield | Defer | CreateNew => .mark(node.u.child)
Return => .mark(node.u.ret.expr)
UnaryOp => .mark(node.u.unary.expr)

Expand Down
20 changes: 17 additions & 3 deletions compiler/passes/register_types.oc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import std::span::{ Span }
import std::compact_map::{ Map }
import @passes::generic_pass::GenericPass
import @ast::scopes::{ Symbol, SymbolType, Scope }
import @ast::program::{ Program, Namespace }
import @ast::program::{ Program, Namespace, CachedSymbols }
import @types::{ Type, BaseType, FunctionType }
import @ast::nodes::{ AST, Function, Structure, Variable,Enum }
import @errors::Error
Expand Down Expand Up @@ -163,6 +163,7 @@ struct Finder {
type: &Type
}

[operator "[]"]
def Finder::to(this, name: str): Finder {
if not .sym? return this
let res = .o.find_in_symbol(.sym, name, allow_templated: true)
Expand All @@ -187,8 +188,21 @@ def Finder::to(this, name: str): Finder {
def RegisterTypes::register_cached_types(&this) {
let finder = Finder(.o, .o.program.global.sym, null)

let res = finder.to("std").to("format")
.o.program.cached_symbols.fmt_string_fn = res.sym
let fmt_string_fn = finder["std"]["format"].sym

let alloc_fn = finder["std"]["mem"]["state"]["alloc_fn"].sym
let allocator = finder["std"]["mem"]["state"]["allocator"].sym

// Sanity checks to make sure the function is what we expect
assert alloc_fn.type == Variable and alloc_fn.u.var.type.base == Function
assert allocator.type == Variable
assert alloc_fn.u.var.type.u.func.params.size == 2

.o.program.cached_symbols = CachedSymbols(
fmt_string_fn: fmt_string_fn,
mem_alloc_fn: alloc_fn,
mem_allocator: allocator,
)
}

def RegisterTypes::create_namespace_scopes(&this, ns: &Namespace, parent: &Namespace) {
Expand Down
13 changes: 13 additions & 0 deletions compiler/passes/typechecker.oc
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,19 @@ def TypeChecker::check_expression_helper(&this, node: &AST, hint: &Type): &Type
arr.u.arr.size = node.u.array_literal.elements.size
return arr
}
ASTType::CreateNew => {
let child_typ = .check_expression(node.u.child)
if not child_typ? return null

if child_typ.base == Pointer {
// FIXME: Should we allow this?
.error(Error::new(node.span, "Cannot use `@new` on a pointer type"))
}

let typ = Type::new_resolved(Pointer, node.span)
typ.u.ptr = child_typ
return typ
}
Error => {
return hint
}
Expand Down
10 changes: 10 additions & 0 deletions tests/bad/compiler_op_new_pointer.oc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// fail: Cannot use `@new` on a pointer type

struct Foo {
a: i32
}

def main() {
let f: Foo
let g = @new &f
}
2 changes: 1 addition & 1 deletion tests/bad/unhandled_expression_sym.oc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// fail: Unexpected token in parse_expression: AtSign
/// fail: Expected compiler operation after @

// Previously, this was getting stuck in an infinite loop
// in the parser.
Expand Down
29 changes: 29 additions & 0 deletions tests/compiler_op_new.oc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// out: "5 10 12 15 20"

struct BST {
x: u32
l: &BST
r: &BST
}

def BST::print(&this) {
if not this? {
return
}
.l.print()
print(`{.x} `)
.r.print()
}

def main() {
let tree = @new BST(
10,
l: @new BST(5, null, null),
r: @new BST(
15,
l: @new BST(12, null, null),
r: @new BST(20, null, null)
)
)
tree.print()
}

0 comments on commit 794fc08

Please sign in to comment.