1/*
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7%skeleton "lalr1.cc" /* -*- C++ -*- */
8%require "3.4.2"
9%defines
10
11%define api.token.constructor
12%define api.value.type variant
13/*%define parse.assert*/
14%define api.location.file "location.h"
15%define parse.lac full
16/* define parse.trace*/
17%define parse.error verbose
18%no-lines
19%locations
20
21%code requires {
22  #include <string>
23  #include <fstream>
24  #include <sstream>
25  #include "pio_types.h"
26  struct pio_assembler;
27
28  #ifdef _MSC_VER
29  #pragma warning(disable : 4065) // default only switch statement
30  #endif
31}
32
33// The parsing context.
34%param { pio_assembler& pioasm }
35
36%code {
37    #include "pio_assembler.h"
38  #ifdef _MSC_VER
39  #pragma warning(disable : 4244) // possible loss of data (valid warning, but there is a software check / missing cast)
40  #endif
41}
42
43%define api.token.prefix {TOK_}
44
45%token
46    END     0       "end of file"
47
48    NEWLINE         "end of line"
49    COMMA           ","
50    COLON           ":"
51
52    LPAREN          "("
53    RPAREN          ")"
54    LBRACKET        "["
55    RBRACKET        "]"
56    PLUS            "+"
57    MINUS           "-"
58    MULTIPLY        "*"
59    DIVIDE          "/"
60    OR              "|"
61    AND             "&"
62    XOR             "^"
63    POST_DECREMENT  "--"
64    NOT_EQUAL       "!="
65    NOT             "!"
66    REVERSE         "::"
67    EQUAL           "="
68
69    PROGRAM         ".program"
70    WRAP_TARGET     ".wrap_target"
71    WRAP            ".wrap"
72    DEFINE          ".define"
73    SIDE_SET        ".side_set"
74    WORD            ".word"
75    ORIGIN          ".origin"
76    LANG_OPT        ".lang_opt"
77
78    JMP             "jmp"
79    WAIT            "wait"
80    IN              "in"
81    OUT             "out"
82    PUSH            "push"
83    PULL            "pull"
84    MOV             "mov"
85    IRQ             "irq"
86    SET             "set"
87    NOP             "nop"
88
89    PIN             "pin"
90    GPIO            "gpio"
91    OSRE            "osre"
92
93    PINS            "pins"
94    NULL            "null"
95    PINDIRS         "pindirs"
96    BLOCK           "block"
97    NOBLOCK         "noblock"
98    IFEMPTY         "ifempty"
99    IFFULL          "iffull"
100    NOWAIT          "nowait"
101    CLEAR           "clear"
102    REL             "rel"
103    X               "x"
104    Y               "y"
105    EXEC            "exec"
106    PC              "pc"
107    ISR             "isr"
108    OSR             "osr"
109    OPTIONAL        "opt"
110    SIDE            "side"
111    STATUS          "status"
112    PUBLIC          "public"
113;
114
115%token
116    <std::string> ID "identifier"
117    <std::string> STRING "string"
118    <std::string> NON_WS "text"
119    <std::string> CODE_BLOCK_START "code block"
120    <std::string> CODE_BLOCK_CONTENTS "%}" // bit ugly but if there is no end this is what we will be missing
121    <std::string> UNKNOWN_DIRECTIVE
122    <int> INT "integer"
123;
124
125
126%left REVERSE
127%left PLUS MINUS
128%left MULTIPLY DIVIDE
129%left AND OR XOR
130
131%printer { yyo << "..."; } <*>;
132
133%%
134
135file:
136    lines END { if (pioasm.error_count || pioasm.write_output()) YYABORT; }
137    ;
138
139lines:
140  line
141  | lines NEWLINE line;
142
143line:
144    PROGRAM ID                                  { if (!pioasm.add_program(@$, $2)) { std::stringstream msg; msg << "program " << $2 << " already exists"; error(@$, msg.str()); abort(); } }
145  | directive
146  | instruction                                 { pioasm.get_current_program(@1, "instruction").add_instruction($1); }
147  | label_decl instruction                      { auto &p = pioasm.get_current_program(@2, "instruction"); p.add_label($1); p.add_instruction($2); }
148  | label_decl                                  { pioasm.get_current_program(@1, "label").add_label($1); }
149  | code_block
150  | %empty
151  | error                                       { if (pioasm.error_count > 6) { std::cerr << "\ntoo many errors; aborting.\n"; YYABORT; } }
152  ;
153
154code_block:
155  CODE_BLOCK_START CODE_BLOCK_CONTENTS          { std::string of = $1; if (of.empty()) of = output_format::default_name; pioasm.get_current_program(@$, "code block", false, false).add_code_block( code_block(@$, of, $2)); }
156
157%type <std::shared_ptr<symbol>> label_decl;
158label_decl:
159    symbol_def COLON        { $1->is_label = true; $$ = $1; }
160
161directive:
162    DEFINE symbol_def expression      { $2->is_label = false; $2->value = $3; pioasm.get_current_program(@1, ".define", false, false).add_symbol($2); }
163  | ORIGIN value                      { pioasm.get_current_program(@1, ".origin", true).set_origin(@$, $2); }
164  | SIDE_SET value OPTIONAL PINDIRS   { pioasm.get_current_program(@1, ".side_set", true).set_sideset(@$, $2, true, true); }
165  | SIDE_SET value OPTIONAL           { pioasm.get_current_program(@1, ".side_set", true).set_sideset(@$, $2, true, false); }
166  | SIDE_SET value PINDIRS            { pioasm.get_current_program(@1, ".side_set", true).set_sideset(@$, $2, false, true); }
167  | SIDE_SET value                    { pioasm.get_current_program(@1, ".side_set", true).set_sideset(@$, $2, false, false); }
168  | WRAP_TARGET                       { pioasm.get_current_program(@1, ".wrap_target").set_wrap_target(@$); }
169  | WRAP                              { pioasm.get_current_program(@1, ".wrap").set_wrap(@$); }
170  | WORD value                        { pioasm.get_current_program(@1, "instruction").add_instruction(std::shared_ptr<instruction>(new instr_word(@$, $2))); }
171  | LANG_OPT NON_WS NON_WS EQUAL INT  { pioasm.get_current_program(@1, ".lang_opt").add_lang_opt($2, $3, std::to_string($5)); }
172  | LANG_OPT NON_WS NON_WS EQUAL STRING { pioasm.get_current_program(@1, ".lang_opt").add_lang_opt($2, $3, $5); }
173  | LANG_OPT NON_WS NON_WS EQUAL NON_WS { pioasm.get_current_program(@1, ".lang_opt").add_lang_opt($2, $3, $5); }
174  | LANG_OPT error                    { error(@$, "expected format is .lang_opt language option_name = option_value"); }
175  | UNKNOWN_DIRECTIVE                 { std::stringstream msg; msg << "unknown directive " << $1; throw syntax_error(@$, msg.str()); }
176  ;
177
178/* value is a more limited top level expression... requiring parenthesis */
179%type <std::shared_ptr<resolvable>> value;
180value: INT { $$ = resolvable_int(@$, $1); }
181     | ID { $$ = std::shared_ptr<resolvable>(new name_ref(@$, $1)); }
182     | LPAREN expression RPAREN { $$ = $2; }
183
184%type <std::shared_ptr<resolvable>> expression;
185expression:
186     value
187     | expression PLUS expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::add, $1, $3)); }
188     | expression MINUS expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::subtract, $1, $3)); }
189     | expression MULTIPLY expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::multiply, $1, $3));  }
190     | expression DIVIDE expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::divide, $1, $3)); }
191     | expression OR expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::or_, $1, $3)); }
192     | expression AND expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::and_, $1, $3)); }
193     | expression XOR expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::xor_, $1, $3)); }
194     | MINUS expression { $$ = std::shared_ptr<unary_operation>(new unary_operation(@$, unary_operation::negate, $2)); }
195     | REVERSE expression { $$ = std::shared_ptr<unary_operation>(new unary_operation(@$, unary_operation::reverse, $2)); }
196
197%type <std::shared_ptr<instruction>> instruction;
198instruction:
199    base_instruction sideset delay { $$ = $1; $$->sideset = $2; $$->delay = $3; }
200  | base_instruction delay sideset { $$ = $1; $$->delay = $2; $$->sideset = $3; }
201  | base_instruction sideset { $$ = $1; $$->sideset = $2; $$->delay = resolvable_int(@$, 0); }
202  | base_instruction delay { $$ = $1; $$->delay = $2; }
203  | base_instruction { $$ = $1; $$->delay = resolvable_int(@$, 0); }
204
205%type <std::shared_ptr<instruction>> base_instruction;
206base_instruction:
207    NOP                                                   { $$ = std::shared_ptr<instruction>(new instr_nop(@$)); }
208    | JMP condition comma expression                      { $$ = std::shared_ptr<instruction>(new instr_jmp(@$, $2, $4)); }
209    | WAIT value wait_source                              { $$ = std::shared_ptr<instruction>(new instr_wait(@$, $2, $3)); }
210    | WAIT value COMMA value                              { std::stringstream msg; location l; l.begin = @2.end; l.end = @3.end; msg << "expected irq, gpio or pin after the polarity value and before the \",\""; throw yy::parser::syntax_error(l, msg.str()); }
211    | WAIT wait_source                                    { $$ = std::shared_ptr<instruction>(new instr_wait(@$, resolvable_int(@$, 1),  $2)); }
212    | IN in_source comma value                            { $$ = std::shared_ptr<instruction>(new instr_in(@$, $2, $4)); }
213    | OUT out_target comma value                          { $$ = std::shared_ptr<instruction>(new instr_out(@$, $2, $4)); }
214    | PUSH if_full blocking                               { $$ = std::shared_ptr<instruction>(new instr_push(@$, $2, $3)); }
215    | PULL if_empty blocking                              { $$ = std::shared_ptr<instruction>(new instr_pull(@$, $2, $3)); }
216    | MOV mov_target comma mov_op mov_source              { $$ = std::shared_ptr<instruction>(new instr_mov(@$, $2, $5, $4)); }
217    | IRQ irq_modifiers value REL                         { $$ = std::shared_ptr<instruction>(new instr_irq(@$, $2, $3, true)); }
218    | IRQ irq_modifiers value                             { $$ = std::shared_ptr<instruction>(new instr_irq(@$, $2, $3)); }
219    | SET set_target comma value                          { $$ = std::shared_ptr<instruction>(new instr_set(@$, $2, $4)); }
220;
221
222%type <std::shared_ptr<resolvable>> delay;
223delay:
224    LBRACKET expression RBRACKET { $$ = $2; }
225
226%type <std::shared_ptr<resolvable>> sideset;
227sideset:
228    SIDE value { $$ = $2; }
229
230%type <enum condition> condition;
231condition:
232    NOT X                   { $$ = condition::xz; }
233  | X POST_DECREMENT        { $$ = condition::xnz__; }
234  | NOT Y                   { $$ = condition::yz; }
235  | Y POST_DECREMENT        { $$ = condition::ynz__; }
236  | X NOT_EQUAL Y           { $$ = condition::xney; }
237  | PIN                     { $$ = condition::pin; }
238  | NOT OSRE                { $$ = condition::osrez; }
239  | %empty                  { $$ = condition::al; }
240
241%type <std::shared_ptr<wait_source>> wait_source;
242wait_source:
243    IRQ comma value REL     { $$ = std::shared_ptr<wait_source>(new wait_source(wait_source::irq, $3, true)); }
244  | IRQ comma value         { $$ = std::shared_ptr<wait_source>(new wait_source(wait_source::irq, $3, false)); }
245  | GPIO comma value        { $$ = std::shared_ptr<wait_source>(new wait_source(wait_source::gpio, $3)); }
246  | PIN comma value         { $$ = std::shared_ptr<wait_source>(new wait_source(wait_source::pin, $3)); }
247
248comma: COMMA | %empty        /* not a huge fan of forcing commas */
249
250%type <enum in_out_set> in_source;
251in_source: PINS { $$ = in_out_set::in_out_set_pins; }
252    | X         { $$ = in_out_set::in_out_set_x; }
253    | Y         { $$ = in_out_set::in_out_set_y; }
254    | NULL      { $$ = in_out_set::in_out_null; }
255    | ISR       { $$ = in_out_set::in_out_isr; }
256    | OSR       { $$ = in_out_set::in_osr; }
257    | STATUS    { $$ = in_out_set::in_status; }
258
259%type <enum in_out_set> out_target;
260out_target: PINS { $$ = in_out_set::in_out_set_pins; }
261    | X          { $$ = in_out_set::in_out_set_x; }
262    | Y          { $$ = in_out_set::in_out_set_y; }
263    | NULL       { $$ = in_out_set::in_out_null; }
264    | PINDIRS    { $$ = in_out_set::in_out_set_pindirs; }
265    | ISR        { $$ = in_out_set::in_out_isr; }
266    | PC         { $$ = in_out_set::out_set_pc; }
267    | EXEC       { $$ = in_out_set::out_exec; }
268
269%type <enum mov> mov_target;
270mov_target: PINS { $$ = mov::pins; }
271    | X          { $$ = mov::x; }
272    | Y          { $$ = mov::y; }
273    | EXEC       { $$ = mov::exec; }
274    | PC         { $$ = mov::pc; }
275    | ISR        { $$ = mov::isr; }
276    | OSR        { $$ = mov::osr; }
277
278%type <enum mov> mov_source;
279mov_source: PINS { $$ = mov::pins; }
280    | X          { $$ = mov::x; }
281    | Y          { $$ = mov::y; }
282    | NULL       { $$ = mov::null; }
283    | STATUS     { $$ = mov::status; }
284    | ISR        { $$ = mov::isr; }
285    | OSR        { $$ = mov::osr; }
286
287%type <enum mov_op> mov_op;
288mov_op:
289    NOT         { $$ = mov_op::invert; }
290  | REVERSE     { $$ = mov_op::bit_reverse; }
291  | %empty      { $$ = mov_op::none; }
292
293%type <enum in_out_set> set_target;
294set_target:
295    PINS        { $$ = in_out_set::in_out_set_pins; }
296  | X           { $$ = in_out_set::in_out_set_x; }
297  | Y           { $$ = in_out_set::in_out_set_y; }
298  | PINDIRS     { $$ = in_out_set::in_out_set_pindirs; }
299
300%type <bool> if_full;
301if_full:
302    IFFULL { $$ = true; }
303  | %empty { $$ = false; }
304
305%type <bool> if_empty;
306if_empty:
307    IFEMPTY { $$ = true; }
308  | %empty  { $$ = false; }
309
310%type <bool> blocking;
311blocking:
312    BLOCK   { $$ = true; }
313  | NOBLOCK { $$ = false; }
314  | %empty  { $$ = true; }
315
316%type <enum irq> irq_modifiers;
317irq_modifiers:
318    CLEAR          { $$ = irq::clear; }
319  | WAIT           { $$ = irq::set_wait; }
320  | NOWAIT         { $$ = irq::set; }
321  | SET            { $$ = irq::set; }
322  | %empty         { $$ = irq::set; }
323
324%type <std::shared_ptr<symbol>> symbol_def;
325symbol_def:
326    ID              { $$ = std::shared_ptr<symbol>(new symbol(@$, $1)); }
327  | PUBLIC ID       { $$ = std::shared_ptr<symbol>(new symbol(@$, $2, true)); }
328  | MULTIPLY ID     { $$ = std::shared_ptr<symbol>(new symbol(@$, $2, true)); }
329
330%%
331void yy::parser::error(const location_type& l, const std::string& m)
332{
333   if (l.begin.filename) {
334      std::cerr << l << ": " << m << '\n';
335      pioasm.error_count++;
336      if (l.begin.line == l.end.line && *l.begin.filename == *l.end.filename) {
337        std::ifstream file(l.begin.filename->c_str());
338        std::string line;
339        for(int i = 0; i < l.begin.line; ++i) {
340             std::getline(file, line);
341        }
342        fprintf(stderr, "%5d | %s\n", l.begin.line, line.c_str());
343        fprintf(stderr, "%5s | %*s", "", l.begin.column, "^");
344        for (int i = l.begin.column; i < l.end.column - 1; i++) {
345              putc ('~', stderr);
346        }
347        putc ('\n', stderr);
348      }
349  } else {
350      std::cerr << m << '\n';
351  }
352}
353
354