Commit a3c97081ca
Changed files (14)
doc
vim
syntax
test
doc/vim/syntax/zig.vim
@@ -14,6 +14,7 @@ syn keyword zigStatement goto break return continue asm
syn keyword zigConditional if else match
syn keyword zigRepeat while for
+syn keyword zigConstant null
syn keyword zigKeyword fn unreachable use void
syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128 string
@@ -28,6 +29,12 @@ syn match zigHexNumber display "\<0x[a-fA-F0-9_]\+\%([iu]\%(size\|8\|16\|32\|64\
syn match zigOctNumber display "\<0o[0-7_]\+\%([iu]\%(size\|8\|16\|32\|64\)\)\="
syn match zigBinNumber display "\<0b[01_]\+\%([iu]\%(size\|8\|16\|32\|64\)\)\="
+
+syn match zigCharacterInvalid display contained /b\?'\zs[\n\r\t']\ze'/
+syn match zigCharacterInvalidUnicode display contained /b'\zs[^[:cntrl:][:graph:][:alnum:][:space:]]\ze'/
+syn match zigCharacter /b'\([^\\]\|\\\(.\|x\x\{2}\)\)'/ contains=zigEscape,zigEscapeError,zigCharacterInvalid,zigCharacterInvalidUnicode
+syn match zigCharacter /'\([^\\]\|\\\(.\|x\x\{2}\|u\x\{4}\|U\x\{8}\|u{\x\{1,6}}\)\)'/ contains=zigEscape,zigEscapeUnicode,zigEscapeError,zigCharacterInvalid
+
syn match zigShebang /\%^#![^[].*/
syn region zigCommentLine start="//" end="$" contains=zigTodo,@Spell
@@ -64,6 +71,9 @@ hi def link zigCommentBlockDoc zigCommentLineDoc
hi def link zigTodo Todo
hi def link zigStringContinuation Special
hi def link zigString String
+hi def link zigCharacterInvalid Error
+hi def link zigCharacterInvalidUnicode zigCharacterInvalid
+hi def link zigCharacter Character
hi def link zigEscape Special
hi def link zigEscapeUnicode zigEscape
hi def link zigEscapeError Error
doc/langref.md
@@ -98,7 +98,9 @@ AsmInputItem : token(LBracket) token(Symbol) token(RBracket) token(String) token
AsmClobbers: token(Colon) list(token(String), token(Comma))
-AssignmentExpression : BoolOrExpression AssignmentOperator BoolOrExpression | BoolOrExpression
+UnwrapMaybeExpression : BoolOrExpression token(DoubleQuestion) BoolOrExpression | BoolOrExpression
+
+AssignmentExpression : UnwrapMaybeExpression AssignmentOperator UnwrapMaybeExpression | UnwrapMaybeExpression
AssignmentOperator : token(Eq) | token(TimesEq) | token(DivEq) | token(ModEq) | token(PlusEq) | token(MinusEq) | token(BitShiftLeftEq) | token(BitShiftRightEq) | token(BitAndEq) | token(BitXorEq) | token(BitOrEq) | token(BoolAndEq) | token(BoolOrEq)
@@ -166,7 +168,7 @@ Goto: token(Goto) token(Symbol)
GroupedExpression : token(LParen) Expression token(RParen)
-KeywordLiteral : token(Unreachable) | token(Void) | token(True) | token(False)
+KeywordLiteral : token(Unreachable) | token(Void) | token(True) | token(False) | token(Null)
```
## Operator Precedence
@@ -184,6 +186,7 @@ as
== != < > <= >=
&&
||
+??
= *= /= %= += -= <<= >>= &= ^= |= &&= ||=
```
@@ -192,7 +195,7 @@ as
### Characters and Strings
| Example | Characters | Escapes | Null Term | Type
----------------------------------------------------------------------------------
+----------------|----------|-------------|----------------|-----------|----------
Byte | 'H' | All ASCII | Byte | No | u8
UTF-8 Bytes | "hello" | All Unicode | Byte & Unicode | No | [5; u8]
UTF-8 C string | c"hello" | All Unicode | Byte & Unicode | Yes | *const u8
@@ -200,7 +203,7 @@ as
### Byte Escapes
| Name
------------------------------------------------
+------|----------------------------------------
\x7F | 8-bit character code (exactly 2 digits)
\n | Newline
\r | Carriage return
@@ -213,13 +216,13 @@ as
### Unicode Escapes
| Name
-----------------------------------------------------------
+----------|-----------------------------------------------
\u{7FFF} | 24-bit Unicode character code (up to 6 digits)
### Numbers
Number literals | Example | Exponentiation
---------------------------------------------------
+--------------------|-------------|---------------
Decimal integer | 98222 | N/A
Hex integer | 0xff | N/A
Octal integer | 0o77 | N/A
example/guess_number/main.zig
@@ -14,7 +14,7 @@ pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 {
var err : isize;
if ({err = os_get_random_bytes(&seed as &u8, #sizeof(u32)); err != #sizeof(u32)}) {
// TODO full error message
- fprint_str(stderr_fileno, "unable to get random bytes");
+ fprint_str(stderr_fileno, "unable to get random bytes\n");
return 1;
}
@@ -27,11 +27,14 @@ pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 {
print_u64(answer);
print_str("\n");
- return 0;
-
- /*
while (true) {
- const line = readline("\nGuess a number between 1 and 100: ");
+ print_str("\nGuess a number between 1 and 100: ");
+ var line_buf : [20]u8;
+ const line = readline(line_buf) ?? {
+ // TODO full error message
+ fprint_str(stderr_fileno, "unable to read input\n");
+ return 1;
+ };
if (const guess ?= parse_u64(line)) {
if (guess > answer) {
@@ -46,5 +49,4 @@ pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 {
print_str("Invalid number format.\n");
}
}
- */
}
example/maybe_type/main.zig
@@ -15,5 +15,21 @@ pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 {
print_str("x is none\n");
}
+ const next_x : ?i32 = null;
+
+ const z = next_x ?? 1234;
+
+ if (z != 1234) {
+ print_str("BAD\n");
+ }
+
+ const final_x : ?i32 = 13;
+
+ const num = final_x ?? unreachable;
+
+ if (num != 13) {
+ print_str("BAD\n");
+ }
+
return 0;
}
src/analyze.cpp
@@ -48,6 +48,7 @@ static AstNode *first_executing_node(AstNode *node) {
case NodeTypeUse:
case NodeTypeVoid:
case NodeTypeBoolLiteral:
+ case NodeTypeNullLiteral:
case NodeTypeIfBoolExpr:
case NodeTypeIfVarExpr:
case NodeTypeLabel:
@@ -358,6 +359,7 @@ static TypeTableEntry *eval_const_expr_bin_op(CodeGen *g, BlockContext *context,
case BinOpTypeSub:
case BinOpTypeMult:
case BinOpTypeDiv:
+ case BinOpTypeUnwrapMaybe:
return g->builtin_types.entry_invalid;
case BinOpTypeInvalid:
case BinOpTypeAssign:
@@ -388,6 +390,8 @@ static TypeTableEntry *eval_const_expr(CodeGen *g, BlockContext *context,
case NodeTypeBoolLiteral:
out_number_literal->data.x_uint = node->data.bool_literal ? 1 : 0;
return node->codegen_node->expr_node.type_entry;
+ case NodeTypeNullLiteral:
+ return node->codegen_node->expr_node.type_entry;
case NodeTypeBinOpExpr:
return eval_const_expr_bin_op(g, context, node, out_number_literal);
case NodeTypeCompilerFnType:
@@ -877,6 +881,7 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
case NodeTypeUnreachable:
case NodeTypeVoid:
case NodeTypeBoolLiteral:
+ case NodeTypeNullLiteral:
case NodeTypeSymbol:
case NodeTypeCastExpr:
case NodeTypePrefixOpExpr:
@@ -951,6 +956,7 @@ static void preview_types(CodeGen *g, ImportTableEntry *import, AstNode *node) {
case NodeTypeUnreachable:
case NodeTypeVoid:
case NodeTypeBoolLiteral:
+ case NodeTypeNullLiteral:
case NodeTypeSymbol:
case NodeTypeCastExpr:
case NodeTypePrefixOpExpr:
@@ -1018,7 +1024,7 @@ static bool num_lit_fits_in_other_type(CodeGen *g, TypeTableEntry *literal_type,
return false;
}
case TypeTableEntryIdMaybe:
- return num_lit_fits_in_other_type(g, literal_type, other_type->data.maybe.child_type);
+ return false;
}
zig_unreachable();
}
@@ -1133,6 +1139,9 @@ static TypeTableEntry *resolve_type_compatibility(CodeGen *g, BlockContext *cont
if (actual_type->id == TypeTableEntryIdNumberLiteral &&
num_lit_fits_in_other_type(g, actual_type, expected_type))
{
+ assert(!node->codegen_node->data.num_lit_node.resolved_type ||
+ node->codegen_node->data.num_lit_node.resolved_type == expected_type);
+ node->codegen_node->data.num_lit_node.resolved_type = expected_type;
return expected_type;
}
@@ -1419,6 +1428,7 @@ static bool is_op_allowed(TypeTableEntry *type, BinOpType op) {
case BinOpTypeMult:
case BinOpTypeDiv:
case BinOpTypeMod:
+ case BinOpTypeUnwrapMaybe:
zig_unreachable();
}
zig_unreachable();
@@ -1619,6 +1629,24 @@ static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import,
return resolve_peer_type_compatibility(g, context, node, op1, op2, lhs_type, rhs_type);
}
+ case BinOpTypeUnwrapMaybe:
+ {
+ AstNode *op1 = node->data.bin_op_expr.op1;
+ AstNode *op2 = node->data.bin_op_expr.op2;
+ TypeTableEntry *lhs_type = analyze_expression(g, import, context, nullptr, op1);
+
+ if (lhs_type->id == TypeTableEntryIdInvalid) {
+ return lhs_type;
+ } else if (lhs_type->id == TypeTableEntryIdMaybe) {
+ TypeTableEntry *child_type = lhs_type->data.maybe.child_type;
+ analyze_expression(g, import, context, child_type, op2);
+ return child_type;
+ } else {
+ add_node_error(g, op1,
+ buf_sprintf("expected maybe type, got '%s'",
+ buf_ptr(&lhs_type->name)));
+ }
+ }
case BinOpTypeInvalid:
zig_unreachable();
}
@@ -1695,6 +1723,27 @@ static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableE
return analyze_variable_declaration_raw(g, import, context, node, variable_declaration, false);
}
+static TypeTableEntry *analyze_null_literal_expr(CodeGen *g, ImportTableEntry *import,
+ BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
+{
+ assert(node->type == NodeTypeNullLiteral);
+
+ if (expected_type) {
+ assert(expected_type->id == TypeTableEntryIdMaybe);
+
+ assert(node->codegen_node);
+ node->codegen_node->data.struct_val_expr_node.type_entry = expected_type;
+ node->codegen_node->data.struct_val_expr_node.source_node = node;
+ block_context->struct_val_expr_alloca_list.append(&node->codegen_node->data.struct_val_expr_node);
+
+ return expected_type;
+ } else {
+ add_node_error(g, node,
+ buf_sprintf("unable to determine null type"));
+ return g->builtin_types.entry_invalid;
+ }
+}
+
static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry *import,
BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
{
@@ -1706,8 +1755,11 @@ static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry
} else if (expected_type) {
NumberLiteralNode *codegen_num_lit = &node->codegen_node->data.num_lit_node;
assert(!codegen_num_lit->resolved_type);
- codegen_num_lit->resolved_type = resolve_type_compatibility(g, block_context, node, expected_type, num_lit_type);
- return codegen_num_lit->resolved_type;
+ TypeTableEntry *after_implicit_cast_resolved_type =
+ resolve_type_compatibility(g, block_context, node, expected_type, num_lit_type);
+ assert(codegen_num_lit->resolved_type ||
+ after_implicit_cast_resolved_type->id == TypeTableEntryIdInvalid);
+ return after_implicit_cast_resolved_type;
} else {
return num_lit_type;
}
@@ -2174,6 +2226,10 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
return_type = g->builtin_types.entry_bool;
break;
+ case NodeTypeNullLiteral:
+ return_type = analyze_null_literal_expr(g, import, context, expected_type, node);
+ break;
+
case NodeTypeSymbol:
{
return_type = analyze_variable_name(g, import, context, node, &node->data.symbol);
@@ -2491,6 +2547,7 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import,
case NodeTypeUnreachable:
case NodeTypeVoid:
case NodeTypeBoolLiteral:
+ case NodeTypeNullLiteral:
case NodeTypeSymbol:
case NodeTypeCastExpr:
case NodeTypePrefixOpExpr:
src/codegen.cpp
@@ -613,6 +613,7 @@ static LLVMValueRef gen_arithmetic_bin_op(CodeGen *g, AstNode *source_node,
case BinOpTypeAssign:
case BinOpTypeAssignBoolAnd:
case BinOpTypeAssignBoolOr:
+ case BinOpTypeUnwrapMaybe:
zig_unreachable();
}
zig_unreachable();
@@ -814,6 +815,70 @@ static LLVMValueRef gen_assign_expr(CodeGen *g, AstNode *node) {
return gen_assign_raw(g, node, node->data.bin_op_expr.bin_op, target_ref, value, op1_type, op2_type);
}
+static LLVMValueRef gen_unwrap_maybe(CodeGen *g, AstNode *node, LLVMValueRef maybe_struct_ref) {
+ add_debug_source_node(g, node);
+ LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_struct_ref, 0, "");
+ // TODO if it's a struct we might not want to load the pointer
+ return LLVMBuildLoad(g->builder, maybe_field_ptr, "");
+}
+
+static LLVMValueRef gen_unwrap_maybe_expr(CodeGen *g, AstNode *node) {
+ assert(node->type == NodeTypeBinOpExpr);
+ assert(node->data.bin_op_expr.bin_op == BinOpTypeUnwrapMaybe);
+
+ AstNode *op1_node = node->data.bin_op_expr.op1;
+ AstNode *op2_node = node->data.bin_op_expr.op2;
+
+ LLVMValueRef maybe_struct_ref = gen_expr(g, op1_node);
+
+ add_debug_source_node(g, node);
+ LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_struct_ref, 1, "");
+ LLVMValueRef cond_value = LLVMBuildLoad(g->builder, maybe_field_ptr, "");
+
+ LLVMBasicBlockRef non_null_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeNonNull");
+ LLVMBasicBlockRef null_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeNull");
+ LLVMBasicBlockRef end_block;
+
+ bool non_null_reachable = get_expr_type(op1_node)->id != TypeTableEntryIdUnreachable;
+ bool null_reachable = get_expr_type(op2_node)->id != TypeTableEntryIdUnreachable;
+ bool end_reachable = non_null_reachable || null_reachable;
+ if (end_reachable) {
+ end_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeEnd");
+ }
+
+ LLVMBuildCondBr(g->builder, cond_value, non_null_block, null_block);
+
+ LLVMPositionBuilderAtEnd(g->builder, non_null_block);
+ LLVMValueRef non_null_result = gen_unwrap_maybe(g, op1_node, maybe_struct_ref);
+ if (non_null_reachable) {
+ add_debug_source_node(g, node);
+ LLVMBuildBr(g->builder, end_block);
+ }
+
+ LLVMPositionBuilderAtEnd(g->builder, null_block);
+ LLVMValueRef null_result = gen_expr(g, op2_node);
+ if (null_reachable) {
+ add_debug_source_node(g, node);
+ LLVMBuildBr(g->builder, end_block);
+ }
+
+ if (end_reachable) {
+ LLVMPositionBuilderAtEnd(g->builder, end_block);
+ if (null_reachable) {
+ add_debug_source_node(g, node);
+ LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMTypeOf(non_null_result), "");
+ LLVMValueRef incoming_values[2] = {non_null_result, null_result};
+ LLVMBasicBlockRef incoming_blocks[2] = {non_null_block, null_block};
+ LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
+ return phi;
+ } else {
+ return non_null_result;
+ }
+ }
+
+ return nullptr;
+}
+
static LLVMValueRef gen_bin_op_expr(CodeGen *g, AstNode *node) {
switch (node->data.bin_op_expr.bin_op) {
case BinOpTypeInvalid:
@@ -843,6 +908,8 @@ static LLVMValueRef gen_bin_op_expr(CodeGen *g, AstNode *node) {
case BinOpTypeCmpLessOrEq:
case BinOpTypeCmpGreaterOrEq:
return gen_cmp_expr(g, node);
+ case BinOpTypeUnwrapMaybe:
+ return gen_unwrap_maybe_expr(g, node);
case BinOpTypeBinOr:
case BinOpTypeBinXor:
case BinOpTypeBinAnd:
@@ -1124,6 +1191,22 @@ static LLVMValueRef gen_asm_expr(CodeGen *g, AstNode *node) {
return LLVMBuildCall(g->builder, asm_fn, param_values, input_and_output_count, "");
}
+static LLVMValueRef gen_null_literal(CodeGen *g, AstNode *node) {
+ assert(node->type == NodeTypeNullLiteral);
+
+ TypeTableEntry *type_entry = get_expr_type(node);
+ assert(type_entry->id == TypeTableEntryIdMaybe);
+
+ LLVMValueRef tmp_struct_ptr = node->codegen_node->data.struct_val_expr_node.ptr;
+
+ add_debug_source_node(g, node);
+ LLVMValueRef field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, 1, "");
+ LLVMValueRef null_value = LLVMConstNull(LLVMInt1Type());
+ LLVMBuildStore(g->builder, null_value, field_ptr);
+
+ return tmp_struct_ptr;
+}
+
static LLVMValueRef gen_struct_val_expr(CodeGen *g, AstNode *node) {
assert(node->type == NodeTypeStructValueExpr);
@@ -1242,10 +1325,7 @@ static LLVMValueRef gen_var_decl_raw(CodeGen *g, AstNode *source_node, AstNodeVa
LLVMValueRef value;
if (unwrap_maybe) {
assert(var_decl->expr);
- add_debug_source_node(g, source_node);
- LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, *init_value, 0, "");
- // TODO if it's a struct we might not want to load the pointer
- value = LLVMBuildLoad(g->builder, maybe_field_ptr, "");
+ value = gen_unwrap_maybe(g, source_node, *init_value);
} else {
value = *init_value;
}
@@ -1375,6 +1455,8 @@ static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) {
return LLVMConstAllOnes(LLVMInt1Type());
else
return LLVMConstNull(LLVMInt1Type());
+ case NodeTypeNullLiteral:
+ return gen_null_literal(g, node);
case NodeTypeIfBoolExpr:
return gen_if_bool_expr(g, node);
case NodeTypeIfVarExpr:
src/parser.cpp
@@ -48,6 +48,7 @@ static const char *bin_op_str(BinOpType bin_op) {
case BinOpTypeAssignBitOr: return "|=";
case BinOpTypeAssignBoolAnd: return "&&=";
case BinOpTypeAssignBoolOr: return "||=";
+ case BinOpTypeUnwrapMaybe: return "??";
}
zig_unreachable();
}
@@ -117,6 +118,8 @@ const char *node_type_str(NodeType node_type) {
return "Void";
case NodeTypeBoolLiteral:
return "BoolLiteral";
+ case NodeTypeNullLiteral:
+ return "NullLiteral";
case NodeTypeIfBoolExpr:
return "IfBoolExpr";
case NodeTypeIfVarExpr:
@@ -349,6 +352,9 @@ void ast_print(AstNode *node, int indent) {
case NodeTypeBoolLiteral:
fprintf(stderr, "%s '%s'\n", node_type_str(node->type), node->data.bool_literal ? "true" : "false");
break;
+ case NodeTypeNullLiteral:
+ fprintf(stderr, "%s\n", node_type_str(node->type));
+ break;
case NodeTypeIfBoolExpr:
fprintf(stderr, "%s\n", node_type_str(node->type));
if (node->data.if_bool_expr.condition)
@@ -1280,6 +1286,7 @@ static AstNode *ast_parse_struct_val_expr(ParseContext *pc, int *token_index) {
/*
PrimaryExpression : token(Number) | token(String) | token(CharLiteral) | KeywordLiteral | GroupedExpression | Goto | token(Break) | token(Continue) | BlockExpression | token(Symbol) | StructValueExpression | CompilerFnType
+KeywordLiteral : token(Unreachable) | token(Void) | token(True) | token(False) | token(Null)
*/
static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) {
Token *token = &pc->tokens->at(*token_index);
@@ -1317,6 +1324,10 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool
node->data.bool_literal = false;
*token_index += 1;
return node;
+ } else if (token->id == TokenIdKeywordNull) {
+ AstNode *node = ast_create_node(pc, NodeTypeNullLiteral, token);
+ *token_index += 1;
+ return node;
} else if (token->id == TokenIdSymbol) {
Token *next_token = &pc->tokens->at(*token_index + 1);
@@ -2045,19 +2056,45 @@ static BinOpType ast_parse_ass_op(ParseContext *pc, int *token_index, bool manda
}
/*
-AssignmentExpression : BoolOrExpression AssignmentOperator BoolOrExpression | BoolOrExpression
+UnwrapMaybeExpression : BoolOrExpression token(DoubleQuestion) BoolOrExpression | BoolOrExpression
*/
-static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mandatory) {
+static AstNode *ast_parse_unwrap_maybe_expr(ParseContext *pc, int *token_index, bool mandatory) {
AstNode *lhs = ast_parse_bool_or_expr(pc, token_index, mandatory);
if (!lhs)
return nullptr;
+ Token *token = &pc->tokens->at(*token_index);
+
+ if (token->id != TokenIdDoubleQuestion) {
+ return lhs;
+ }
+
+ *token_index += 1;
+
+ AstNode *rhs = ast_parse_bool_or_expr(pc, token_index, true);
+
+ AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token);
+ node->data.bin_op_expr.op1 = lhs;
+ node->data.bin_op_expr.bin_op = BinOpTypeUnwrapMaybe;
+ node->data.bin_op_expr.op2 = rhs;
+
+ return node;
+}
+
+/*
+AssignmentExpression : UnwrapMaybeExpression AssignmentOperator UnwrapMaybeExpression | UnwrapMaybeExpression
+*/
+static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mandatory) {
+ AstNode *lhs = ast_parse_unwrap_maybe_expr(pc, token_index, mandatory);
+ if (!lhs)
+ return nullptr;
+
Token *token = &pc->tokens->at(*token_index);
BinOpType ass_op = ast_parse_ass_op(pc, token_index, false);
if (ass_op == BinOpTypeInvalid)
return lhs;
- AstNode *rhs = ast_parse_bool_or_expr(pc, token_index, true);
+ AstNode *rhs = ast_parse_unwrap_maybe_expr(pc, token_index, true);
AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token);
node->data.bin_op_expr.op1 = lhs;
src/parser.hpp
@@ -45,6 +45,7 @@ enum NodeType {
NodeTypeUse,
NodeTypeVoid,
NodeTypeBoolLiteral,
+ NodeTypeNullLiteral,
NodeTypeIfBoolExpr,
NodeTypeIfVarExpr,
NodeTypeWhileExpr,
@@ -161,6 +162,7 @@ enum BinOpType {
BinOpTypeMult,
BinOpTypeDiv,
BinOpTypeMod,
+ BinOpTypeUnwrapMaybe,
};
struct AstNodeBinOpExpr {
src/tokenizer.cpp
@@ -241,6 +241,8 @@ static void end_token(Tokenize *t) {
t->cur_tok->id = TokenIdKeywordContinue;
} else if (mem_eql_str(token_mem, token_len, "break")) {
t->cur_tok->id = TokenIdKeywordBreak;
+ } else if (mem_eql_str(token_mem, token_len, "null")) {
+ t->cur_tok->id = TokenIdKeywordNull;
}
t->cur_tok = nullptr;
@@ -418,6 +420,11 @@ void tokenize(Buf *buf, Tokenization *out) {
break;
case TokenizeStateSawQuestionMark:
switch (c) {
+ case '?':
+ t.cur_tok->id = TokenIdDoubleQuestion;
+ end_token(&t);
+ t.state = TokenizeStateStart;
+ break;
case '=':
t.cur_tok->id = TokenIdMaybeAssign;
end_token(&t);
@@ -1002,6 +1009,7 @@ static const char * token_name(Token *token) {
case TokenIdKeywordWhile: return "While";
case TokenIdKeywordContinue: return "Continue";
case TokenIdKeywordBreak: return "Break";
+ case TokenIdKeywordNull: return "Null";
case TokenIdLParen: return "LParen";
case TokenIdRParen: return "RParen";
case TokenIdComma: return "Comma";
@@ -1052,6 +1060,7 @@ static const char * token_name(Token *token) {
case TokenIdDot: return "Dot";
case TokenIdEllipsis: return "Ellipsis";
case TokenIdMaybe: return "Maybe";
+ case TokenIdDoubleQuestion: return "DoubleQuestion";
case TokenIdMaybeAssign: return "MaybeAssign";
}
return "(invalid token)";
src/tokenizer.hpp
@@ -35,6 +35,7 @@ enum TokenId {
TokenIdKeywordWhile,
TokenIdKeywordContinue,
TokenIdKeywordBreak,
+ TokenIdKeywordNull,
TokenIdLParen,
TokenIdRParen,
TokenIdComma,
@@ -85,6 +86,7 @@ enum TokenId {
TokenIdDot,
TokenIdEllipsis,
TokenIdMaybe,
+ TokenIdDoubleQuestion,
TokenIdMaybeAssign,
};
std/std.zig
@@ -1,5 +1,6 @@
use "syscall.zig";
+const stdin_fileno : isize = 0;
const stdout_fileno : isize = 1;
const stderr_fileno : isize = 2;
@@ -38,6 +39,26 @@ pub fn print_i64(x: i64) -> isize {
return write(stdout_fileno, buf.ptr, len);
}
+/*
+// TODO error handling
+pub fn readline(buf: []u8) -> ?[]u8 {
+ var index = 0;
+ while (index < buf.len) {
+ // TODO unknown size array indexing operator
+ const err = read(stdin_fileno, &buf.ptr[index], 1);
+ if (err != 0) {
+ return null;
+ }
+ // TODO unknown size array indexing operator
+ if (buf.ptr[index] == '\n') {
+ return buf[0...index + 1];
+ }
+ index += 1;
+ }
+ return null;
+}
+*/
+
fn digit_to_char(digit: u64) -> u8 {
'0' + (digit as u8)
}
std/syscall.zig
@@ -1,3 +1,4 @@
+const SYS_read : usize = 0;
const SYS_write : usize = 1;
const SYS_exit : usize = 60;
const SYS_getrandom : usize = 318;
@@ -16,6 +17,10 @@ fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) -> usize {
: "rcx", "r11")
}
+pub fn read(fd: isize, buf: &u8, count: usize) -> isize {
+ syscall3(SYS_read, fd as usize, buf as usize, count) as isize
+}
+
pub fn write(fd: isize, buf: &const u8, count: usize) -> isize {
syscall3(SYS_write, fd as usize, buf as usize, count) as isize
}
test/run_tests.cpp
@@ -710,7 +710,7 @@ pub fn main(argc : isize, argv : &&u8, env : &&u8) -> i32 {
add_simple_case("maybe type", R"SOURCE(
use "std.zig";
-pub fn main(argc : isize, argv : &&u8, env : &&u8) -> i32 {
+pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 {
const x : ?bool = true;
if (const y ?= x) {
@@ -722,6 +722,23 @@ pub fn main(argc : isize, argv : &&u8, env : &&u8) -> i32 {
} else {
print_str("x is none\n");
}
+
+ const next_x : ?i32 = null;
+
+ const z = next_x ?? 1234;
+
+ if (z != 1234) {
+ print_str("BAD\n");
+ }
+
+ const final_x : ?i32 = 13;
+
+ const num = final_x ?? unreachable;
+
+ if (num != 13) {
+ print_str("BAD\n");
+ }
+
return 0;
}
)SOURCE", "x is true\n");
CMakeLists.txt
@@ -120,6 +120,7 @@ set(ZIG_STD_SRC
"${CMAKE_SOURCE_DIR}/std/builtin.zig"
"${CMAKE_SOURCE_DIR}/std/std.zig"
"${CMAKE_SOURCE_DIR}/std/syscall.zig"
+ "${CMAKE_SOURCE_DIR}/std/errno.zig"
"${CMAKE_SOURCE_DIR}/std/rand.zig"
)