Skip to content

Commit 327ee9c

Browse files
committed
Added language documentation
1 parent 901cb46 commit 327ee9c

3 files changed

Lines changed: 194 additions & 1 deletion

File tree

About Tiny-C.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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.

code_samples/bad_paren.tc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(i ;

src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ fn parse_tc_file(file: &str) -> Result<TCStatement, Error<Rule>> {
185185
}
186186

187187
fn parse_statement(pair: Pair<Rule>) -> TCStatement {
188+
189+
println!("Tokens of this statement: {:?}\n\n", &(pair.clone().tokens()));
190+
188191
match pair.as_rule() {
189192

190193
Rule::statement => parse_statement(pair.into_inner().next().unwrap()),
@@ -295,7 +298,7 @@ fn main() {
295298

296299
match parse_result {
297300
Ok(ast) => println!("AST: \n {:?}", ast),
298-
_ => println!("Could not parse file"),
301+
Err(e) => println!("Could not parse file: \n {:?}", e),
299302
}
300303
}
301304
}

0 commit comments

Comments
 (0)