aboutsummaryrefslogtreecommitdiff
path: root/src/parser.y
diff options
context:
space:
mode:
Diffstat (limited to 'src/parser.y')
-rw-r--r--src/parser.y666
1 files changed, 666 insertions, 0 deletions
diff --git a/src/parser.y b/src/parser.y
new file mode 100644
index 0000000..4a70341
--- /dev/null
+++ b/src/parser.y
@@ -0,0 +1,666 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+%{
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include <qbt/parser.h>
+#include <qbt/debug.h>
+#include <qbt/nodes.h>
+
+struct ret_helper {
+ const char *r;
+ enum val_type t;
+};
+
+%}
+
+%locations
+
+%define parse.trace
+%define parse.error verbose
+%define api.pure full
+%define lr.type ielr
+
+%lex-param {void *scanner} {struct parser *parser}
+%parse-param {void *scanner} {struct parser* parser}
+
+%union {
+ struct val val;
+ struct ret_helper ret;
+ enum val_type type;
+ int64_t integer;
+ char *str;
+};
+
+%token <integer> INT
+%token <str> STRING
+%token <str> ID
+
+%token LEXASSIGN "="
+%token LEXCOLON ":"
+%token LEXBANG "!"
+%token LEXSTAR "*"
+%token LEXDIV "/"
+%token LEXREM "%"
+%token LEXMINUS "-"
+%token LEXPLUS "+"
+%token LEXAND "&"
+%token LEXLT "<"
+%token LEXGT ">"
+%token LEXLE "<="
+%token LEXGE ">="
+%token LEXNE "!="
+%token LEXEQ "=="
+%token LEXCOMMA ","
+%token LEXLPAREN "("
+%token LEXRPAREN ")"
+%token LEXLBRACE "{"
+%token LEXRBRACE "}"
+%token LEXFATARROW "=>"
+%token LEXTHINARROW "->"
+%token LEXTO ">>"
+%token LEXFROM "<<"
+%token LEXSEMI ";"
+
+%token LEXI9 "i9"
+%token LEXI27 "i27"
+
+%nterm <val> mem_loc
+%nterm <str> mem_base
+%nterm <integer> mem_off
+%nterm <integer> int
+%nterm <type> type
+
+%nterm <str> id addr local label
+%nterm <val> arg opt_arg
+%nterm <ret> call_ret opt_call_ret
+
+%{
+
+/** Modifies the signature of yylex to fit our parser better. */
+#define YY_DECL int yylex(YYSTYPE *yylval, YYLTYPE *yylloc, \
+ void *yyscanner, struct parser *parser)
+
+/**
+ * Declare yylex.
+ *
+ * @param yylval Bison current value.
+ * @param yylloc Bison location info.
+ * @param yyscanner Flex scanner.
+ * @param parser Current parser state.
+ * @return \c 0 when succesful, \c 1 otherwise.
+ * More info on yylex() can be found in the flex manual.
+ */
+YY_DECL;
+
+/**
+ * Convert bison location info to our own source location info.
+ *
+ * @param yylloc Bison location info.
+ * @return Internal location info.
+ */
+static struct src_loc src_loc(YYLTYPE yylloc);
+
+/**
+ * Print parsing error.
+ * Automatically called by bison.
+ *
+ * @param yylloc Location of error.
+ * @param lexer Lexer.
+ * @param parser Parser state.
+ * @param msg Message to print.
+ */
+static void yyerror(YYLTYPE *yylloc, void *lexer,
+ struct parser *parser, const char *msg);
+
+/**
+ * Try to convert escape code to its actual value.
+ * I.e. '\n' -> 0x0a.
+ *
+ * @param c Escape character without backslash.
+ * @return Corresponding value.
+ */
+static long long match_escape(char c);
+
+/**
+ * Similar to strdup() but skips quotation marks that would
+ * otherwise be included.
+ * I.e. "something" -> something.
+ *
+ * @param s String to clone, with quotation marks surrounding it.
+ * @return Identical string but without quotation marks around it.
+ */
+static char *clone_string(const char *s);
+
+static inline struct val do_idalloc(struct parser *p, const char *id)
+{
+ int64_t t = idalloc(p->f, id);
+ return tmp_val(t);
+}
+
+static inline struct val do_idtoval(struct parser *p, const char *id)
+{
+ int64_t t = idmatch(p->f, id);
+ if (t < 0) {
+ error("no such temporary: %s\n", id);
+ abort();
+ }
+
+ return tmp_val(t);
+}
+
+static inline void do_new_block(struct parser *p,
+ enum insn_type type,
+ struct val a0,
+ struct val a1,
+ const char *label)
+{
+ finish_block(p->b, type, a0, a1, label);
+ p->b = p->b->s1 = new_block(p->f);
+}
+
+static inline void do_insadd(struct parser *p,
+ enum insn_type cmp,
+ enum val_type t,
+ struct val r,
+ struct val a0,
+ struct val a1)
+{
+ insadd(p->b, cmp, t, r, a0, a1);
+}
+
+static inline void do_new_function(struct parser *p, const char *name)
+{
+ finish_function(p->f, name);
+ vec_append(&p->fns, &(struct fn_map){.id = name, .fn = p->f});
+ p->f = new_function();
+ p->b = blk_at(p->f->blks, 0);
+}
+
+static inline char *do_strdup(struct parser *p, const char *s)
+{
+ char *new = strdup(s);
+ vec_append(&p->strs, &new);
+ return new;
+}
+
+static inline char *do_strclone(struct parser *p, const char *s)
+{
+ char *new = clone_string(s);
+ vec_append(&p->strs, &new);
+ return new;
+}
+
+static inline void do_new_label(struct parser *p, const char *s)
+{
+ new_label(p->f, p->b, s);
+}
+
+#define INSADD(o, t, r, a0, a1)\
+ do_insadd(parser, o, t, r, a0, a1)
+
+#define IDALLOC(i)\
+ do_idalloc(parser, i)
+
+#define IDTOVAL(i)\
+ do_idtoval(parser, i)
+
+#define NEW_BLOCK(c, a0, a1, l)\
+ do_new_block(parser, c, a0, a1, l)
+
+#define NEW_FUNCTION(n)\
+ do_new_function(parser, n)
+
+#define DUP_STR(s)\
+ do_strdup(parser, s)
+
+#define CLONE_STR(s)\
+ do_strclone(parser, s)
+
+#define NEW_LABEL(s)\
+ do_new_label(parser, s)
+
+%}
+
+%start input;
+%%
+
+id
+ : ID { $$ = DUP_STR($1); }
+ | STRING { $$ = CLONE_STR($1); }
+
+int
+ : INT
+
+type
+ : "i9" { $$ = I9; }
+ | "i27" { $$ = I27; }
+
+const
+ : "i9" int
+ | "i27" int
+
+consts
+ : const "," consts
+ | const ","
+ | const
+
+opt_consts
+ : consts
+ | {}
+
+data
+ : id "=" "{" opt_consts "}"
+
+param
+ : type id {
+ struct val t = IDALLOC($[id]);
+ INSADD(PARAM, $[type], t, imm_val(parser->idx++, I27), noclass());
+ }
+
+params
+ : param "," params
+ | param ","
+ | param
+
+opt_params
+ : params
+ | {}
+
+ret
+ : type
+
+opt_ret
+ : ret
+ | {}
+
+/* only three return args permitted (keep things simple for now) */
+rets
+ : opt_ret "," opt_ret "," opt_ret
+
+label
+ : id ":" {
+ if (empty_block(parser->b)) {
+ parser->b->name = $[id];
+ } else {
+ NEW_BLOCK(J, noclass(), noclass(), NULL);
+ parser->b->name = $[id];
+ }
+ NEW_LABEL($[id]);
+ }
+
+arg
+ : id {
+ $$ = IDTOVAL($[id]);
+ }
+ | type int {
+ $$ = imm_val($[type], $[int]);
+ }
+
+arith
+ : type id "=" arg "+" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(ADD, $[type], t, $4, $6);
+ }
+ | type id "=" arg "-" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(SUB, $[type], t, $4, $6);
+ }
+ | type id "=" arg "*" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(MUL, $[type], t, $4, $6);
+ }
+ | type id "=" arg "/" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(DIV, $[type], t, $4, $6);
+ }
+ | type id "=" arg "%" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(REM, $[type], t, $4, $6);
+ }
+
+addr
+ : "&" id { $$ = $2; }
+
+imm
+ : type id "=" int {
+ struct val t = IDALLOC($[id]);
+ INSADD(COPY, $[type], t, imm_val($[int], I27), noclass());
+ }
+ | type id "=" addr {
+ struct val t = IDALLOC($[id]);
+ INSADD(COPY, $[type], t, imm_ref($[addr]), noclass());
+ }
+
+move
+ : type id "=" id {
+ struct val t = IDALLOC($2);
+ struct val f = IDTOVAL($4);
+ INSADD(MOVE, $[type], t, f, noclass());
+ }
+
+mem_base
+ : id
+
+mem_off
+ : int
+
+mem_loc
+ : "(" mem_base mem_off ")" {$$ = mem_val(IDTOVAL($[mem_base]).r, $[mem_off]);}
+
+mem
+ : type id "<<" mem_loc {
+ struct val t = IDALLOC($[id]);
+ INSADD(LOAD, $[type], t, $[mem_loc], noclass());
+ }
+ | id ">>" type mem_loc {
+ struct val t = IDALLOC($[id]);
+ INSADD(STORE, $[type], noclass(), t, $[mem_loc]);
+ }
+
+stack
+ : type id "=" "alloc" int {
+ struct val t = IDALLOC($[id]);
+ INSADD(ALLOC, $[type], t, imm_val($[int], I27), noclass());
+ }
+
+cond
+ : type id "=" arg "==" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(EQ, $[type], t, $4, $6);
+ }
+ | type id "=" arg "!=" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(NE, $[type], t, $4, $6);
+ }
+ | type id "=" arg "<=" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(LE, $[type], t, $4, $6);
+ }
+ | type id "=" arg ">=" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(GE, $[type], t, $4, $6);
+ }
+ | type id "=" arg "<" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(LT, $[type], t, $4, $6);
+ }
+ | type id "=" arg ">" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(GT, $[type], t, $4, $6);
+ }
+
+logic
+ : type id "=" "!" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(NOT, $[type], t, $5, noclass());
+ }
+ | type id "=" "-" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(NEG, $[type], t, $5, noclass());
+ }
+ | type id "=" arg "<<" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(LSHIFT, $[type], t, $4, $6);
+ }
+ | type id "=" arg ">>" arg {
+ struct val t = IDALLOC($[id]);
+ INSADD(RSHIFT, $[type], t, $4, $6);
+ }
+
+local
+ : id
+
+branch
+ : arg "==" arg "->" local {
+ NEW_BLOCK(BEQ, $1, $3, $[local]);
+ }
+ | arg "!=" arg "->" local {
+ NEW_BLOCK(BNE, $1, $3, $[local]);
+ }
+ | arg "<=" arg "->" local {
+ NEW_BLOCK(BLE, $1, $3, $[local]);
+ }
+ | arg ">=" arg "->" local {
+ NEW_BLOCK(BGE, $1, $3, $[local]);
+ }
+ | arg "<" arg "->" local {
+ NEW_BLOCK(BLT, $1, $3, $[local]);
+ }
+ | arg ">" arg "->" local {
+ NEW_BLOCK(BGT, $1, $3, $[local]);
+ }
+ | "->" local {
+ NEW_BLOCK(J, noclass(), noclass(), $[local]);
+ }
+
+call_ret
+ : type id {
+ $$ = (struct ret_helper){.r = $[id], .t = $[type]};
+ }
+
+opt_call_ret
+ : call_ret
+ | { $$ = (struct ret_helper){.r = NULL, .t = NOTYPE}; }
+
+call_arg
+ : arg {
+ INSADD(ARG, NOTYPE, noclass(), $[arg], imm_val(parser->idx++, I27));
+ }
+
+call_args
+ : call_arg "," call_args
+ | call_arg ","
+ | call_arg
+
+opt_call_args
+ : call_args
+ | {}
+
+/* empty rule for starting counts */
+reset_index
+ : {parser->idx = 0;}
+
+call
+ : "(" opt_call_ret "," opt_call_ret "," opt_call_ret ")"
+ "=" addr reset_index "(" opt_call_args ")" {
+ /* call args should have inserted their own nodes */
+ INSADD(CALL, NOTYPE, noclass(), imm_ref($[addr]), noclass());
+
+ if ($2.r) {
+ struct val t = IDALLOC($2.r);
+ INSADD(RETVAL, $2.t, t, noclass(), imm_val(0, I27));
+ }
+
+ if ($4.r) {
+ struct val t = IDALLOC($4.r);
+ INSADD(RETVAL, $4.t, t, noclass(), imm_val(1, I27));
+ }
+
+ if ($6.r) {
+ struct val t = IDALLOC($6.r);
+ INSADD(RETVAL, $6.t, t, noclass(), imm_val(2, I27));
+ }
+ }
+
+opt_arg
+ : arg
+ | { $$ = noclass(); }
+
+return
+ : "=>" "(" opt_arg "," opt_arg "," opt_arg ")" {
+ if (!hasnoclass($3))
+ INSADD(RET, NOTYPE, noclass(), $3, imm_val(0, I27));
+
+ if (!hasnoclass($5))
+ INSADD(RET, NOTYPE, noclass(), $5, imm_val(1, I27));
+
+ if (!hasnoclass($7))
+ INSADD(RET, NOTYPE, noclass(), $7, imm_val(2, I27));
+
+ NEW_BLOCK(RET, noclass(), noclass(), NULL);
+ }
+
+insn
+ : arith
+ | imm
+ | move
+ | mem
+ | stack
+ | cond
+ | logic
+ | branch
+ | call
+ | return
+
+body
+ : insn ";" body
+ | label body
+ | label
+ | insn ";"
+
+function
+ : id reset_index "(" opt_params "=>" rets ")" "{" body "}" {
+ NEW_FUNCTION($[id]);
+ }
+
+top
+ : data
+ | function
+
+unit
+ : top unit
+ | top
+
+input
+ : unit
+ | /* empty */
+
+%%
+
+#include "gen_lexer.inc"
+
+static struct src_loc src_loc(YYLTYPE yylloc)
+{
+ struct src_loc loc;
+ loc.first_line = yylloc.first_line;
+ loc.last_line = yylloc.last_line;
+ loc.first_col = yylloc.first_column;
+ loc.last_col = yylloc.last_column;
+ return loc;
+}
+
+static void yyerror(YYLTYPE *yylloc, void *lexer,
+ struct parser *parser, const char *msg)
+{
+ (void)lexer;
+
+ struct src_issue issue;
+ issue.level = SRC_ERROR;
+ issue.loc = src_loc(*yylloc);
+ issue.fctx.fbuf = parser->buf;
+ issue.fctx.fname = parser->fname;
+ src_issue(issue, msg);
+}
+
+static long long match_escape(char c)
+{
+ switch (c) {
+ case '\'': return '\'';
+ case '\\': return '\\';
+ case 'a': return '\a';
+ case 'b': return '\b';
+ case 'f': return '\f';
+ case 'n': return '\n';
+ case 'r': return '\r';
+ case 't': return '\t';
+ case 'v': return '\v';
+ }
+
+ return c;
+}
+
+static char *clone_string(const char *str)
+{
+ const size_t len = strlen(str) + 1;
+ char *buf = malloc(len);
+ if (!buf) {
+ /* should probably try to handle the error in some way... */
+ internal_error("failed allocating buffer for string clone");
+ return NULL;
+ }
+
+ /* skip quotation marks */
+ size_t j = 0;
+ for (size_t i = 1; i < len - 2; ++i) {
+ char c = str[i];
+
+ if (c == '\\')
+ c = match_escape(str[++i]);
+
+ buf[j++] = c;
+ }
+
+ buf[j] = 0;
+ return buf;
+
+}
+
+struct parser *create_parser()
+{
+ return calloc(1, sizeof(struct parser));
+}
+
+void destroy_parser(struct parser *p)
+{
+ foreach_fn(i, p->fns) {
+ struct fn_map m = fn_at(p->fns, i);
+ /* I assume the 'extra' empty function is never appended to the
+ * function vector */
+ assert(m.fn != p->f);
+ destroy_function(m.fn);
+ }
+ vec_destroy(&p->fns);
+ destroy_function(p->f);
+
+ foreach_str(i, p->strs) {
+ char *s = str_at(p->strs, i);
+ free(s);
+ }
+ vec_destroy(&p->strs);
+
+/* data isn't really handled yet properly
+ foreach_data(i, p->datas) {
+ struct data_map m = data_at(p->datas, i);
+ destroy_data(m.data);
+ free((void *)m.id);
+ }
+ */
+
+ yylex_destroy(p->lexer);
+ free(p);
+}
+
+void parse(struct parser *p, const char *fname, const char *buf)
+{
+ p->fname = fname;
+ p->buf = buf;
+
+ p->fns = vec_create(sizeof(struct fn_map));
+ p->strs = vec_create(sizeof(char *));
+
+ p->f = new_function();
+ p->b = blk_at(p->f->blks, 0);
+ p->comment_nesting = 0;
+
+ p->failed = false;
+
+ yylex_init(&p->lexer);
+ yy_scan_string(buf, p->lexer);
+ yyparse(p->lexer, p);
+}