diff --git a/include/ast.h b/include/ast.h index 2d08fe0..fa89932 100644 --- a/include/ast.h +++ b/include/ast.h @@ -7,8 +7,10 @@ // AST operation types enum { A_ASSIGN, - A_ADD, A_SUB, A_MUL, A_DIV, + A_NEG, A_ADD, A_SUB, A_MUL, A_DIV, A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE, + A_LNOT, A_LAND, A_LOR, + A_BNOT, A_LIT_I32, A_LIT_I64, A_VAR, A_BLOCK, diff --git a/include/quad.h b/include/quad.h index 6c4d675..5e0e836 100644 --- a/include/quad.h +++ b/include/quad.h @@ -11,6 +11,11 @@ enum { // Loading immediates Q_IMM_I32, // integer (32bits) + // Arithmetic operations + Q_NEG, // negation + Q_NOT, // bitwise not + Q_CMP_EQ, // compare whether equal + // Terminates Q_RET, // return Q_BR_ALWAYS, // conditional goto: always goto true branch. diff --git a/include/token.h b/include/token.h index 5cf6bab..e83bfdf 100644 --- a/include/token.h +++ b/include/token.h @@ -23,7 +23,9 @@ enum { T_SEMI, // ; T_LB, T_RB, T_LP, T_RP, // { } ( ) T_ASSIGN, // = - T_PLUS, T_MINUS, T_STAR, T_SLASH, // + - * / + T_PLUS, T_MINUS, T_STAR, T_SLASH, // - + - * / + T_LNOT, T_LAND, T_LOR, // ! && || + T_BNOT, // ~ T_EQ, T_NE, T_LT, T_GT, T_LE, T_GE, // == != < > <= >= T_INT, T_VOID, T_CHAR, T_LONG, // int void char long T_SHORT, // short diff --git a/src/ast.c b/src/ast.c index 5bd45cd..b98097a 100644 --- a/src/ast.c +++ b/src/ast.c @@ -6,8 +6,10 @@ const char *ast_opname[] = { "=", - "+", "-", "*", "/", + "neg", "add", "sub", "mul", "div", "==", "!=", "<", ">", "<=", ">=", + "not", "and", "or", + "~", "int32", "int64", "var", "block", @@ -188,7 +190,8 @@ void ast_free(struct ASTnode *x) { ast_free(t->right); } break; - case A_PRINT: case A_RETURN: { + case A_PRINT: case A_RETURN: + case A_LNOT: case A_BNOT: case A_NEG: { struct ASTunnode *t = (struct ASTunnode*)x; ast_free(t->left); } break; diff --git a/src/parse.c b/src/parse.c index d4ec163..e42cd09 100644 --- a/src/parse.c +++ b/src/parse.c @@ -10,7 +10,7 @@ static struct linklist Tokens; // current token for parsing // Check that we have a binary operator and return its precedence. -// operators with larger precedence value will be evaluated first +// Operators with larger precedence value will be evaluated first. static int op_precedence(struct token *t) { switch (t->type) { case T_ASSIGN: @@ -28,8 +28,8 @@ static int op_precedence(struct token *t) { } } -// Convert a arithmetic token into an AST operation. -static int arithop(struct token *t) { +// Converts a binary arithmetic token into an AST operation. +static int binary_arithop(struct token *t) { static const int map[][2] = { {T_PLUS, A_ADD}, {T_MINUS, A_SUB}, @@ -42,6 +42,8 @@ static int arithop(struct token *t) { {T_GT, A_GT}, {T_GE, A_GE}, {T_ASSIGN, A_ASSIGN}, + {T_LAND, A_LAND}, + {T_LOR, A_LOR}, {-1} }; @@ -53,7 +55,25 @@ static int arithop(struct token *t) { fail_ce_expect(t->line, "an binary operator", token_typename[t->type]); } -// operator ssociativity direction +// Converts a unary arithmetic token into an AST operation. +static int unary_arithop(struct token *t) { + static const int map[][2] = { + {T_MINUS, A_NEG}, + {T_LNOT, A_LNOT}, + {T_BNOT, A_BNOT}, + {-1} + }; + + for (int i = 0; map[i][0] != -1; ++i) { + if (t->type == map[i][0]) { + return map[i][1]; + } + } + + fail_ce_expect(t->line, "an unary operator", token_typename[t->type]); +} + +// Operator associativity direction // Returns false if left to right, e.g. + // true if right to left, e.g. = static bool direction_rtl(int t) { @@ -114,6 +134,7 @@ static struct ASTnode* expression(void); static struct ASTnode* primary(void) { struct ASTnode *res; struct token *t = current(); + if (t->type == T_LP) { // ( expr ) considered as primary next(); @@ -143,16 +164,50 @@ static struct ASTnode* primary(void) { return (res); } -// Check if it is binary operator -static int is_binop(int t) { - return (T_ASSIGN <= t && t <= T_GE); +// Returns whether the given token type can be a prefix operator (negation, logical not, bitwise not) +static bool is_prefix_op(int op) { + switch (op) { + case T_MINUS: case T_LNOT: case T_BNOT: + return (true); + + default: + return (false); + } +} + +// Parses a primary expression with prefixes, e.g. ~10 +static struct ASTnode* prefixed_primary(void) { + struct token *t = current(); + + if (is_prefix_op(t->type)) { + next(); + struct ASTnode *child = prefixed_primary(); + return (ast_make_unary(unary_arithop(t), child)); + } + + return (primary()); +} + +// Returns whether the given token type can be a binary operator. +static bool is_binop(int t) { + switch (t) { + case T_ASSIGN: + case T_PLUS: case T_MINUS: case T_STAR: case T_SLASH: + case T_LAND: case T_LOR: + case T_EQ: case T_NE: case T_LT: + case T_GT: case T_LE: case T_GE: + return (true); + + default: + return (false); + } } // Return an AST tree whose root is a binary operator static struct ASTnode* binexpr(int precedence) { struct ASTnode *left, *right; - left = primary(); + left = prefixed_primary(); struct token *op = current(); if (!is_binop(op->type)) { return (left); @@ -164,10 +219,10 @@ static struct ASTnode* binexpr(int precedence) { if (direction_rtl(op->type)) { right = binexpr(precedence); - left = ast_make_assign(arithop(op), left, right); + left = ast_make_assign(binary_arithop(op), left, right); } else { right = binexpr(tp); - left = ast_make_binary(arithop(op), left, right); // join right into left + left = ast_make_binary(binary_arithop(op), left, right); // join right into left } op = current(); @@ -304,7 +359,7 @@ static struct ASTnode* for_statement(void) { return ((struct ASTnode*)container); } -static struct ASTnode* return_statement() { +static struct ASTnode* return_statement(void) { match(T_RETURN); struct ASTnode *res = expression(); match(T_SEMI); @@ -347,7 +402,7 @@ static struct ASTnode* statement(void) { // Parse one top-level function // Sets the func_name param. -static struct Afunction* function() { +static struct Afunction* function(void) { struct Afunction *res = afunc_make(); match(T_INT); diff --git a/src/quad.c b/src/quad.c index 147cafe..13a6008 100644 --- a/src/quad.c +++ b/src/quad.c @@ -84,7 +84,20 @@ struct Qvar* qfunc_new_var(struct Qfunction *f) { return (res); } +// Translate an AST unary arithmetic opcode to a Quad opcode. +static int unary_arithop(int op) { + switch (op) { + case A_NEG: return (Q_NEG); + case A_BNOT: return (Q_NOT); + default: fail_quad_op(op, __FUNCTION__); + } +} + static struct Qvar* qcg_dfs(struct ASTnode *x, struct Qfunction *f, struct Qblock *b) { + if (x == NULL) { + return (NULL); + } + switch (x->op) { case A_RETURN: { struct ASTunnode *t = (void*)x; @@ -94,13 +107,6 @@ static struct Qvar* qcg_dfs(struct ASTnode *x, struct Qfunction *f, struct Qbloc return (NULL); } - case A_LIT_I32: { - struct ASTi32node *t = (void*)x; - struct Qvar *res = qfunc_new_var(f); - qblock_add_ins(b, quad_make_i32(res, t->val)); - return (res); - } - case A_BLOCK: { struct ASTblocknode *t = (void*)x; struct llist_node *p = t->st.head; @@ -111,6 +117,31 @@ static struct Qvar* qcg_dfs(struct ASTnode *x, struct Qfunction *f, struct Qbloc return (NULL); } + case A_LIT_I32: { + struct Qvar *res = qfunc_new_var(f); + struct ASTi32node *t = (void*)x; + qblock_add_ins(b, quad_make_i32(res, t->val)); + return (res); + } + + case A_NEG: case A_BNOT: { + struct Qvar *res = qfunc_new_var(f); + struct ASTunnode *t = (void*)x; + struct Qvar *value = qcg_dfs(t->left, f, b); + qblock_add_ins(b, quad_make(unary_arithop(x->op), res, value, NULL)); + return (res); + } + + case A_LNOT: { + struct Qvar *res = qfunc_new_var(f); + struct ASTunnode *t = (void*)x; + struct Qvar *value = qcg_dfs(t->left, f, b); + struct Qvar *zero = qfunc_new_var(f); + qblock_add_ins(b, quad_make_i32(zero, 0)); + qblock_add_ins(b, quad_make(Q_CMP_EQ, res, value, zero)); + return (res); + } + default: { fail_ast_op(x->op, __FUNCTION__); } @@ -163,7 +194,23 @@ static void quad_debug_print(struct Quad *self, FILE *Outfile) { } break; case Q_RET: { - fprintf(Outfile, "\tret $%d.\n", self->left->id); + if (self->left) { + fprintf(Outfile, "\tret $%d.\n", self->left->id); + } else { + fputs("\tret.", Outfile); + } + } break; + + case Q_NEG: { + fprintf(Outfile, "\t$%d = neg $%d;\n", self->dest->id, self->left->id); + } break; + + case Q_NOT: { + fprintf(Outfile, "\t$%d = not $%d;\n", self->dest->id, self->left->id); + } break; + + case Q_CMP_EQ: { + fprintf(Outfile, "\t$%d = eq $%d, $%d;\n", self->dest->id, self->left->id, self->right->id); } break; default: { diff --git a/src/scan.c b/src/scan.c index 656fab4..cca632d 100644 --- a/src/scan.c +++ b/src/scan.c @@ -185,8 +185,7 @@ static struct token* scan(void) { t->type = T_NE; next(); } else { - // TODO: the not operator - fail_char(t->line, c); + t->type = T_LNOT; } } else if (c == '<') { t->type = T_LT; @@ -204,6 +203,27 @@ static struct token* scan(void) { t->type = T_GE; next(); } + } else if (c == '~') { + t->type = T_BNOT; + next(); + } else if (c == '&') { + next(); + c = preview(); + if (c == '&') { + t->type = T_LAND; + } else { + // TODO: bitwise and + fail_char(t->line, c); + } + } else if (c == '|') { + next(); + c = preview(); + if (c == '|') { + t->type = T_LOR; + } else { + // TODO: bitwise or + fail_char(t->line, c); + } } else { if (isdigit(c)) { // If it's a digit, scan the integer literal value in scan_int(t); diff --git a/src/token.c b/src/token.c index 61f754e..b094071 100644 --- a/src/token.c +++ b/src/token.c @@ -7,6 +7,8 @@ const char *token_typename[63] = { "{", "}", "(", ")", "=", "+", "-", "*", "/", + "!", "&&", "||", + "~", "==", "!=", "<", ">", "<=", ">=", "int", "void", "char", "long", "short", diff --git a/tests/invalid/missing_const.c b/tests/invalid/missing_const.c new file mode 100644 index 0000000..6dd069e --- /dev/null +++ b/tests/invalid/missing_const.c @@ -0,0 +1,3 @@ +int main() { + return !; +} \ No newline at end of file diff --git a/tests/invalid/missing_semicolon.c b/tests/invalid/missing_semicolon.c new file mode 100644 index 0000000..5570d64 --- /dev/null +++ b/tests/invalid/missing_semicolon.c @@ -0,0 +1,3 @@ +int main() { + return !5 +} \ No newline at end of file diff --git a/tests/invalid/nested_missing_const.c b/tests/invalid/nested_missing_const.c new file mode 100644 index 0000000..43b7097 --- /dev/null +++ b/tests/invalid/nested_missing_const.c @@ -0,0 +1,3 @@ +int main() { + return !~; +} \ No newline at end of file diff --git a/tests/invalid/wrong_order.c b/tests/invalid/wrong_order.c new file mode 100644 index 0000000..27a9f02 --- /dev/null +++ b/tests/invalid/wrong_order.c @@ -0,0 +1,3 @@ +int main() { + return 4-; +} \ No newline at end of file diff --git a/tests/valid/bitwise.c b/tests/valid/bitwise.c new file mode 100644 index 0000000..a0070d3 --- /dev/null +++ b/tests/valid/bitwise.c @@ -0,0 +1,3 @@ +int main() { + return ~12; +} diff --git a/tests/valid/bitwise_zero.c b/tests/valid/bitwise_zero.c new file mode 100644 index 0000000..2c2ed2e --- /dev/null +++ b/tests/valid/bitwise_zero.c @@ -0,0 +1,3 @@ +int main() { + return ~0; +} \ No newline at end of file diff --git a/tests/valid/neg.c b/tests/valid/neg.c new file mode 100644 index 0000000..b7ac431 --- /dev/null +++ b/tests/valid/neg.c @@ -0,0 +1,3 @@ +int main() { + return -5; +} \ No newline at end of file diff --git a/tests/valid/nested_ops.c b/tests/valid/nested_ops.c new file mode 100644 index 0000000..9fb3f87 --- /dev/null +++ b/tests/valid/nested_ops.c @@ -0,0 +1,3 @@ +int main() { + return !-3; +} \ No newline at end of file diff --git a/tests/valid/nested_ops_2.c b/tests/valid/nested_ops_2.c new file mode 100644 index 0000000..37d9f4c --- /dev/null +++ b/tests/valid/nested_ops_2.c @@ -0,0 +1,3 @@ +int main() { + return -------!!!----~0; +} diff --git a/tests/valid/not_five.c b/tests/valid/not_five.c new file mode 100644 index 0000000..df792bb --- /dev/null +++ b/tests/valid/not_five.c @@ -0,0 +1,3 @@ +int main() { + return !5; +} \ No newline at end of file diff --git a/tests/valid/not_zero.c b/tests/valid/not_zero.c new file mode 100644 index 0000000..b6b7cb5 --- /dev/null +++ b/tests/valid/not_zero.c @@ -0,0 +1,3 @@ +int main() { + return !0; +} \ No newline at end of file