Support statements
This commit is contained in:
parent
3cc1b4b0d4
commit
9818c21097
10 changed files with 405 additions and 271 deletions
|
@ -4,24 +4,28 @@
|
|||
#include "lexer.h"
|
||||
|
||||
/*
|
||||
expression → literal
|
||||
| unary
|
||||
| binary
|
||||
| grouping ;
|
||||
literal → NUMBER | STRING | "true" | "false" | "nil" ;
|
||||
grouping → "(" expression ")" ;
|
||||
unary → ( "-" | "!" ) expression ;
|
||||
binary → expression operator expression ;
|
||||
operator → "==" | "!=" | "<" | "<=" | ">" | ">="
|
||||
| "+" | "-" | "*" | "/" ;
|
||||
expression → literal | unary | binary | grouping ;
|
||||
literal → NUMBER | STRING | "true" | "false" | "nil" ;
|
||||
grouping → "(" expression ")" ;
|
||||
unary → ( "-" | "!" ) expression ;
|
||||
binary → expression operator expression ;
|
||||
operator → "==" | "!=" | "<" | "<=" | ">" | ">=" | "+" | "-" | "*" | "/" ;
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
BINARY,
|
||||
UNARY,
|
||||
LITERAL,
|
||||
GROUPING,
|
||||
} expr_type;
|
||||
EXPR_ASSIGN,
|
||||
EXPR_BINARY,
|
||||
EXPR_CALL,
|
||||
EXPR_GET,
|
||||
EXPR_GROUPING,
|
||||
EXPR_LITERAL,
|
||||
EXPR_LOGICAL,
|
||||
EXPR_SET,
|
||||
EXPR_SUPER,
|
||||
EXPR_THIS,
|
||||
EXPR_UNARY,
|
||||
EXPR_VARIABLE,
|
||||
} expr_type_t;
|
||||
|
||||
typedef enum {
|
||||
VAL_BOOL,
|
||||
|
@ -40,29 +44,62 @@ typedef struct {
|
|||
} value_t;
|
||||
|
||||
typedef struct expr_t {
|
||||
expr_type type;
|
||||
expr_type_t type;
|
||||
int line;
|
||||
union {
|
||||
struct {
|
||||
token_t binary_op;
|
||||
token_t name;
|
||||
struct expr_t *value;
|
||||
} assign;
|
||||
struct {
|
||||
token_t operator;
|
||||
struct expr_t *left;
|
||||
struct expr_t *right;
|
||||
} binary;
|
||||
struct {
|
||||
token_t unary_op;
|
||||
struct expr_t *right;
|
||||
} unary;
|
||||
struct expr_t *callee;
|
||||
token_t paren;
|
||||
struct expr_t **arguments;
|
||||
} call;
|
||||
struct {
|
||||
struct expr_t *object;
|
||||
token_t name;
|
||||
} get;
|
||||
struct {
|
||||
struct expr_t *expression;
|
||||
} grouping;
|
||||
struct {
|
||||
value_t value;
|
||||
} literal;
|
||||
struct {
|
||||
struct expr_t *expression;
|
||||
} grouping;
|
||||
token_t operator;
|
||||
struct expr_t *left;
|
||||
struct expr_t *right;
|
||||
} logical;
|
||||
struct {
|
||||
struct expr_t *object;
|
||||
token_t name;
|
||||
struct expr_t *value;
|
||||
} set;
|
||||
struct {
|
||||
token_t keyword;
|
||||
token_t method;
|
||||
} super;
|
||||
struct {
|
||||
token_t keyword;
|
||||
} this;
|
||||
struct {
|
||||
token_t operator;
|
||||
struct expr_t *right;
|
||||
} unary;
|
||||
struct {
|
||||
token_t name;
|
||||
} variable;
|
||||
} as;
|
||||
} expr_t;
|
||||
|
||||
expr_t *create_binary_expr(token_t *binary_op, expr_t *left, expr_t *right);
|
||||
expr_t *create_unary_expr(token_t *unary_op, expr_t *right);
|
||||
expr_t *create_binary_expr(token_t *operator, expr_t *left, expr_t *right);
|
||||
expr_t *create_unary_expr(token_t *operator, expr_t *right);
|
||||
expr_t *create_literal_expr(token_t *token);
|
||||
expr_t *create_grouping_expr(expr_t *expression);
|
||||
void print_ast(expr_t *expr);
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
#define INTERPRETER_H
|
||||
|
||||
#include "ast.h"
|
||||
#include "stmt.h"
|
||||
|
||||
value_t evaluate(expr_t *expr);
|
||||
void print_value(value_t *value);
|
||||
void print_statements(stmt_array_t *array);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,23 +5,23 @@
|
|||
|
||||
typedef enum {
|
||||
// Single-character tokens
|
||||
LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE,
|
||||
COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR,
|
||||
TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE,
|
||||
TOKEN_COMMA, TOKEN_DOT, TOKEN_MINUS, TOKEN_PLUS, TOKEN_SEMICOLON,
|
||||
TOKEN_SLASH, TOKEN_STAR,
|
||||
|
||||
// One or two character tokens
|
||||
BANG, BANG_EQUAL,
|
||||
EQUAL, EQUAL_EQUAL,
|
||||
GREATER, GREATER_EQUAL,
|
||||
LESS, LESS_EQUAL,
|
||||
TOKEN_BANG, TOKEN_BANG_EQUAL, TOKEN_EQUAL, TOKEN_EQUAL_EQUAL, TOKEN_GREATER,
|
||||
TOKEN_GREATER_EQUAL, TOKEN_LESS, TOKEN_LESS_EQUAL,
|
||||
|
||||
// Literals
|
||||
IDENTIFIER, STRING, NUMBER,
|
||||
TOKEN_IDENTIFIER, TOKEN_STRING, TOKEN_NUMBER,
|
||||
|
||||
// Keywords
|
||||
AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR,
|
||||
PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE,
|
||||
TOKEN_AND, TOKEN_CLASS, TOKEN_ELSE, TOKEN_FALSE, TOKEN_FUN, TOKEN_FOR,
|
||||
TOKEN_IF, TOKEN_NIL, TOKEN_OR, TOKEN_PRINT, TOKEN_RETURN, TOKEN_SUPER,
|
||||
TOKEN_THIS, TOKEN_TRUE, TOKEN_VAR, TOKEN_WHILE,
|
||||
|
||||
END_OF_FILE
|
||||
TOKEN_EOF
|
||||
} token_type_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
|
@ -7,16 +7,17 @@ equality → comparison ( ( "!=" | "==" ) comparison )* ;
|
|||
comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
|
||||
term → factor ( ( "-" | "+" ) factor )* ;
|
||||
factor → unary ( ( "/" | "*" ) unary )* ;
|
||||
unary → ( "!" | "-" ) unary
|
||||
| primary ;
|
||||
primary → NUMBER | STRING | "true" | "false" | "nil"
|
||||
| "(" expression ")" ;
|
||||
unary → ( "!" | "-" ) unary | primary ;
|
||||
primary → NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" ;
|
||||
*/
|
||||
|
||||
#include "ast.h"
|
||||
#include "lexer.h"
|
||||
#include "stmt.h"
|
||||
|
||||
expr_t *parse(token_t *tks);
|
||||
stmt_array_t *parse(token_t *tks);
|
||||
expr_t *parse_expr(token_t *tks);
|
||||
void free_expr(expr_t *expr);
|
||||
void free_statements(stmt_array_t *array);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
#define STMT_H
|
||||
|
||||
#include "ast.h"
|
||||
#include "scanner.h"
|
||||
#include "lexer.h"
|
||||
|
||||
#define DEFAULT_STMTS_SIZE 512
|
||||
|
||||
typedef enum {
|
||||
STMT_BLOCK,
|
||||
STMT_CLASS,
|
||||
|
@ -13,53 +16,54 @@ typedef enum {
|
|||
STMT_VAR,
|
||||
STMT_WHILE,
|
||||
STMT_COUNT,
|
||||
} StmtType;
|
||||
typedef struct Stmt Stmt;
|
||||
struct Stmt{
|
||||
StmtType type;
|
||||
} stmt_type_t;
|
||||
|
||||
typedef struct stmt_t {
|
||||
stmt_type_t type;
|
||||
union {
|
||||
struct {
|
||||
Stmt **statements;
|
||||
struct stmt_t **statements;
|
||||
} block;
|
||||
struct {
|
||||
Token name;
|
||||
Token superclass;
|
||||
Stmt **methods;
|
||||
token_t name;
|
||||
token_t superclass;
|
||||
struct stmt_t **methods;
|
||||
} class;
|
||||
struct {
|
||||
Expr *expression;
|
||||
expr_t *expression;
|
||||
} expr;
|
||||
struct {
|
||||
Token name;
|
||||
TokenArray params;
|
||||
Stmt **body;
|
||||
token_t name;
|
||||
array_t *params;
|
||||
struct stmt_t **body;
|
||||
} function;
|
||||
struct {
|
||||
Expr *condition;
|
||||
Stmt *thenBranch;
|
||||
Stmt *elseBranch;
|
||||
expr_t *condition;
|
||||
struct stmt_t *thenBranch;
|
||||
struct stmt_t *elseBranch;
|
||||
} _if;
|
||||
struct {
|
||||
Expr *expression;
|
||||
expr_t *expression;
|
||||
} print;
|
||||
struct {
|
||||
Token keyword;
|
||||
Expr *value;
|
||||
token_t keyword;
|
||||
expr_t *value;
|
||||
} _return;
|
||||
struct {
|
||||
Token name;
|
||||
Expr *initializer;
|
||||
token_t name;
|
||||
expr_t *initializer;
|
||||
} variable;
|
||||
struct {
|
||||
Expr *condition;
|
||||
Stmt *body;
|
||||
expr_t *condition;
|
||||
struct stmt_t *body;
|
||||
} _while;
|
||||
} as;
|
||||
};
|
||||
} stmt_t;
|
||||
|
||||
typedef struct {
|
||||
Stmt *statements;
|
||||
int count;
|
||||
struct stmt_t *statements;
|
||||
int length;
|
||||
int capacity;
|
||||
bool hadError;
|
||||
} StmtArray;
|
||||
} stmt_array_t;
|
||||
|
||||
#endif
|
||||
|
|
56
src/ast.c
56
src/ast.c
|
@ -5,29 +5,29 @@
|
|||
#include "ast.h"
|
||||
#include "lexer.h"
|
||||
|
||||
expr_t *create_binary_expr(token_t *binary_op, expr_t *left, expr_t *right)
|
||||
expr_t *create_binary_expr(token_t *operator, expr_t *left, expr_t *right)
|
||||
{
|
||||
expr_t *expr = malloc(sizeof(expr_t));
|
||||
expr->type = BINARY;
|
||||
expr->line = binary_op->line;
|
||||
expr->type = EXPR_BINARY;
|
||||
expr->line = operator->line;
|
||||
expr->as.binary.left = left;
|
||||
expr->as.binary.right = right;
|
||||
expr->as.binary.binary_op.type = binary_op->type;
|
||||
char *bin_op_val = strdup(binary_op->value);
|
||||
expr->as.binary.binary_op.value = bin_op_val;
|
||||
expr->as.binary.binary_op.line = binary_op->line;
|
||||
expr->as.binary.operator.type = operator->type;
|
||||
char *bin_op_val = strdup(operator->value);
|
||||
expr->as.binary.operator.value = bin_op_val;
|
||||
expr->as.binary.operator.line = operator->line;
|
||||
return expr;
|
||||
}
|
||||
|
||||
expr_t *create_unary_expr(token_t *unary_op, expr_t *right)
|
||||
expr_t *create_unary_expr(token_t *operator, expr_t *right)
|
||||
{
|
||||
expr_t *expr = malloc(sizeof(expr_t));
|
||||
expr->type = UNARY;
|
||||
expr->line = unary_op->line;
|
||||
expr->as.unary.unary_op.type = unary_op->type;
|
||||
char *u_op_val = strdup(unary_op->value);
|
||||
expr->as.unary.unary_op.value = u_op_val;
|
||||
expr->as.unary.unary_op.line = unary_op->line;
|
||||
expr->type = EXPR_UNARY;
|
||||
expr->line = operator->line;
|
||||
expr->as.unary.operator.type = operator->type;
|
||||
char *u_op_val = strdup(operator->value);
|
||||
expr->as.unary.operator.value = u_op_val;
|
||||
expr->as.unary.operator.line = operator->line;
|
||||
expr->as.unary.right = right;
|
||||
return expr;
|
||||
}
|
||||
|
@ -35,28 +35,28 @@ expr_t *create_unary_expr(token_t *unary_op, expr_t *right)
|
|||
expr_t *create_literal_expr(token_t *token)
|
||||
{
|
||||
expr_t *expr = malloc(sizeof(expr_t));
|
||||
expr->type = LITERAL;
|
||||
expr->type = EXPR_LITERAL;
|
||||
expr->line = token->line;
|
||||
switch (token->type) {
|
||||
case NUMBER:
|
||||
case TOKEN_NUMBER:
|
||||
expr->as.literal.value.type = VAL_NUMBER;
|
||||
double num;
|
||||
sscanf(token->value, "%lf", &num);
|
||||
expr->as.literal.value.as.number = num;
|
||||
break;
|
||||
|
||||
case NIL:
|
||||
case TOKEN_NIL:
|
||||
expr->as.literal.value.type = VAL_NIL;
|
||||
expr->as.literal.value.as.number = 0;
|
||||
break;
|
||||
|
||||
case TRUE:
|
||||
case FALSE:
|
||||
case TOKEN_TRUE:
|
||||
case TOKEN_FALSE:
|
||||
expr->as.literal.value.type = VAL_BOOL;
|
||||
expr->as.literal.value.as.boolean = token->type == TRUE;
|
||||
expr->as.literal.value.as.boolean = token->type == TOKEN_TRUE;
|
||||
break;
|
||||
|
||||
case STRING:
|
||||
case TOKEN_STRING:
|
||||
expr->as.literal.value.type = VAL_STRING;
|
||||
char *tkvalue = strdup(token->value);
|
||||
expr->as.literal.value.as.string = tkvalue;
|
||||
|
@ -74,7 +74,7 @@ expr_t *create_grouping_expr(expr_t *expression)
|
|||
return NULL;
|
||||
}
|
||||
expr_t *expr = malloc(sizeof(expr_t));
|
||||
expr->type = GROUPING;
|
||||
expr->type = EXPR_GROUPING;
|
||||
expr->line = expression->line;
|
||||
expr->as.grouping.expression = expression;
|
||||
return expr;
|
||||
|
@ -84,7 +84,7 @@ void print_ast(expr_t *expr)
|
|||
{
|
||||
if (!expr)
|
||||
return;
|
||||
if (expr->type == LITERAL) {
|
||||
if (expr->type == EXPR_LITERAL) {
|
||||
switch (expr->as.literal.value.type) {
|
||||
case VAL_BOOL:
|
||||
printf("%s", expr->as.literal.value.as.boolean ? "true" : "false");
|
||||
|
@ -107,17 +107,17 @@ void print_ast(expr_t *expr)
|
|||
printf("%s", expr->as.literal.value.as.string);
|
||||
break;
|
||||
}
|
||||
} else if (expr->type == BINARY) {
|
||||
printf("(%s ", expr->as.binary.binary_op.value);
|
||||
} else if (expr->type == EXPR_BINARY) {
|
||||
printf("(%s ", expr->as.binary.operator.value);
|
||||
print_ast(expr->as.binary.left);
|
||||
printf(" ");
|
||||
print_ast(expr->as.binary.right);
|
||||
printf(")");
|
||||
} else if (expr->type == UNARY) {
|
||||
printf("(%s ", expr->as.unary.unary_op.value);
|
||||
} else if (expr->type == EXPR_UNARY) {
|
||||
printf("(%s ", expr->as.unary.operator.value);
|
||||
print_ast(expr->as.unary.right);
|
||||
printf(")");
|
||||
} else if (expr->type == GROUPING) {
|
||||
} else if (expr->type == EXPR_GROUPING) {
|
||||
printf("(group ");
|
||||
print_ast(expr->as.grouping.expression);
|
||||
printf(")");
|
||||
|
|
|
@ -31,7 +31,7 @@ void runtime_error(const char *message, int line)
|
|||
|
||||
value_t visit_binary(expr_t *expr)
|
||||
{
|
||||
token_type_t op_type = expr->as.binary.binary_op.type;
|
||||
token_type_t op_type = expr->as.binary.operator.type;
|
||||
value_t right = evaluate(expr->as.binary.right);
|
||||
value_t left = evaluate(expr->as.binary.left);
|
||||
|
||||
|
@ -39,19 +39,19 @@ value_t visit_binary(expr_t *expr)
|
|||
if (left.type == VAL_NUMBER && right.type == VAL_NUMBER) {
|
||||
value_t result = {.type = VAL_NUMBER};
|
||||
switch (op_type) {
|
||||
case PLUS:
|
||||
case TOKEN_PLUS:
|
||||
result.as.number = left.as.number + right.as.number;
|
||||
return result;
|
||||
|
||||
case MINUS:
|
||||
case TOKEN_MINUS:
|
||||
result.as.number = left.as.number - right.as.number;
|
||||
return result;
|
||||
|
||||
case STAR:
|
||||
case TOKEN_STAR:
|
||||
result.as.number = left.as.number * right.as.number;
|
||||
return result;
|
||||
|
||||
case SLASH:
|
||||
case TOKEN_SLASH:
|
||||
if (right.as.number == 0) {
|
||||
runtime_error("Division by zero.", expr->line);
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ value_t visit_binary(expr_t *expr)
|
|||
}
|
||||
|
||||
// Comparison
|
||||
if (op_type == EQUAL_EQUAL || op_type == BANG_EQUAL) {
|
||||
if (op_type == TOKEN_EQUAL_EQUAL || op_type == TOKEN_BANG_EQUAL) {
|
||||
int is_equal;
|
||||
if (left.type != right.type) {
|
||||
is_equal = 0;
|
||||
|
@ -91,7 +91,7 @@ value_t visit_binary(expr_t *expr)
|
|||
}
|
||||
}
|
||||
value_t result = {.type = VAL_BOOL};
|
||||
result.as.boolean = op_type == EQUAL_EQUAL ? is_equal : !is_equal;
|
||||
result.as.boolean = op_type == TOKEN_EQUAL_EQUAL ? is_equal : !is_equal;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -99,19 +99,19 @@ value_t visit_binary(expr_t *expr)
|
|||
if (left.type == VAL_NUMBER && right.type == VAL_NUMBER) {
|
||||
value_t result = {.type = VAL_BOOL };
|
||||
switch (op_type) {
|
||||
case GREATER:
|
||||
case TOKEN_GREATER:
|
||||
result.as.boolean= left.as.number > right.as.number;
|
||||
return result;
|
||||
|
||||
case GREATER_EQUAL:
|
||||
case TOKEN_GREATER_EQUAL:
|
||||
result.as.boolean = left.as.number >= right.as.number;
|
||||
return result;
|
||||
|
||||
case LESS:
|
||||
case TOKEN_LESS:
|
||||
result.as.boolean = left.as.number < right.as.number;
|
||||
return result;
|
||||
|
||||
case LESS_EQUAL:
|
||||
case TOKEN_LESS_EQUAL:
|
||||
result.as.boolean = left.as.number <= right.as.number;
|
||||
return result;
|
||||
|
||||
|
@ -121,7 +121,7 @@ value_t visit_binary(expr_t *expr)
|
|||
|
||||
// String concatenation
|
||||
if (left.type == VAL_STRING && right.type == VAL_STRING) {
|
||||
if (op_type == PLUS) {
|
||||
if (op_type == TOKEN_PLUS) {
|
||||
value_t result = {.type = VAL_STRING};
|
||||
size_t left_len = strlen(left.as.string);
|
||||
size_t right_len = strlen(right.as.string);
|
||||
|
@ -133,7 +133,8 @@ value_t visit_binary(expr_t *expr)
|
|||
}
|
||||
|
||||
// String/number comparisons
|
||||
if ((left.type == VAL_STRING && right.type == VAL_NUMBER) || (left.type == VAL_NUMBER && right.type == VAL_STRING)) {
|
||||
if ((left.type == VAL_STRING && right.type == VAL_NUMBER) ||
|
||||
(left.type == VAL_NUMBER && right.type == VAL_STRING)) {
|
||||
runtime_error("Operands must be numbers.", expr->line);
|
||||
}
|
||||
|
||||
|
@ -168,14 +169,14 @@ value_t visit_unary(expr_t *expr)
|
|||
{
|
||||
value_t operand = evaluate(expr->as.unary.right);
|
||||
|
||||
if (expr->as.unary.unary_op.type == MINUS) {
|
||||
if (expr->as.unary.operator.type == TOKEN_MINUS) {
|
||||
if (operand.type == VAL_NUMBER) {
|
||||
value_t result = {.type = VAL_NUMBER, .as.number = -operand.as.number};
|
||||
return result;
|
||||
} else {
|
||||
runtime_error("Operand must be a number.", expr->line);
|
||||
}
|
||||
} else if (expr->as.unary.unary_op.type == BANG) {
|
||||
} else if (expr->as.unary.operator.type == TOKEN_BANG) {
|
||||
value_t result = {.type = VAL_BOOL, .as.boolean = !is_truthy(&operand)};
|
||||
return result;
|
||||
}
|
||||
|
@ -190,16 +191,16 @@ value_t evaluate(expr_t *expr)
|
|||
return nil_value;
|
||||
}
|
||||
switch (expr->type) {
|
||||
case LITERAL:
|
||||
case EXPR_LITERAL:
|
||||
return visit_literal(expr);
|
||||
case BINARY:
|
||||
case EXPR_BINARY:
|
||||
return visit_binary(expr);
|
||||
case UNARY:
|
||||
case EXPR_UNARY:
|
||||
return visit_unary(expr);
|
||||
case GROUPING:
|
||||
case EXPR_GROUPING:
|
||||
return visit_grouping(expr);
|
||||
default:
|
||||
errno = 65;
|
||||
exit(65);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -231,3 +232,20 @@ void print_value(value_t *value)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void print_statement(stmt_t stmt)
|
||||
{
|
||||
if (stmt.type == STMT_PRINT) {
|
||||
value_t obj = evaluate(stmt.as.print.expression);
|
||||
print_value(&obj);
|
||||
/* } else if (stmt.type == STMT_EXPR) { */
|
||||
/* value_t obj = evaluate(stmt.as.expr.expression); */
|
||||
/* print_value(&obj); */
|
||||
}
|
||||
}
|
||||
void print_statements(stmt_array_t *array)
|
||||
{
|
||||
for (int i = 0; i < array->length; i++) {
|
||||
print_statement(array->statements[i]);
|
||||
}
|
||||
}
|
||||
|
|
99
src/lexer.c
99
src/lexer.c
|
@ -7,35 +7,35 @@
|
|||
#include "lexer.h"
|
||||
|
||||
const keyword_map reserved_keywords[] = {
|
||||
{"and", AND}, {"class", CLASS}, {"else", ELSE},
|
||||
{"false", FALSE}, {"fun", FUN}, {"for", FOR},
|
||||
{"if", IF}, {"nil", NIL}, {"or", OR},
|
||||
{"print", PRINT}, {"return", RETURN},
|
||||
{"super", SUPER}, {"this", THIS}, {"true", TRUE},
|
||||
{"var", VAR}, {"while", WHILE}
|
||||
{"and", TOKEN_AND}, {"class", TOKEN_CLASS}, {"else", TOKEN_ELSE},
|
||||
{"false", TOKEN_FALSE}, {"fun", TOKEN_FUN}, {"for", TOKEN_FOR},
|
||||
{"if", TOKEN_IF}, {"nil", TOKEN_NIL}, {"or", TOKEN_OR},
|
||||
{"print", TOKEN_PRINT}, {"return", TOKEN_RETURN},
|
||||
{"super", TOKEN_SUPER}, {"this", TOKEN_THIS}, {"true", TOKEN_TRUE},
|
||||
{"var", TOKEN_VAR}, {"while", TOKEN_WHILE}
|
||||
};
|
||||
|
||||
const keyword_map regular_tokens[] = {
|
||||
{"LEFT_PAREN", LEFT_PAREN}, {"RIGHT_PAREN", RIGHT_PAREN},
|
||||
{"LEFT_BRACE", LEFT_BRACE}, {"RIGHT_BRACE", RIGHT_BRACE},
|
||||
{"COMMA", COMMA}, {"DOT", DOT}, {"MINUS", MINUS},
|
||||
{"PLUS", PLUS}, {"SEMICOLON", SEMICOLON}, {"SLASH", SLASH},
|
||||
{"STAR", STAR}, {"BANG", BANG}, {"BANG_EQUAL", BANG_EQUAL},
|
||||
{"EQUAL", EQUAL}, {"EQUAL_EQUAL", EQUAL_EQUAL}, {"GREATER", GREATER},
|
||||
{"GREATER_EQUAL", GREATER_EQUAL}, {"LESS", LESS}, {"LESS_EQUAL", LESS_EQUAL},
|
||||
{"IDENTIFIER", IDENTIFIER}, {"STRING", STRING}, {"NUMBER", NUMBER},
|
||||
{"AND", AND}, {"CLASS", CLASS}, {"ELSE", ELSE}, {"FALSE", FALSE},
|
||||
{"FUN", FUN}, {"FOR", FOR}, {"IF", IF}, {"NIL", NIL},
|
||||
{"OR", OR}, {"PRINT", PRINT}, {"RETURN", RETURN},
|
||||
{"SUPER", SUPER}, {"THIS", THIS}, {"TRUE", TRUE},
|
||||
{"VAR", VAR}, {"WHILE", WHILE}, {"END_OF_FILE", END_OF_FILE}
|
||||
{"LEFT_PAREN", TOKEN_LEFT_PAREN}, {"RIGHT_PAREN", TOKEN_RIGHT_PAREN},
|
||||
{"LEFT_BRACE", TOKEN_LEFT_BRACE}, {"RIGHT_BRACE", TOKEN_RIGHT_BRACE},
|
||||
{"COMMA", TOKEN_COMMA}, {"DOT", TOKEN_DOT}, {"MINUS", TOKEN_MINUS},
|
||||
{"PLUS", TOKEN_PLUS}, {"SEMICOLON", TOKEN_SEMICOLON}, {"SLASH", TOKEN_SLASH},
|
||||
{"STAR", TOKEN_STAR}, {"BANG", TOKEN_BANG}, {"BANG_EQUAL", TOKEN_BANG_EQUAL},
|
||||
{"EQUAL", TOKEN_EQUAL}, {"EQUAL_EQUAL", TOKEN_EQUAL_EQUAL}, {"GREATER", TOKEN_GREATER},
|
||||
{"GREATER_EQUAL", TOKEN_GREATER_EQUAL}, {"LESS", TOKEN_LESS}, {"LESS_EQUAL", TOKEN_LESS_EQUAL},
|
||||
{"IDENTIFIER", TOKEN_IDENTIFIER}, {"STRING", TOKEN_STRING}, {"NUMBER", TOKEN_NUMBER},
|
||||
{"AND", TOKEN_AND}, {"CLASS", TOKEN_CLASS}, {"ELSE", TOKEN_ELSE}, {"FALSE", TOKEN_FALSE},
|
||||
{"FUN", TOKEN_FUN}, {"FOR", TOKEN_FOR}, {"IF", TOKEN_IF}, {"NIL", TOKEN_NIL},
|
||||
{"OR", TOKEN_OR}, {"PRINT", TOKEN_PRINT}, {"RETURN", TOKEN_RETURN},
|
||||
{"SUPER", TOKEN_SUPER}, {"THIS", TOKEN_THIS}, {"TRUE", TOKEN_TRUE},
|
||||
{"VAR", TOKEN_VAR}, {"WHILE", TOKEN_WHILE}, {"END_OF_FILE", TOKEN_EOF}
|
||||
};
|
||||
|
||||
char *read_source(const char *filename)
|
||||
{
|
||||
FILE *file = fopen(filename, "r");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "rd: Error reading file: %s\n", filename);
|
||||
fprintf(stderr, "Error reading file: %s\n", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -45,14 +45,14 @@ char *read_source(const char *filename)
|
|||
|
||||
char *source = malloc(file_size + 1);
|
||||
if (source == NULL) {
|
||||
fprintf(stderr, "rd: Error allocating memory\n");
|
||||
fprintf(stderr, "Memory allocation failed\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(source, 1, file_size, file);
|
||||
if (bytes_read < file_size) {
|
||||
fprintf(stderr, "rd: Error reading file contents\n");
|
||||
fprintf(stderr, "Error reading file contents\n");
|
||||
free(source);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
|
@ -105,11 +105,11 @@ char *type_str(token_type_t type)
|
|||
|
||||
void print_tokens(token_t *tokens)
|
||||
{
|
||||
for (int i = 0; tokens[i].type != END_OF_FILE; i++) {
|
||||
for (int i = 0; tokens[i].type != TOKEN_EOF; i++) {
|
||||
token_t token = tokens[i];
|
||||
if (token.type == STRING) {
|
||||
if (token.type == TOKEN_STRING) {
|
||||
printf("STRING \"%s\" %s\n", token.value, token.value);
|
||||
} else if (token.type == NUMBER) {
|
||||
} else if (token.type == TOKEN_NUMBER) {
|
||||
double value = strtod(token.value, NULL);
|
||||
if (value == (int) value) {
|
||||
printf("NUMBER %s %d.0\n", token.value, (int) value);
|
||||
|
@ -142,74 +142,71 @@ array_t *tokenize(char *filename)
|
|||
tokens->capacity = DEFAULT_TOKENS_SIZE;
|
||||
|
||||
char *source = read_source(filename);
|
||||
if (!source) {
|
||||
return NULL;
|
||||
}
|
||||
int line = 1;
|
||||
size_t source_len = strlen(source);
|
||||
if (source_len > 0) {
|
||||
for (int i = 0; i < source_len; i++) {
|
||||
switch (source[i]) {
|
||||
case '(':
|
||||
token_add(tokens, token_gen(LEFT_PAREN, "(", line));
|
||||
token_add(tokens, token_gen(TOKEN_LEFT_PAREN, "(", line));
|
||||
break;
|
||||
case ')':
|
||||
token_add(tokens, token_gen(RIGHT_PAREN, ")", line));
|
||||
token_add(tokens, token_gen(TOKEN_RIGHT_PAREN, ")", line));
|
||||
break;
|
||||
case '{':
|
||||
token_add(tokens, token_gen(LEFT_BRACE, "{", line));
|
||||
token_add(tokens, token_gen(TOKEN_LEFT_BRACE, "{", line));
|
||||
break;
|
||||
case '}':
|
||||
token_add(tokens, token_gen(RIGHT_BRACE, "}", line));
|
||||
token_add(tokens, token_gen(TOKEN_RIGHT_BRACE, "}", line));
|
||||
break;
|
||||
case '*':
|
||||
token_add(tokens, token_gen(STAR, "*", line));
|
||||
token_add(tokens, token_gen(TOKEN_STAR, "*", line));
|
||||
break;
|
||||
case '.':
|
||||
token_add(tokens, token_gen(DOT, ".", line));
|
||||
token_add(tokens, token_gen(TOKEN_DOT, ".", line));
|
||||
break;
|
||||
case ',':
|
||||
token_add(tokens, token_gen(COMMA, ",", line));
|
||||
token_add(tokens, token_gen(TOKEN_COMMA, ",", line));
|
||||
break;
|
||||
case '+':
|
||||
token_add(tokens, token_gen(PLUS, "+", line));
|
||||
token_add(tokens, token_gen(TOKEN_PLUS, "+", line));
|
||||
break;
|
||||
case '-':
|
||||
token_add(tokens, token_gen(MINUS, "-", line));
|
||||
token_add(tokens, token_gen(TOKEN_MINUS, "-", line));
|
||||
break;
|
||||
case ';':
|
||||
token_add(tokens, token_gen(SEMICOLON, ";", line));
|
||||
token_add(tokens, token_gen(TOKEN_SEMICOLON, ";", line));
|
||||
break;
|
||||
case '=':
|
||||
if (source[i + 1] == '=') {
|
||||
token_add(tokens, token_gen(EQUAL_EQUAL, "==", line));
|
||||
token_add(tokens, token_gen(TOKEN_EQUAL_EQUAL, "==", line));
|
||||
i++;
|
||||
} else {
|
||||
token_add(tokens, token_gen(EQUAL, "=", line));
|
||||
token_add(tokens, token_gen(TOKEN_EQUAL, "=", line));
|
||||
}
|
||||
break;
|
||||
case '!':
|
||||
if (source[i + 1] == '=') {
|
||||
token_add(tokens, token_gen(BANG_EQUAL, "!=", line));
|
||||
token_add(tokens, token_gen(TOKEN_BANG_EQUAL, "!=", line));
|
||||
i++;
|
||||
} else {
|
||||
token_add(tokens, token_gen(BANG, "!", line));
|
||||
token_add(tokens, token_gen(TOKEN_BANG, "!", line));
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if (source[i + 1] == '=') {
|
||||
token_add(tokens, token_gen(GREATER_EQUAL, ">=", line));
|
||||
token_add(tokens, token_gen(TOKEN_GREATER_EQUAL, ">=", line));
|
||||
i++;
|
||||
} else {
|
||||
token_add(tokens, token_gen(GREATER, ">", line));
|
||||
token_add(tokens, token_gen(TOKEN_GREATER, ">", line));
|
||||
}
|
||||
break;
|
||||
case '<':
|
||||
if (source[i + 1] == '=') {
|
||||
token_add(tokens, token_gen(LESS_EQUAL, "<=", line));
|
||||
token_add(tokens, token_gen(TOKEN_LESS_EQUAL, "<=", line));
|
||||
i++;
|
||||
} else {
|
||||
token_add(tokens, token_gen(LESS, "<", line));
|
||||
token_add(tokens, token_gen(TOKEN_LESS, "<", line));
|
||||
}
|
||||
break;
|
||||
case '/':
|
||||
|
@ -220,7 +217,7 @@ array_t *tokenize(char *filename)
|
|||
}
|
||||
i--;
|
||||
} else {
|
||||
token_add(tokens, token_gen(SLASH, "/", line));
|
||||
token_add(tokens, token_gen(TOKEN_SLASH, "/", line));
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
|
@ -246,7 +243,7 @@ array_t *tokenize(char *filename)
|
|||
char str[len + 1];
|
||||
strncpy(str, &source[str_start], len);
|
||||
str[len] = 0;
|
||||
token_add(tokens, token_gen(STRING, str, line));
|
||||
token_add(tokens, token_gen(TOKEN_STRING, str, line));
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -277,7 +274,7 @@ array_t *tokenize(char *filename)
|
|||
}
|
||||
}
|
||||
if (!found) {
|
||||
token_add(tokens, token_gen(IDENTIFIER, id, line));
|
||||
token_add(tokens, token_gen(TOKEN_IDENTIFIER, id, line));
|
||||
}
|
||||
i--;
|
||||
break;
|
||||
|
@ -296,7 +293,7 @@ array_t *tokenize(char *filename)
|
|||
char integer[len + 1];
|
||||
strncpy(integer, iend, len);
|
||||
integer[len] = 0;
|
||||
token_add(tokens, token_gen(NUMBER, integer, line));
|
||||
token_add(tokens, token_gen(TOKEN_NUMBER, integer, line));
|
||||
i--;
|
||||
|
||||
} else {
|
||||
|
@ -306,7 +303,7 @@ array_t *tokenize(char *filename)
|
|||
}
|
||||
}
|
||||
}
|
||||
token_add(tokens, token_gen(END_OF_FILE, "EOF", line));
|
||||
token_add(tokens, token_gen(TOKEN_EOF, "EOF", line));
|
||||
free(source);
|
||||
return tokens;
|
||||
}
|
||||
|
|
147
src/parser.c
147
src/parser.c
|
@ -8,12 +8,11 @@
|
|||
|
||||
int current = 0;
|
||||
token_t *tokens;
|
||||
|
||||
expr_t *expression(void);
|
||||
|
||||
void error(token_t *token, char *message)
|
||||
{
|
||||
if (token->type == END_OF_FILE) {
|
||||
if (token->type == TOKEN_EOF) {
|
||||
fprintf(stderr, "[line %d] at end: %s\n", token->line, message);
|
||||
} else {
|
||||
fprintf(stderr, "[line %d] at '%s': %s\n", token->line, token->value, message);
|
||||
|
@ -26,25 +25,25 @@ void free_expr(expr_t *expr)
|
|||
if (!expr)
|
||||
return;
|
||||
switch (expr->type) {
|
||||
case BINARY:
|
||||
free(expr->as.binary.binary_op.value);
|
||||
case EXPR_BINARY:
|
||||
free(expr->as.binary.operator.value);
|
||||
free_expr(expr->as.binary.left);
|
||||
free_expr(expr->as.binary.right);
|
||||
free(expr);
|
||||
break;
|
||||
|
||||
case GROUPING:
|
||||
case EXPR_GROUPING:
|
||||
free_expr(expr->as.grouping.expression);
|
||||
free(expr);
|
||||
break;
|
||||
|
||||
case UNARY:
|
||||
free(expr->as.unary.unary_op.value);
|
||||
case EXPR_UNARY:
|
||||
free(expr->as.unary.operator.value);
|
||||
free_expr(expr->as.unary.right);
|
||||
free(expr);
|
||||
break;
|
||||
|
||||
case LITERAL:
|
||||
case EXPR_LITERAL:
|
||||
if (expr->as.literal.value.type == VAL_STRING) {
|
||||
free(expr->as.literal.value.as.string);
|
||||
}
|
||||
|
@ -56,24 +55,14 @@ void free_expr(expr_t *expr)
|
|||
}
|
||||
}
|
||||
|
||||
expr_t *parse(token_t *tks)
|
||||
{
|
||||
tokens = tks;
|
||||
if (errno == 65) {
|
||||
return NULL;
|
||||
} else {
|
||||
return expression();
|
||||
}
|
||||
}
|
||||
|
||||
token_t *peek(void)
|
||||
{
|
||||
return &tokens[current];
|
||||
}
|
||||
|
||||
int isAtEnd(void)
|
||||
int end(void)
|
||||
{
|
||||
return tokens[current].type == END_OF_FILE;
|
||||
return tokens[current].type == TOKEN_EOF;
|
||||
}
|
||||
|
||||
token_t *previous(void)
|
||||
|
@ -83,11 +72,10 @@ token_t *previous(void)
|
|||
|
||||
void advance(void)
|
||||
{
|
||||
if (!isAtEnd())
|
||||
if (!end())
|
||||
current++;
|
||||
}
|
||||
|
||||
|
||||
int check(token_type_t type)
|
||||
{
|
||||
if (tokens[current].type == type) {
|
||||
|
@ -106,12 +94,13 @@ void consume(token_type_t type, char *message) {
|
|||
|
||||
expr_t *primary(void)
|
||||
{
|
||||
if (check(FALSE) || check(TRUE) || check(NIL) || check(NUMBER) || check(STRING)) {
|
||||
if (check(TOKEN_FALSE) || check(TOKEN_TRUE) || check(TOKEN_NIL) ||
|
||||
check(TOKEN_NUMBER) || check(TOKEN_STRING)) {
|
||||
return create_literal_expr(previous());
|
||||
}
|
||||
if (check(LEFT_PAREN)) {
|
||||
if (check(TOKEN_LEFT_PAREN)) {
|
||||
expr_t *expr = expression();
|
||||
consume(RIGHT_PAREN, "Expect ')' after expression.");
|
||||
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
|
||||
return create_grouping_expr(expr);
|
||||
}
|
||||
error(peek(), "Expect expression.");
|
||||
|
@ -120,7 +109,7 @@ expr_t *primary(void)
|
|||
|
||||
expr_t *unary(void)
|
||||
{
|
||||
if (check(BANG) || check(MINUS)) {
|
||||
if (check(TOKEN_BANG) || check(TOKEN_MINUS)) {
|
||||
token_t *operator = previous();
|
||||
expr_t *right = unary();
|
||||
return create_unary_expr(operator, right);
|
||||
|
@ -133,7 +122,7 @@ expr_t *factor(void)
|
|||
{
|
||||
expr_t *expr = unary();
|
||||
|
||||
while (check(SLASH) || check(STAR)) {
|
||||
while (check(TOKEN_SLASH) || check(TOKEN_STAR)) {
|
||||
token_t *operator = previous();
|
||||
expr_t *right = unary();
|
||||
expr = create_binary_expr(operator, expr, right);
|
||||
|
@ -146,7 +135,7 @@ expr_t *term(void)
|
|||
{
|
||||
expr_t *expr = factor();
|
||||
|
||||
while (check(MINUS) || check(PLUS)) {
|
||||
while (check(TOKEN_MINUS) || check(TOKEN_PLUS)) {
|
||||
token_t *operator = previous();
|
||||
expr_t *right = factor();
|
||||
expr = create_binary_expr(operator, expr, right);
|
||||
|
@ -159,7 +148,8 @@ expr_t *comparison(void)
|
|||
{
|
||||
expr_t *expr = term();
|
||||
|
||||
while (check(GREATER) || check(GREATER_EQUAL) || check(LESS) || check(LESS_EQUAL)) {
|
||||
while (check(TOKEN_GREATER) || check(TOKEN_GREATER_EQUAL) || check(TOKEN_LESS)
|
||||
|| check(TOKEN_LESS_EQUAL)) {
|
||||
token_t *operator = previous();
|
||||
expr_t *right = term();
|
||||
expr = create_binary_expr(operator, expr, right);
|
||||
|
@ -172,7 +162,7 @@ expr_t *equality(void)
|
|||
{
|
||||
expr_t *expr = comparison();
|
||||
|
||||
while (check(BANG_EQUAL) || check(EQUAL_EQUAL)) {
|
||||
while (check(TOKEN_BANG_EQUAL) || check(TOKEN_EQUAL_EQUAL)) {
|
||||
token_t *operator = previous();
|
||||
expr_t *right = comparison();
|
||||
expr = create_binary_expr(operator, expr, right);
|
||||
|
@ -186,22 +176,99 @@ expr_t *expression(void)
|
|||
return equality();
|
||||
}
|
||||
|
||||
stmt_t print_stmt(void)
|
||||
{
|
||||
expr_t *value = expression();
|
||||
consume(TOKEN_SEMICOLON, "Expect ; after value.");
|
||||
return (stmt_t) {
|
||||
.type = STMT_PRINT,
|
||||
.as.print.expression = value,
|
||||
};
|
||||
}
|
||||
|
||||
stmt_t expression_stmt(void)
|
||||
{
|
||||
expr_t *expr = expression();
|
||||
consume(TOKEN_SEMICOLON, "Expect ; after expression.");
|
||||
return (stmt_t) {
|
||||
.type = STMT_EXPR,
|
||||
.as.expr.expression = expr,
|
||||
};
|
||||
}
|
||||
|
||||
stmt_t statement(void)
|
||||
{
|
||||
if (check(TOKEN_PRINT))
|
||||
return print_stmt();
|
||||
return expression_stmt();
|
||||
}
|
||||
|
||||
void stmt_add(stmt_array_t *array, stmt_t stmt)
|
||||
{
|
||||
if (array->length == array->capacity) {
|
||||
array->capacity *= 2;
|
||||
array->statements = realloc(array->statements, array->capacity * sizeof(stmt_t));
|
||||
}
|
||||
array->statements[array->length++] = stmt;
|
||||
}
|
||||
|
||||
void free_statements(stmt_array_t *array)
|
||||
{
|
||||
for (int i = 0; i < array->length; i++) {
|
||||
if (array->statements[i].type == STMT_PRINT) {
|
||||
free_expr(array->statements[i].as.print.expression);
|
||||
}
|
||||
if (array->statements[i].type == STMT_EXPR) {
|
||||
free_expr(array->statements[i].as.expr.expression);
|
||||
}
|
||||
}
|
||||
free(array->statements);
|
||||
free(array);
|
||||
}
|
||||
|
||||
stmt_array_t *parse(token_t *tks)
|
||||
{
|
||||
tokens = tks;
|
||||
if (errno == 65) {
|
||||
return NULL;
|
||||
} else {
|
||||
stmt_array_t *statements = malloc(sizeof(stmt_array_t));
|
||||
statements->statements = malloc(DEFAULT_STMTS_SIZE * sizeof(stmt_t));
|
||||
statements->length = 0;
|
||||
statements->capacity = DEFAULT_STMTS_SIZE;
|
||||
while (!end()) {
|
||||
stmt_add(statements, statement());
|
||||
}
|
||||
return statements;
|
||||
}
|
||||
}
|
||||
|
||||
expr_t *parse_expr(token_t *tks)
|
||||
{
|
||||
tokens = tks;
|
||||
if (errno == 65) {
|
||||
return NULL;
|
||||
} else {
|
||||
return expression();
|
||||
}
|
||||
}
|
||||
|
||||
void synchronize(void)
|
||||
{
|
||||
advance();
|
||||
|
||||
while (!isAtEnd()) {
|
||||
if (previous()->type == SEMICOLON) return;
|
||||
while (!end()) {
|
||||
if (previous()->type == TOKEN_SEMICOLON) return;
|
||||
|
||||
switch (peek()->type) {
|
||||
case CLASS:
|
||||
case FUN:
|
||||
case VAR:
|
||||
case FOR:
|
||||
case IF:
|
||||
case WHILE:
|
||||
case PRINT:
|
||||
case RETURN:
|
||||
case TOKEN_CLASS:
|
||||
case TOKEN_FUN:
|
||||
case TOKEN_VAR:
|
||||
case TOKEN_FOR:
|
||||
case TOKEN_IF:
|
||||
case TOKEN_WHILE:
|
||||
case TOKEN_PRINT:
|
||||
case TOKEN_RETURN:
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
|
|
24
src/rd.c
24
src/rd.c
|
@ -11,21 +11,21 @@
|
|||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: rd tokenize|parse|evaluate <filename>\n");
|
||||
fprintf(stderr, "Usage: rd tokenize|parse|evaluate|run <filename>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *command = argv[1];
|
||||
|
||||
if (!strcmp(command, "tokenize")) {
|
||||
array_t *array = tokenize(argv[2]);
|
||||
if (array) {
|
||||
if (!array) {
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(command, "tokenize")) {
|
||||
print_tokens(array->tokens);
|
||||
free_array(array);
|
||||
}
|
||||
} else if (!strcmp(command, "parse")) {
|
||||
array_t *array = tokenize(argv[2]);
|
||||
expr_t *expr = parse(array->tokens);
|
||||
expr_t *expr = parse_expr(array->tokens);
|
||||
if (errno != 65) {
|
||||
print_ast(expr);
|
||||
printf("\n");
|
||||
|
@ -33,10 +33,18 @@ int main(int argc, char **argv)
|
|||
free_array(array);
|
||||
free_expr(expr);
|
||||
} else if (!strcmp(command, "evaluate")) {
|
||||
array_t *array = tokenize(argv[2]);
|
||||
expr_t *expr = parse(array->tokens);
|
||||
expr_t *expr = parse_expr(array->tokens);
|
||||
value_t val = evaluate(expr);
|
||||
print_value(&val);
|
||||
free_array(array);
|
||||
free_expr(expr);
|
||||
} else if (!strcmp(command, "run")) {
|
||||
stmt_array_t *stmts = parse(array->tokens);
|
||||
if (errno != 65) {
|
||||
print_statements(stmts);
|
||||
free_array(array);
|
||||
free_statements(stmts);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Unknown command: %s\n", command);
|
||||
return 1;
|
||||
|
|
Loading…
Reference in a new issue