Skip to content

Latest commit

 

History

History
161 lines (113 loc) · 3.91 KB

File metadata and controls

161 lines (113 loc) · 3.91 KB

Contributor & Development Standards

Every contributor should read this before touching the codebase. This document outlines the architectural invariants and coding standards required to maintain the strict mapping between high-level language semantics and native x86-64 assembly.


Core Principle

Architectural transparency is the primary design constraint. Every transformation in the pipeline — from source text to native assembly — must be independently verifiable and debuggable at the instruction level. Prefer explicitness over abstraction whenever the two are in tension.


Python Style

  • Standard Python only. No exotic syntax.
  • Type hints on every function signature. Nothing else fancy.
  • Functions under 30 lines. If longer, split it.
  • One function, one job.
  • Descriptive names: parse_expression, not pe or _p_expr_r.
# Good
def parse_expression(self) -> ASTNode:
    left = self.parse_term()
    while self.current_token.type == TokenType.PLUS:
        self.advance()
        right = self.parse_term()
        left = BinaryOp(op='+', left=left, right=right)
    return left

# Bad — too compressed, hard to explain
def pe(self): return self._reduce(self.pt, {T.PLUS: BinaryOp})

What to Avoid

  • No metaclasses, decorators beyond @dataclass, or descriptor magic
  • No *args / **kwargs unless there is a clear reason
  • No list/dict comprehensions longer than one line
  • No chained method calls more than 2 deep
  • No regex for parsing — use the lexer
  • No third-party libraries for compilation logic (no PLY, ANTLR, Lark)

Data Structures

Use @dataclass for AST nodes and tokens. Keep them flat and readable.

# Good
@dataclass
class BinaryOp(ASTNode):
    op: str
    left: ASTNode
    right: ASTNode

# Bad
class BinaryOp(ASTNode, metaclass=NodeMeta, slots=True, frozen=True): ...

Error Handling

Use the project's CompilerError class. Always include line and column. Never use bare raise Exception("something went wrong").

raise CompilerError(
    message="undefined variable 'x'",
    line=token.line,
    col=token.col
)

Comments

Write comments that explain why, not what.

# Good: explains a non-obvious constraint
# Advance past the closing paren before parsing the body —
# otherwise the body parser will consume it as an expression.
self.expect(TokenType.RPAREN)

# Bad: restates the code
self.advance()  # advance the token

Adding a New Module

Follow this order so reviewers can read top-to-bottom without forward references:

  1. Data structures (@dataclass)
  2. Main class with __init__
  3. Public methods
  4. Private helpers

Project Scope

The compiler targets integer-based systems tasks on x86-64 with strict register pressure management. Correctness and pipeline traceability take precedence over throughput optimization. Avoid premature optimization; prefer algorithms whose invariants are locally verifiable over those that require global context to reason about.


Quick Build Reference

Complete workflow from Nova source to native executable.

Compile Nova → NASM assembly

python main.py run examples/fibonacci.nv
# Writes: fibonacci.asm

Optional flags:

python main.py run examples/fibonacci.nv --dump-ast   # print AST
python main.py run examples/fibonacci.nv --dump-ir    # print TAC before/after optimisation

Assemble and link — macOS (Mach-O 64-bit)

nasm -f macho64 fibonacci.asm -o fibonacci.o
gcc fibonacci.o -o fibonacci
./fibonacci

Assemble and link — Linux (ELF 64-bit)

nasm -f elf64 fibonacci.asm -o fibonacci.o
gcc fibonacci.o -o fibonacci
./fibonacci

Run the test suite

pytest tests/ -v

One-liner: compile → assemble → run (macOS)

python main.py run examples/fibonacci.nv && \
  nasm -f macho64 fibonacci.asm -o fibonacci.o && \
  gcc fibonacci.o -o fibonacci && \
  ./fibonacci