En aquest tema, aprendrem a construir un compilador simple per a un subconjunt del llenguatge ALGOL. Aquest procés ens ajudarà a entendre millor com funcionen els compiladors i ens proporcionarà una base sòlida per a projectes més complexos en el futur.
Objectius del Tema
- Comprendre els components bàsics d'un compilador.
- Aprendre a analitzar sintàcticament un codi font.
- Generar codi intermedi.
- Implementar un compilador simple per a ALGOL.
Components d'un Compilador
Un compilador típic es compon de diverses fases, cadascuna amb una funció específica:
- Anàlisi Lèxica (Lexer): Converteix el codi font en una seqüència de tokens.
- Anàlisi Sintàctica (Parser): Construeix un arbre sintàctic a partir dels tokens.
- Anàlisi Semàntica: Verifica la correcció semàntica de l'arbre sintàctic.
- Generació de Codi Intermedi: Converteix l'arbre sintàctic en una representació intermèdia.
- Optimització de Codi: Millora el codi intermedi per a una execució més eficient.
- Generació de Codi Final: Converteix el codi intermedi en codi màquina o bytecode.
Anàlisi Lèxica
L'anàlisi lèxica és el primer pas en la construcció d'un compilador. El lexer llegeix el codi font i el divideix en tokens, que són les unitats lèxiques bàsiques del llenguatge.
Exemple de Lexer en Pseudocodi
function lexer(input):
tokens = []
i = 0
while i < length(input):
if is_whitespace(input[i]):
i = i + 1
else if is_digit(input[i]):
num = ""
while i < length(input) and is_digit(input[i]):
num = num + input[i]
i = i + 1
tokens.append(Token("NUMBER", num))
else if is_letter(input[i]):
ident = ""
while i < length(input) and is_letter(input[i]):
ident = ident + input[i]
i = i + 1
tokens.append(Token("IDENTIFIER", ident))
else:
tokens.append(Token("SYMBOL", input[i]))
i = i + 1
return tokensExercici Pràctic
Implementa un lexer per a ALGOL que reconegui números, identificadors i símbols.
class Token:
def __init__(self, type, value):
self.type = type
self.value = value
def lexer(input):
tokens = []
i = 0
while i < len(input):
if input[i].isspace():
i += 1
elif input[i].isdigit():
num = ""
while i < len(input) and input[i].isdigit():
num += input[i]
i += 1
tokens.append(Token("NUMBER", num))
elif input[i].isalpha():
ident = ""
while i < len(input) and input[i].isalpha():
ident += input[i]
i += 1
tokens.append(Token("IDENTIFIER", ident))
else:
tokens.append(Token("SYMBOL", input[i]))
i += 1
return tokens
# Prova del lexer
input_code = "x = 10 + 20"
tokens = lexer(input_code)
for token in tokens:
print(f"Type: {token.type}, Value: {token.value}")Anàlisi Sintàctica
L'anàlisi sintàctica pren els tokens generats pel lexer i construeix un arbre sintàctic que representa l'estructura del programa.
Exemple de Parser en Pseudocodi
function parser(tokens):
def parse_expression():
token = tokens.pop(0)
if token.type == "NUMBER":
return NumberNode(token.value)
elif token.type == "IDENTIFIER":
return IdentifierNode(token.value)
elif token.type == "SYMBOL" and token.value == "(":
expr = parse_expression()
match(")")
return expr
else:
raise SyntaxError("Unexpected token: " + token.value)
def match(expected):
token = tokens.pop(0)
if token.value != expected:
raise SyntaxError("Expected " + expected + " but got " + token.value)
return parse_expression()Exercici Pràctic
Implementa un parser per a expressions aritmètiques simples en ALGOL.
class Node:
pass
class NumberNode(Node):
def __init__(self, value):
self.value = value
class IdentifierNode(Node):
def __init__(self, value):
self.value = value
def parser(tokens):
def parse_expression():
token = tokens.pop(0)
if token.type == "NUMBER":
return NumberNode(token.value)
elif token.type == "IDENTIFIER":
return IdentifierNode(token.value)
elif token.type == "SYMBOL" and token.value == "(":
expr = parse_expression()
match(")")
return expr
else:
raise SyntaxError("Unexpected token: " + token.value)
def match(expected):
token = tokens.pop(0)
if token.value != expected:
raise SyntaxError("Expected " + expected + " but got " + token.value)
return parse_expression()
# Prova del parser
tokens = lexer("x = 10 + 20")
ast = parser(tokens)
print(ast)Generació de Codi Intermedi
La generació de codi intermedi converteix l'arbre sintàctic en una representació intermèdia que és més fàcil d'optimitzar i traduir a codi màquina.
Exemple de Generació de Codi Intermedi en Pseudocodi
function generate_code(node):
if node is NumberNode:
return "PUSH " + node.value
elif node is IdentifierNode:
return "LOAD " + node.value
else:
raise ValueError("Unknown node type: " + type(node))Exercici Pràctic
Implementa la generació de codi intermedi per a expressions aritmètiques simples.
def generate_code(node):
if isinstance(node, NumberNode):
return f"PUSH {node.value}"
elif isinstance(node, IdentifierNode):
return f"LOAD {node.value}"
else:
raise ValueError("Unknown node type: " + type(node))
# Prova de la generació de codi intermedi
code = generate_code(ast)
print(code)Resum
En aquest tema, hem après els components bàsics d'un compilador i hem implementat un lexer, un parser i un generador de codi intermedi per a un subconjunt simple d'ALGOL. Aquestes eines ens proporcionen una base sòlida per a la construcció de compiladors més complexos en el futur.
Exercicis Addicionals
- Extensió del Lexer: Modifica el lexer per reconèixer operadors aritmètics (+, -, *, /) i parèntesis.
- Extensió del Parser: Modifica el parser per manejar operacions aritmètiques i expressions amb parèntesis.
- Generació de Codi Final: Implementa una fase de generació de codi final que converteixi el codi intermedi en codi màquina o bytecode.
Solucions als Exercicis
Extensió del Lexer
def lexer(input):
tokens = []
i = 0
while i < len(input):
if input[i].isspace():
i += 1
elif input[i].isdigit():
num = ""
while i < len(input) and input[i].isdigit():
num += input[i]
i += 1
tokens.append(Token("NUMBER", num))
elif input[i].isalpha():
ident = ""
while i < len(input) and input[i].isalpha():
ident += input[i]
i += 1
tokens.append(Token("IDENTIFIER", ident))
elif input[i] in "+-*/()":
tokens.append(Token("SYMBOL", input[i]))
i += 1
else:
raise ValueError("Unknown character: " + input[i])
return tokensExtensió del Parser
class BinaryOpNode(Node):
def __init__(self, left, op, right):
self.left = left
self.op = op
self.right = right
def parser(tokens):
def parse_expression():
left = parse_term()
while tokens and tokens[0].value in "+-":
op = tokens.pop(0).value
right = parse_term()
left = BinaryOpNode(left, op, right)
return left
def parse_term():
left = parse_factor()
while tokens and tokens[0].value in "*/":
op = tokens.pop(0).value
right = parse_factor()
left = BinaryOpNode(left, op, right)
return left
def parse_factor():
token = tokens.pop(0)
if token.type == "NUMBER":
return NumberNode(token.value)
elif token.type == "IDENTIFIER":
return IdentifierNode(token.value)
elif token.type == "SYMBOL" and token.value == "(":
expr = parse_expression()
match(")")
return expr
else:
raise SyntaxError("Unexpected token: " + token.value)
def match(expected):
token = tokens.pop(0)
if token.value != expected:
raise SyntaxError("Expected " + expected + " but got " + token.value)
return parse_expression()Generació de Codi Final
def generate_code(node):
if isinstance(node, NumberNode):
return f"PUSH {node.value}"
elif isinstance(node, IdentifierNode):
return f"LOAD {node.value}"
elif isinstance(node, BinaryOpNode):
left_code = generate_code(node.left)
right_code = generate_code(node.right)
return f"{left_code}\n{right_code}\n{node.op.upper()}"
else:
raise ValueError("Unknown node type: " + type(node))
# Prova de la generació de codi final
tokens = lexer("x = 10 + 20 * (30 - 40)")
ast = parser(tokens)
code = generate_code(ast)
print(code)Amb aquests exercicis i solucions, hauríeu de tenir una comprensió sòlida de com construir un compilador simple per a ALGOL.
Curs de Programació en ALGOL
Mòdul 1: Introducció a ALGOL
Mòdul 2: Sintaxi i Estructura Bàsica
- Estructura del Programa ALGOL
- Variables i Tipus de Dades
- Entrada i Sortida Bàsica
- Operadors i Expressions
Mòdul 3: Estructures de Control
Mòdul 4: Funcions i Procediments
- Definició de Funcions
- Paràmetres de Funció i Valors de Retorn
- Funcions Recursives
- Procediments en ALGOL
Mòdul 5: Estructures de Dades
Mòdul 6: Temes Avançats
Mòdul 7: Aplicacions Pràctiques
- Mètodes Numèrics
- Implementació d'Algorismes
- Construcció d'un Compilador Simple
- Estudis de Cas i Projectes
