Skip to content

Commit

Permalink
Add print functions to type system
Browse files Browse the repository at this point in the history
  • Loading branch information
mustafaquraish committed Nov 15, 2024
1 parent d4e21dc commit 342a86f
Show file tree
Hide file tree
Showing 8 changed files with 3,278 additions and 3,223 deletions.
6,379 changes: 3,205 additions & 3,174 deletions bootstrap/stage0.c

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions compiler/main.oc
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def run_executable(argc: i32, argv: &str) {
}

log(Info, f"{cmd}")
let exit_code = system(cmd.str())
let ret = system(cmd.str())
let exit_code = (ret >> 8) & 0xFF // Effectively WEXITSTATUS(ret)
log(Info, f"Exited with code: {exit_code}")
std::exit(exit_code)
}
Expand All @@ -109,7 +110,6 @@ def parse_args(argc: &i32, argv: &&str, program: &Program) {
"-d" => debug = true
"-n" => {
compile_c = false
program.keep_all_code = true
}
"--no-dce" => program.keep_all_code = true
"-o" => exec_path = shift_args(argc, argv)
Expand Down
28 changes: 20 additions & 8 deletions compiler/passes/code_generator.oc
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,22 @@ def CodeGenerator::get_op(&this, node: &AST): str => match node.type {
else => std::panic(`Unknown op type in get_op: {node.type}`)
}
def CodeGenerator::gen_internal_print(&this, node: &AST) {
// NOTE: This function doesn't actually "need" to return a boolean, this is just to make the call in
// gen_call a little nicer so we can match on the callee name.
def CodeGenerator::gen_internal_print(&this, node: &AST, newline_after: bool, is_stderr: bool): bool {
let callee = node.u.call.callee
let newline_after = callee.u.ident.name.eq("println")

.out += "printf("
if is_stderr {
.out += "fprintf(stderr, "
} else {
.out += "printf("
}

let args = node.u.call.args
let first = args.at(0)
if first.expr.type == FormatStringLiteral {
.gen_format_string_variadic(first.expr, newline_after)
.out += ")"
return
return true
}

for let i = 0; i < args.size; i += 1 {
Expand All @@ -103,6 +107,8 @@ def CodeGenerator::gen_internal_print(&this, node: &AST) {
if i == 0 and newline_after then .out += "\"\\n\""
}
.out += ")"

return true
}

//* Generate all the escape sequences in format string part
Expand Down Expand Up @@ -563,9 +569,15 @@ def CodeGenerator::gen_expression(&this, node: &AST, is_top_level: bool = false)
let callee = node.u.call.callee

// FIXME: Re-do abomination of hacky-IO with some sort of variadics?
if callee.type == Identifier and (callee.u.ident.name.eq("print") or callee.u.ident.name.eq("println")) {
.gen_internal_print(node)
return
if callee.type == Identifier {
let matched = match callee.u.ident.name {
"print" => .gen_internal_print(node, newline_after: false, is_stderr: false)
"println" => .gen_internal_print(node, newline_after: true, is_stderr: false)
"eprint" => .gen_internal_print(node, newline_after: false, is_stderr: true)
"eprintln" => .gen_internal_print(node, newline_after: true, is_stderr: true)
else => false
}
if matched then return
}

let sym = callee.symbol()
Expand Down
29 changes: 29 additions & 0 deletions compiler/passes/register_types.oc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import std::span::{ Span }
import std::compact_map::{ Map }
import std::vector::{ Vector }
import @passes::generic_pass::GenericPass
import @ast::scopes::{ Symbol, SymbolType, Scope }
import @ast::program::{ Program, Namespace, CachedSymbols }
Expand Down Expand Up @@ -131,6 +132,28 @@ def RegisterTypes::register_alias(&this, name: str, orig: &Type) {
}


def RegisterTypes::register_print_function(&this, name: str) {
let sym = Symbol::new(Function, ns: null, name, name, name, Span::default())
let func = Function::new()
func.sym = sym
func.sym.u.func = func

let params = Vector<&Variable>::new()
let param = Variable::new(.o.program.get_type_by_name("str", Span::default()))
param.sym = Symbol::from_local_variable("fmt", param, Span::default())
params.push(param)

func.params = params
func.is_variadic = true
func.is_variadic_format = true

let typ = Type::new_resolved(Function, Span::default())
typ.u.func = FunctionType(func, params, .o.program.get_type_by_name("void", Span::default()), true)
func.type = typ

.o.insert_into_scope_checked(sym, name)
}

def RegisterTypes::register_builtin_types(&this) {
for let i = 0; i < BaseType::NUM_BASE_TYPES as u32; i += 1 {
.register_base_type(i as BaseType)
Expand All @@ -154,6 +177,12 @@ def RegisterTypes::register_builtin_types(&this) {
.register_alias("str", typ)
}

// Register built in print-functions
.register_print_function("print")
.register_print_function("println")
.register_print_function("eprint")
.register_print_function("eprintln")

.o.pop_scope()
}

Expand Down
48 changes: 11 additions & 37 deletions compiler/passes/typechecker.oc
Original file line number Diff line number Diff line change
Expand Up @@ -516,32 +516,6 @@ def TypeChecker::check_method_call(&this, method: &Function, node: &AST) {
node.u.call.args.push_front(Argument::new(first_arg))
}

def TypeChecker::check_internal_print(&this, node: &AST): &Type {
let call = &node.u.call
let args = call.args
if args.size < 1 {
let span = call.open_paren_span.join(call.close_paren_span)
.o.error(Error::new(
span, "Function requires at least one argument"
))
return .get_base_type(Void, node.span)
}
let first = args.at(0)
let first_type = .check_expression(first.expr)
if first_type? and not first_type.is_str() {
.o.error(Error::new(
first.expr.span, `First argument must be a string literal, got {first_type.str()}`
))
}

for arg : args.iter() {
.check_expression(arg.expr)
.call_dbg_on_enum_value(&arg.expr)
}

return .get_base_type(Void, node.span)
}

def TypeChecker::check_union_constructor(&this, node: &AST, params: &Vector<&Variable>) {
let args = node.u.call.args
if args.size != 1 {
Expand Down Expand Up @@ -803,16 +777,6 @@ def TypeChecker::check_call(&this, node: &AST, hint: &Type = null): &Type {
let callee = node.u.call.callee
let args = node.u.call.args

// FIXME: this is a fucking abonimation
if callee.type == ASTType::Identifier {
callee.u.ident.is_function = false
let name = callee.u.ident.name

if name.eq("print") or name.eq("println") {
return .check_internal_print(node)
}
}

let res = match callee.type {
Member => .check_member(callee, is_being_called: true)
Identifier | NSLookup | Specialization => {
Expand Down Expand Up @@ -890,12 +854,22 @@ def TypeChecker::check_call(&this, node: &AST, hint: &Type = null): &Type {
}
match arg.type {
StringLiteral | FormatStringLiteral => {}
else => {

// If the type is NOT a string, we should have errored already (variadic-format functions
// check for this). However, if it IS a string, but not a string literal, then we want to
// complain since printf-like functions require a string literal.
else => if arg.etype == .get_type_by_name("str", arg.span) {
.error(Error::new(
arg.span, "Expected a string literal for variadic-format function"
))
}
}

// For variadic arguments, if they are enums we should call the dbg function on them
for let i = params.size; i < args.size; i++ {
let arg = args[i]
.call_dbg_on_enum_value(&arg.expr)
}
}

if func.orig? {
Expand Down
4 changes: 3 additions & 1 deletion meta/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ def handle_test(compiler: str, num: int, path: Path, expected: Expected, debug:
return False, f"Expected exit code {expected.value}, but got {process.returncode}", path

if expected.type == Result.EXIT_WITH_OUTPUT:
output = process.stdout.decode('utf-8').strip()
stdout_output = process.stdout.decode('utf-8').strip()
stderr_output = process.stderr.decode('utf-8').strip()
output = (stdout_output + '\n' + stderr_output).strip()
expected_out = literal_eval(expected.value).strip()
if output != expected_out:
return False, f'Incorrect output produced\n expected: {repr(expected_out)}\n got: {repr(output)}', path
Expand Down
2 changes: 1 addition & 1 deletion tests/bad/print_first_not_string.oc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// fail: First argument must be a string literal
/// fail: Expected `fmt` with type str, but got u32
def main() {
print(5)
Expand Down
7 changes: 7 additions & 0 deletions tests/eprint_functions.oc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// out: "foo=bar\nbaz"
def main() {
eprint("foo=")
eprintln("bar")
eprintln("baz")
}

0 comments on commit 342a86f

Please sign in to comment.