diff options
Diffstat (limited to 'src/parse/binops.c')
| -rw-r--r-- | src/parse/binops.c | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/src/parse/binops.c b/src/parse/binops.c new file mode 100644 index 00000000..f8d0854d --- /dev/null +++ b/src/parse/binops.c @@ -0,0 +1,137 @@ +#include <stdbool.h> +#include <string.h> + +#include "../ast.h" +#include "../stdlib/print.h" +#include "../stdlib/util.h" +#include "containers.h" +#include "context.h" +#include "errors.h" +#include "files.h" +#include "functions.h" +#include "numbers.h" +#include "parse.h" +#include "text.h" +#include "types.h" +#include "utils.h" + +int op_tightness[] = { + [Power] = 9, + [Multiply] = 8, + [Divide] = 8, + [Mod] = 8, + [Mod1] = 8, + [Plus] = 7, + [Minus] = 7, + [Concat] = 6, + [LeftShift] = 5, + [RightShift] = 5, + [UnsignedLeftShift] = 5, + [UnsignedRightShift] = 5, + [Min] = 4, + [Max] = 4, + [Equals] = 3, + [NotEquals] = 3, + [LessThan] = 2, + [LessThanOrEquals] = 2, + [GreaterThan] = 2, + [GreaterThanOrEquals] = 2, + [Compare] = 2, + [And] = 1, + [Or] = 1, + [Xor] = 1, +}; + +public +ast_e match_binary_operator(const char **pos) { + switch (**pos) { + case '+': { + *pos += 1; + return match(pos, "+") ? Concat : Plus; + } + case '-': { + *pos += 1; + if ((*pos)[0] != ' ' && (*pos)[-2] == ' ') // looks like `fn -5` + return Unknown; + return Minus; + } + case '*': *pos += 1; return Multiply; + case '/': *pos += 1; return Divide; + case '^': *pos += 1; return Power; + case '<': { + *pos += 1; + if (match(pos, "=")) return LessThanOrEquals; // "<=" + else if (match(pos, ">")) return Compare; // "<>" + else if (match(pos, "<")) { + if (match(pos, "<")) return UnsignedLeftShift; // "<<<" + return LeftShift; // "<<" + } else return LessThan; + } + case '>': { + *pos += 1; + if (match(pos, "=")) return GreaterThanOrEquals; // ">=" + if (match(pos, ">")) { + if (match(pos, ">")) return UnsignedRightShift; // ">>>" + return RightShift; // ">>" + } + return GreaterThan; + } + default: { + if (match(pos, "!=")) return NotEquals; + else if (match(pos, "==") && **pos != '=') return Equals; + else if (match_word(pos, "and")) return And; + else if (match_word(pos, "or")) return Or; + else if (match_word(pos, "xor")) return Xor; + else if (match_word(pos, "mod1")) return Mod1; + else if (match_word(pos, "mod")) return Mod; + else if (match_word(pos, "_min_")) return Min; + else if (match_word(pos, "_max_")) return Max; + else return Unknown; + } + } +} + +public +ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightness) { + ast_t *lhs = optional(ctx, &pos, parse_term); + if (!lhs) return NULL; + + int64_t starting_line = get_line_number(ctx->file, pos); + int64_t starting_indent = get_indent(ctx, pos); + spaces(&pos); + for (ast_e op; (op = match_binary_operator(&pos)) != Unknown && op_tightness[op] >= min_tightness; spaces(&pos)) { + ast_t *key = NULL; + if (op == Min || op == Max) { + key = NewAST(ctx->file, pos, pos, Var, .name = "$"); + for (bool progress = true; progress;) { + ast_t *new_term; + progress = + (false || (new_term = parse_index_suffix(ctx, key)) + || (new_term = parse_method_call_suffix(ctx, key)) || (new_term = parse_field_suffix(ctx, key)) + || (new_term = parse_fncall_suffix(ctx, key)) || (new_term = parse_optional_suffix(ctx, key)) + || (new_term = parse_non_optional_suffix(ctx, key))); + if (progress) key = new_term; + } + if (key && key->tag == Var) key = NULL; + else if (key) pos = key->end; + } + + whitespace(&pos); + if (get_line_number(ctx->file, pos) != starting_line && get_indent(ctx, pos) < starting_indent) + parser_err(ctx, pos, eol(pos), "I expected this line to be at least as indented than the line above it"); + + ast_t *rhs = parse_infix_expr(ctx, pos, op_tightness[op] + 1); + if (!rhs) break; + pos = rhs->end; + + if (op == Min) { + return NewAST(ctx->file, lhs->start, rhs->end, Min, .lhs = lhs, .rhs = rhs, .key = key); + } else if (op == Max) { + return NewAST(ctx->file, lhs->start, rhs->end, Max, .lhs = lhs, .rhs = rhs, .key = key); + } else { + lhs = new (ast_t, .file = ctx->file, .start = lhs->start, .end = rhs->end, .tag = op, + .__data.Plus.lhs = lhs, .__data.Plus.rhs = rhs); + } + } + return lhs; +} |
