Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions analyser/module_analyser_stmt.c2
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,11 @@ fn void Analyser.analyseAssertStmt(Analyser* ma, Stmt* s) {

Expr* inner = a.getInner();
ma.checker.check(builtins[BuiltinKind.Bool], qt, a.getInner2(), inner.getLoc());

if (a.getCall()) {
qt = ma.analyseExpr(a.getCall2(), true, RHS);
if (qt.isInvalid()) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which case will a.getCall() be nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the call expression is only constructed by the parser if asserts are enabled. The expression is always parsed and stored for the analyser.

}
}

fn void Analyser.analyseReturnStmt(Analyser* ma, Stmt* s) {
Expand Down
15 changes: 12 additions & 3 deletions ast/assert_stmt.c2
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,41 @@ import string_buffer;
public type AssertStmt struct @(opaque) {
Stmt base;
Expr* inner;
Expr* call;
}

public fn AssertStmt* AssertStmt.create(ast_context.Context* c,
SrcLoc loc,
Expr* inner)
SrcLoc loc,
Expr* inner,
Expr* call)
{
AssertStmt* s = c.alloc(sizeof(AssertStmt));
s.base.init(StmtKind.Assert, loc);
s.inner = inner;
s.call = call;
#if AstStatistics
Stats.addStmt(StmtKind.Assert, sizeof(AssertStmt));
#endif
return s;
}

fn Stmt* AssertStmt.instantiate(AssertStmt* s, Instantiator* inst) {
return (Stmt*)AssertStmt.create(inst.c, s.base.loc, s.inner.instantiate(inst));
return (Stmt*)AssertStmt.create(inst.c, s.base.loc, s.inner.instantiate(inst),
s.call ? s.call.instantiate(inst) : nil);
}

public fn Expr* AssertStmt.getInner(const AssertStmt* s) { return s.inner; }

public fn Expr** AssertStmt.getInner2(AssertStmt* s) { return &s.inner; }

public fn Expr* AssertStmt.getCall(const AssertStmt* s) { return s.call; }

public fn Expr** AssertStmt.getCall2(AssertStmt* s) { return &s.call; }

fn void AssertStmt.print(const AssertStmt* s, string_buffer.Buf* out, u32 indent) {
s.base.printKind(out, indent);
out.newline();
s.inner.print(out, indent + 1);
if (s.call) s.call.print(out, indent + 1);
}

2 changes: 1 addition & 1 deletion ast/utils.c2
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ static_assert(80, sizeof(FunctionDecl));
static_assert(8, sizeof(Stmt));
static_assert(12, sizeof(GotoStmt));
static_assert(24, sizeof(LabelStmt));
static_assert(16, sizeof(AssertStmt));
static_assert(24, sizeof(AssertStmt));
static_assert(8, sizeof(DeclStmt));
static_assert(16, sizeof(SwitchStmt));
static_assert(24, sizeof(AsmStmt));
Expand Down
4 changes: 2 additions & 2 deletions common/ast_builder.c2
Original file line number Diff line number Diff line change
Expand Up @@ -883,8 +883,8 @@ public fn SwitchCase* Builder.actOnCase(Builder* b,
conds, num_conds, stmts, num_stmts);
}

