Skip to content

Commit

Permalink
Added base compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
myrma committed Jun 13, 2016
1 parent 0e2a39b commit 5cff447
Show file tree
Hide file tree
Showing 15 changed files with 903 additions and 45 deletions.
29 changes: 3 additions & 26 deletions acid/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@
acid
====

Ce module est la racine de notre compilateur. Il définit des opérations et des
types pour les différentes étapes du processus:

- *Tokenizing*: il s'agit du découpage du code en une liste de lexèmes.
- *Parsing*: cela consiste à regrouper nos lexèmes en un AST.

Rappel:

**Un lexème** est un bout de chaine de caractère auquel on associe un type. Ici
Je représente mes lexèmes par la classe `Token`, et leur types par l'énumération
`TokenType`.

**Un AST** (de l'anglais *Abstract Syntax Tree*, arbre de syntaxe abstraite)
est la représentation du programme avant sa compilation ou son exécution.
On appelle ça un arbre car on représente souvent le résultat sous cette forme
(troisième étape):

```
parenthèse, plus, +
┌───────┐ parenthèse, étoile, ┌────────┐ / \
(+ (* 3 2) 7) ───┤ lexer ├─▶ nombre(3), nombre(2), ───┤ parser ├──▶ * 7
└───────┘ parenthèse, nombre(7), └────────┘ / \
parenthèse 3 2
CODE LEXEMES AST
```
`acid` est le module racine de notre projet. Il est composé de deux sous-modules
`parser` et ̀`compiler`. Chacun de ces sous-modules est chargé d'une étape
spécifique, de la lecture de notre code Acid brut à son exécution.
3 changes: 1 addition & 2 deletions acid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@
# coding: utf-8

from acid.parser import *
from acid.lexer import *
from acid.compiler import *
from acid.exception import *
from acid.types import *
37 changes: 20 additions & 17 deletions acid/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import os
import argparse

from acid import tokenize, parse
from acid import *


arg_parser = argparse.ArgumentParser(
Expand All @@ -22,29 +22,32 @@
help='tokenize the given input file')
arg_parser.add_argument('--ast', '-a', dest='ast', action='store_true',
help='parse the given input file')
arg_parser.add_argument('--compile', '-c', dest='compile', action='store_true',
help='compile the given input file')
arg_parser.add_argument('--compile', '-c', dest='comp', action='store_true',
help='compile the given input file')


def main(path, lex=False, ast=False, compile=False):
with open(path) as file:
code = file.read()
def main(path, lex=False, ast=False, comp=False):
path = os.path.abspath(path)

if lex:
for token in tokenize(code):
print(token)
if lex:
for token in tokenize(code):
print(token)

elif ast:
tree = parse(code, os.path.abspath(path))
print(tree)
elif ast:
parser = Parser.from_file(path)
tree = parser.run()
print(tree)

elif compile:
raise NotImplementedError('Compiling is not implemented yet')
elif comp:
compiler = Compiler.from_file(path)
compiler.dump()

else:
if path.endswith('.acidc'):
Compiler.execute_compiled_file(path)
else:
raise NotImplementedError('The interpreter is not implemented yet')
# when the interpreter will be implemented
# execute(code)
compiler = Compiler.from_file(path)
compiler.execute()

if __name__ == '__main__':
args = arg_parser.parse_args()
Expand Down
17 changes: 17 additions & 0 deletions acid/compiler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
acid.compiler
=============

Ce module s'occupe de traduire notre AST Acid en AST Python grâce au module `ast`
de la librairie standard de Python. Ainsi, grâce à notre AST Python obtenu, nous
pouvons "compiler" notre code Acid en objet compréhensible par l'interpréteur
Python via la fonction *built-in* `compile`.

Notre classe `Compiler` est chargée de compiler notre code Acid brut en un objet
de code Python, et éventuellement de le stocker dans un fichier binaire en ROM.

Ainsi, nous pouvons exécuter plus tard un code Acid sans passer par les étapes
du *lexing*, du *parsing*, et de la traduction en AST Python.

Note: ce n'est pas de la compilation en code machine, mais plutôt en *bytecode*
de la machine virtuelle de Python. Nous ne pouvons pas transformer notre code
en exécutable (ni sous Windows, ni sous Linux/OS X) avec cette technique.
5 changes: 5 additions & 0 deletions acid/compiler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env python3.4
# coding: utf-8

from acid.compiler.compiler import *
from acid.compiler.translations import *
103 changes: 103 additions & 0 deletions acid/compiler/compiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python3.4
# coding: utf-8

"""
This module defines a compiler class which can compile and dump an Acid code
to a Python code object.
Contributors: myrma
"""

import os
import ast
import marshal

from acid.parser import Parser
from acid.prelude import default_env


class Compiler:
"""
Compiles an Acid AST to a Python AST.
"""

translations = {}

def __init__(self, ast, path=None):
self.path = path
self.ast = ast

@classmethod
def from_file(cls, path):
"""
Loads the Acid AST from a given path.
"""

parser = Parser.from_file(path)
ast = parser.run()
return cls(ast, path)

@classmethod
def execute_compiled_file(cls, path, prelude=default_env):
"""
Executes a Python code object stored in a file.
"""

with open(path, 'rb') as compiled_file:
code = marshal.load(compiled_file)

exec(code, prelude)

@classmethod
def register(cls, *node_types):
"""
Registers a translation from an Acid AST node to a Python AST node.
"""

def _decorator_wrapper(translation):
for node_type in node_types:
cls.translations[node_type] = translation

return translation

return _decorator_wrapper

def translate(self, node):
"""
Translates an Acid AST node into a Python AST node.
"""

py_ast = self.translations[type(node)](self, node)
return ast.fix_missing_locations(py_ast)

def compile(self):
"""
Compiles the Acid AST to a Python code object.
"""

py_ast = self.translate(self.ast)

code = compile(py_ast, self.path or '<string>', mode='exec')
return code

def dump(self, target=None):
"""
Dumps the Python code object to a given path.
"""

if target is None and self.path is None:
raise ValueError('Unspecified target path')

code = self.compile()
target = target or os.path.basename(self.path).split('.')[0] + '.acidc'

with open(target, 'wb') as dump_file:
marshal.dump(code, dump_file)

def execute(self, prelude=default_env):
"""
Executes the resulting Python code object.
"""

code = self.compile()
exec(code, prelude)
69 changes: 69 additions & 0 deletions acid/compiler/translations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3.4
# coding: utf-8

"""
This module defines some conversions between Acid's and Python's AST nodes.
Contributors: myrma
"""

import ast as python_ast

from acid.compiler.compiler import Compiler
from acid.parser.ast import *


@Compiler.register(Program)
def translate_program(compiler, program):
instrs = map(compiler.translate, program.instructions)
module = python_ast.Module(body=list(instrs))
return module


@Compiler.register(Declaration)
def translate_declaration(compiler, declaration):
assign = python_ast.Assign()
assign.targets = [
python_ast.Name(id=declaration.name, ctx=python_ast.Store())
]
assign.value = compiler.translate(declaration.value)
return assign


@Compiler.register(TopLevelExpr)
def translate_toplevel_expr(compiler, expr):
return python_ast.Expr(compiler.translate(expr.expr))


@Compiler.register(Call)
def translate_call(compiler, call):
return python_ast.Call(
func=compiler.translate(call.func),
args=list(map(compiler.translate, call.args)),
keywords=[]
)


@Compiler.register(Lambda)
def translate_lambda(compiler, lambda_):
return python_ast.Lambda(
args=python_ast.arguments(
args=list(map(lambda n: python_ast.arg(arg=n, annotation=None), lambda_.params)),
vararg=None,
kwonlyargs=[],
kw_defaults=[],
kwarg=None,
defaults=[]
),
body=compiler.translate(lambda_.body)
)


@Compiler.register(Variable)
def translate_variable(compiler, var):
return python_ast.Name(var.name, python_ast.Load())


@Compiler.register(IntLiteral, FloatLiteral)
def translate_num(compiler, num):
return python_ast.Num(num.value)
29 changes: 29 additions & 0 deletions acid/parser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
acid.parser
===========

Ce module est la racine de notre parser. Il définit des opérations et des
types pour les différentes étapes du processus:

- *Tokenizing*: il s'agit du découpage du code en une liste de lexèmes.
- *Parsing*: cela consiste à regrouper nos lexèmes en un AST.

Rappel:

**Un lexème** est un bout de chaine de caractère auquel on associe un type. Ici
Je représente mes lexèmes par la classe `Token`, et leur types par l'énumération
`TokenType`.

**Un AST** (de l'anglais *Abstract Syntax Tree*, arbre de syntaxe abstraite)
est la représentation du programme avant sa compilation ou son exécution.
On appelle ça un arbre car on représente souvent le résultat sous cette forme
(troisième étape):

```
parenthèse, plus, +
┌───────┐ parenthèse, étoile, ┌────────┐ / \
(+ (* 3 2) 7) ───┤ lexer ├─▶ nombre(3), nombre(2), ───┤ parser ├──▶ * 7
└───────┘ parenthèse, nombre(7), └────────┘ / \
parenthèse 3 2
CODE LEXEMES AST
```
8 changes: 8 additions & 0 deletions acid/parser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env python3.4
# coding: utf-8

from acid.parser.parser import *
from acid.parser.ast import *
from acid.parser.rules import *
from acid.parser.lexer import *
from acid.parser.types import *
Loading

0 comments on commit 5cff447

Please sign in to comment.