|
| 1 | +## Lenguaje a compilar: Tiny-C |
| 2 | + |
| 3 | +Tiny-C es un subconjunto muy pequeño de C. |
| 4 | + |
| 5 | +Las variables "a" a la "z" son globales, predefinidas e inicializadas a 0. No es posible declarar nuevas variables. Al ejecutar un programa, éste imprime el valor de las variables que no son 0 al finalizar. |
| 6 | + |
| 7 | +El lenguaje no permite crear funciones ni recibir input externo. El control de flujo se hace mediante `while`, `do-while`, `if`, `else-if` y scopes (espacio entre 2 llaves: `{...}`. |
| 8 | + |
| 9 | +Este lenguaje es claramente muy pequeño, por lo que planeo expandirlo una vez la base esté firme. Idealmente, le agregaría: |
| 10 | + |
| 11 | +* Funciones (0..N) -> 1 |
| 12 | +* Comparaciones completas (por el momento sólo acepta "<") |
| 13 | +* Variables del usuario |
| 14 | +* Input de consola (del tipo "a=1, b=100") |
| 15 | +* Arreglos, quizás. |
| 16 | + |
| 17 | +Mediante testing planeo evitar romper lo que ya funciona. Así debiera poder ir creciendo el lenguaje de a poco en lugar de construir un lenguaje grande desde 0. |
| 18 | + |
| 19 | +### Tokens |
| 20 | + |
| 21 | +Los tokens del lenguaje son: |
| 22 | + |
| 23 | +``` |
| 24 | +// Constantes |
| 25 | +int = 1 |
| 26 | +
|
| 27 | +
|
| 28 | +// Variables |
| 29 | +id = a |
| 30 | +
|
| 31 | +// Un término |
| 32 | +term = (a) |
| 33 | +
|
| 34 | +// Suma |
| 35 | +summation = i + j |
| 36 | +// Resta |
| 37 | +substraction = 1 - a |
| 38 | +// Un valor numérico |
| 39 | +sum = (a - 1) + 2 |
| 40 | +
|
| 41 | +// Una comparación (1 == true, other == false) |
| 42 | +comparison = a < 2 |
| 43 | +
|
| 44 | +// Una asignación sobre una variable |
| 45 | +assignment = i = 1 |
| 46 | +// Una expresión. Expresiones siempre tienen valores numéricos |
| 47 | +expr = i = j < (2 - k) |
| 48 | +
|
| 49 | +// Una expresión entre paréntesis |
| 50 | +paren_expr = (i = j < (2 - k)) |
| 51 | +
|
| 52 | +// Statements: |
| 53 | +// Par if-else |
| 54 | +if_else = if (i < j) { i = j; } else { j = i } |
| 55 | +// If |
| 56 | +_if = if (i < j) { i = j; } |
| 57 | +
|
| 58 | +// Do While |
| 59 | +do_while = do {;} while(1); |
| 60 | +// While |
| 61 | +_while = while(1) ; |
| 62 | +
|
| 63 | +// Un Scope con 1 o más Statements |
| 64 | +scoped_statement = { a = b = 1; } |
| 65 | +
|
| 66 | +// Una expresión dentro de un Statement |
| 67 | +expr_statement = (((a = 1))); |
| 68 | +
|
| 69 | +// Un Statement vacío |
| 70 | +semicolon_statement = ; |
| 71 | +
|
| 72 | +// Cualquier Statement |
| 73 | +statement = { i = j; } |
| 74 | +
|
| 75 | +// Un programa |
| 76 | +tiny_c = do ; while (i) ; |
| 77 | +``` |
| 78 | + |
| 79 | +### Gramática |
| 80 | + |
| 81 | +La gramática del lenguaje, en EBNF, es: |
| 82 | + |
| 83 | +``` |
| 84 | +<program> ::= <statement> |
| 85 | +<statement> ::= "if" <paren_expr> <statement> | |
| 86 | + "if" <paren_expr> <statement> "else" <statement> | |
| 87 | + "while" <paren_expr> <statement> | |
| 88 | + "do" <statement> "while" <paren_expr> ";" | |
| 89 | + "{" { <statement>+ } "}" | |
| 90 | + <expr> ";" | |
| 91 | + ";" |
| 92 | +<paren_expr> ::= "(" <expr> ")" |
| 93 | +<expr> ::= <test> | <id> "=" <expr> |
| 94 | +<test> ::= <sum> | <sum> "<" <sum> |
| 95 | +<sum> ::= <term> | <sum> "+" <term> | <sum> "-" <term> |
| 96 | +<term> ::= <id> | <int> | <paren_expr> |
| 97 | +<id> ::= "a" | "b" | "c" | "d" | ... | "z" |
| 98 | +<int> ::= <an_unsigned_decimal_integer> |
| 99 | +``` |
| 100 | + |
| 101 | +Esta gramática es una CFG, pero además se puede convertir a una [PEG](https://en.wikipedia.org/wiki/Parsing_expression_grammar#Expressive_power). Esto nos permite evitar el _backtracking_ a la hora de parsear, y obtener parsing en tiempo lineal usando una librería que se aproveche de esta propiedad. |
| 102 | + |
| 103 | +La gramática final es ésta: |
| 104 | + |
| 105 | +``` |
| 106 | +// Global settings |
| 107 | +WHITESPACE = _{ " " | "\t" | "\r" | "\n" } |
| 108 | +
|
| 109 | +// Positive integers |
| 110 | +int = @{ "0" | ASCII_NONZERO_DIGIT ~ (ASCII_DIGIT)* } |
| 111 | +
|
| 112 | +// Lower letters are the only variables, and they are global. |
| 113 | +id = { ASCII_ALPHA_LOWER } |
| 114 | +
|
| 115 | +// Expressions are always terms, as in C: "a = 5" has the value "5" |
| 116 | +term = { paren_expr | id | int } |
| 117 | +
|
| 118 | +// Everything is a number here, therefore terms are already sums |
| 119 | +summation = { term ~ "+" ~ sum } |
| 120 | +substraction = { term ~ "-" ~ sum } |
| 121 | +sum = { summation | substraction | term } |
| 122 | +
|
| 123 | +// True if "sum" is different from 0, or if $1 < $2 |
| 124 | +comparison = { sum ~ "<" ~ sum } |
| 125 | +
|
| 126 | +// An expression |
| 127 | +assignment = { id ~ "=" ~ expr } |
| 128 | +expr = { assignment | comparison | sum } |
| 129 | +
|
| 130 | +// An expression surrounded by parenthesis |
| 131 | +paren_expr = { "(" ~ expr ~ ")"} |
| 132 | +
|
| 133 | +// All the different Statements |
| 134 | +if_else = { "if" ~ paren_expr ~ statement ~ "else" ~ statement } |
| 135 | +_if = { "if" ~ paren_expr ~ statement } |
| 136 | +do_while = { "do" ~ statement ~ "while" ~ paren_expr ~ ";" } |
| 137 | +_while = { "while" ~ paren_expr ~ statement } |
| 138 | +scoped_statement = { "{" ~ statement+ ~ "}" } |
| 139 | +expr_statement = { expr ~ ";" } |
| 140 | +semicolon_statement = { ";" } |
| 141 | +
|
| 142 | +// |
| 143 | +statement = { |
| 144 | + if_else |
| 145 | + | _if |
| 146 | + | do_while |
| 147 | + | _while |
| 148 | + | scoped_statement |
| 149 | + | expr_statement |
| 150 | + | semicolon_statement |
| 151 | +} |
| 152 | +
|
| 153 | +// The language |
| 154 | +tiny_c = _{ SOI ~ statement ~ EOI } |
| 155 | +
|
| 156 | +``` |
| 157 | + |
| 158 | +Muy importante es el orden de las opciones de cada regla, como por ejemplo para `expr`: |
| 159 | + |
| 160 | + |
| 161 | +`expr = { assignment | comparison | sum }` |
| 162 | + |
| 163 | +Si fuese |
| 164 | + |
| 165 | +`expr = { sum | assignment | comparison }` |
| 166 | + |
| 167 | +Entonces el parser terminaría antes de parsear todo el archivo en casos como: |
| 168 | + |
| 169 | +`i = 5` |
| 170 | + |
| 171 | +Pues antes de encontrar el resultado de `assignment`, encontraría `i`. Y al no poder expandir más el match ni retroceder, falla el parseo. |
| 172 | + |
| 173 | +Algo similar pasa al invertir el orden de `if` y `if_else` o de `while` y `do_while`: |
| 174 | + |
| 175 | +``` |
| 176 | +statement = { |
| 177 | + _if |
| 178 | + | if_else |
| 179 | + | _while |
| 180 | + | do_while |
| 181 | + | scoped_statement |
| 182 | + | expr_statement |
| 183 | + | semicolon_statement |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +Sucede que al encontrar un `if_else`, primero lo parsea como un `if` y luego no sabe qué hacer con el `else` y falla. |
| 188 | + |
| 189 | +Como lección de todo esto, podemos obtener que las PEG son muy útiles pero es necesario tener cuidado de que (1) podamos tener una gramática completa que no retroceda y (2) de poder reordenar bien sus reglas cuando todavía esté tratando de retroceder. |
0 commit comments