public fn Stmt* Builder.actOnAssertStmt(Builder* b, SrcLoc loc, Expr* inner) {
return (Stmt*)AssertStmt.create(b.context, loc, inner);
public fn Stmt* Builder.actOnAssertStmt(Builder* b, SrcLoc loc, Expr* inner, Expr* call) {
return (Stmt*)AssertStmt.create(b.context, loc, inner, call);
}

public fn Stmt* Builder.actOnBreakStmt(Builder* b, SrcLoc loc) {
Expand Down
3 changes: 2 additions & 1 deletion compiler/compiler.c2
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ fn void Compiler.build(Compiler* c,
c.astPool,
c.builder,
&c.kwinfo,
target.getFeatures());
target.getFeatures(),
c.target.hasAsserts());

ast.initialize(c.context, c.astPool, c.targetInfo.intWidth / 8, color.useColor());

Expand Down
2 changes: 2 additions & 0 deletions generator/ast_visitor.c2
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ fn void Visitor.handleStmt(Visitor* v, Stmt* s) {
case Assert:
AssertStmt* a = (AssertStmt*)s;
v.handleExpr(a.getInner());
Expr* call = a.getCall();
if (call) v.handleExpr(call);
break;
}
}
Expand Down
15 changes: 0 additions & 15 deletions generator/c/c_generator.c2
Original file line number Diff line number Diff line change
Expand Up @@ -1431,17 +1431,6 @@ const char[] C_defines =
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
```;

const char[] C2_assert =
```c
int dprintf(int fd, const char *format, ...);
void abort(void);
static int c2_assert(const char* filename, int line, const char* funcname, const char* condstr) {
dprintf(2, "%s:%d: function %s: Assertion failed: %s\n", filename, line, funcname, condstr);
abort();
return 0;
}
```;

const char[] C2_strswitch =
```c
static int c2_strswitch(const char* s1, const char* s2) {
Expand Down Expand Up @@ -1508,10 +1497,6 @@ fn void Generator.emit_external_header(Generator* gen, bool enable_asserts, cons
#endif
out.add("#define to_container(type, member, ptr) ((type*)((char*)(ptr) - offsetof(type, member)))\n\n");

if (enable_asserts) {
out.add(C2_assert);
}

// for switch(str):
// String format: strings are prefixed by a length byte and concatenated as a single character array.
// This minimizes memory use, cache misses and avoids extra symbols.
Expand Down
18 changes: 5 additions & 13 deletions generator/c/c_generator_stmt.c2
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
module c_generator;

import ast local;
import source_mgr;
import string_buffer;

fn void Generator.emitVarDecl(Generator* gen, VarDecl* vd, string_buffer.Buf* out, bool emit_init, bool first) {
Expand Down Expand Up @@ -240,19 +239,12 @@ fn void Generator.emitStmt(Generator* gen, Stmt* s, u32 indent, bool newline) {
if (!gen.enable_asserts) out.print(";//assert");
AssertStmt* a = (AssertStmt*)s;
out.add1('(');
Expr* inner = a.getInner();
gen.emitExpr(out, inner);
gen.emitExpr(out, a.getInner());
out.add1(')');
if (gen.enable_asserts) {
source_mgr.Location loc = gen.sm.locate(s.getLoc());
const char* funcname = gen.cur_function.asDecl().getFullName();
out.print(" || c2_assert(\"%s\", %d, \"%s\", \"", loc.filename, loc.line, funcname);
// encode expression as a string
string_buffer.Buf* str = string_buffer.create(128, false, 0);
inner.printLiteral(str);
out.encodeBytes(str.data(), str.size(), '"');
str.free();
out.add("\")");
Expr* call = a.getCall();
if (call) {
out.add(" || ");
gen.emitExpr(out, call);
}
out.add(";\n");
break;
Expand Down
14 changes: 14 additions & 0 deletions libs/libc/c2_assert.c2i
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module c2_assert;

import stdio local;
import stdlib local;

public fn i32 c2_assert_fail(const char* filename @(auto_file),
u32 line @(auto_line),
const char* funcname @(auto_func),
const char* condstr)
{
dprintf(2, "%s:%d: function %s: Assertion failed: %s\n", filename, line, funcname, condstr);
abort();
return 0;
}
2 changes: 1 addition & 1 deletion libs/libc/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ modules:
- sys_utsname
- uio
- unistd

- c2_assert
9 changes: 8 additions & 1 deletion parser/c2_parser.c2
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ public type Parser struct @(opaque) {
const string_list.List* features;
const keywords.Info* kwinfo;
bool is_interface;
bool has_asserts;
u32 va_list_idx;
u32 varargs_idx;
u32 stdarg_idx;
u32 c2_assert_idx;
u32 c2_assert_fail_idx;

stmt_list.List* stmt_lists;
u32 stmt_list_count;
Expand All @@ -77,7 +80,8 @@ public fn Parser* create(SourceMgr* sm,
string_pool.Pool* pool,
ast_builder.Builder* builder,
const keywords.Info* kwinfo,
const string_list.List* features)
const string_list.List* features,
bool has_asserts)
{
Parser* p = calloc(1, sizeof(Parser));
p.sm = sm;
Expand All @@ -86,9 +90,12 @@ public fn Parser* create(SourceMgr* sm,
p.builder = builder;
p.features = features;
p.kwinfo = kwinfo;
p.has_asserts = has_asserts;
p.va_list_idx = pool.addStr("va_list", true);
p.varargs_idx = pool.addStr("varargs", true);
p.stdarg_idx = pool.addStr("stdarg", true);
p.c2_assert_idx = p.pool.addStr("c2_assert", true);
p.c2_assert_fail_idx = p.pool.addStr("c2_assert_fail", true);

// create a stack of stmt_lists to re-use (resize stack if needed)
p.stmt_lists = malloc(NumStmtLists * sizeof(stmt_list.List));
Expand Down
29 changes: 28 additions & 1 deletion parser/c2_parser_stmt.c2
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import constants local;
import token local;
import src_loc local;
import string_list;
import string_buffer;

fn Stmt* Parser.parseStmt(Parser* p) {
// TODO use Jump Table (combined one for multiple purposes?)
Expand Down Expand Up @@ -387,10 +388,36 @@ fn Stmt* Parser.parseAssertStmt(Parser* p) {
SrcLoc loc = p.tok.loc;
p.consumeToken();
p.expectAndConsume(Kind.LParen);
SrcLoc eloc = p.tok.loc;
Expr* inner = p.parseExpr();
u32 elen = p.prev_loc - eloc;
p.expectAndConsume(Kind.RParen);
u32 loc_end = p.prev_loc;
p.expectAndConsume(Kind.Semicolon);
return p.builder.actOnAssertStmt(loc, inner);

Expr* call = nil;
if (p.has_asserts) {
// add c2_assert.fail func designator
Ref[2] ref;
ref[0].loc = loc;
ref[0].name_idx = p.c2_assert_idx;
ref[1].loc = loc;
ref[1].name_idx = p.c2_assert_fail_idx;
p.addImplicitImport(p.c2_assert_idx, false);
Expr* func = p.builder.actOnMemberExpr(nil, ref, 2);

// encode expression as a string
string_buffer.Buf* buf = p.tokenizer.buf;
buf.clear();
inner.printLiteral(buf);
u32 msg_len = buf.size();
u32 msg_idx = p.pool.add(buf.data(), msg_len, true);
Expr* arg = p.builder.actOnStringLiteral(eloc, elen, msg_idx, msg_len);

// create call expression `assert.fail("expression")
call = p.builder.actOnCallExpr(loc, loc_end, func, &arg, 1);
}
return p.builder.actOnAssertStmt(loc, inner, call);
Copy link
Member

@bvdberg bvdberg Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parser now insert implicit Imports. I dont really like that. The parser should convert the tokens into the AST. The analyser can then do its thing. This breaks that division...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is sloppy. I suggest this:

  • rename the c2_assert module in the C library as assert and c2_assert.c2_assert_fail as assert.fail for clarity.
  • add a flag in b.ast to tell the analyser to add assert to the scope. This would be set in ast_builder and handled in module_analyser.c2.
  • the same approach should be used for the string switch helper code that should be part of the c2 library as module switch.

}

fn Stmt* Parser.parseBreakStmt(Parser* p) {
Expand Down
4 changes: 2 additions & 2 deletions test/c_generator/stmts/assert.c2t
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ int32_t main(void);

int32_t main(void)
{
(test_a) || c2_assert("file1.c2", 7, "test.main", "a");
(test_p) || c2_assert("file1.c2", 8, "test.main", "p");
(test_a) || c2_assert_fail("file1.c2", 7, "test.main", "a");
(test_p) || c2_assert_fail("file1.c2", 8, "test.main", "p");
return 0;
}