| """Q-Lambda lexer, parser, reversible QIR synthesizer, and uncompute pass."""
|
|
|
| from __future__ import annotations
|
|
|
| from dataclasses import dataclass
|
| from enum import Enum
|
| import re
|
| from typing import Dict, Iterable, List, Optional, Tuple, Union
|
|
|
|
|
| class TokenType(Enum):
|
| KW_QBIT = "qbit"
|
| KW_LET = "let"
|
| KW_REVERSED = "reversed"
|
| KW_WITH = "with"
|
| KW_DO = "do"
|
| KW_ORACLE = "oracle"
|
| OP_ROTR = ">>>"
|
| OP_SHR = ">>"
|
| OP_XOR = "^"
|
| OP_AND = "&"
|
| OP_OR = "|"
|
| OP_NOT = "~"
|
| OP_ASSIGN = "="
|
| OP_ADD = "+"
|
| LPAREN = "("
|
| RPAREN = ")"
|
| LBRACE = "{"
|
| RBRACE = "}"
|
| LBRACK = "["
|
| RBRACK = "]"
|
| COMMA = ","
|
| COLON = ":"
|
| SEMI = ";"
|
| IDENT = "IDENT"
|
| INTEGER = "INTEGER"
|
| EOF = "EOF"
|
|
|
|
|
| @dataclass(frozen=True)
|
| class Token:
|
| type: TokenType
|
| value: str
|
| line: int
|
| col: int
|
|
|
|
|
| class Lexer:
|
| TOKEN_REGEX: Tuple[Tuple[TokenType, str], ...] = (
|
| (TokenType.KW_QBIT, r"\bqbit\b"),
|
| (TokenType.KW_LET, r"\blet\b"),
|
| (TokenType.KW_REVERSED, r"\breversed\b"),
|
| (TokenType.KW_WITH, r"\bwith\b"),
|
| (TokenType.KW_DO, r"\bdo\b"),
|
| (TokenType.KW_ORACLE, r"\boracle\b"),
|
| (TokenType.OP_ROTR, r">>>"),
|
| (TokenType.OP_SHR, r">>"),
|
| (TokenType.OP_XOR, r"\^"),
|
| (TokenType.OP_AND, r"&"),
|
| (TokenType.OP_OR, r"\|"),
|
| (TokenType.OP_NOT, r"~"),
|
| (TokenType.OP_ASSIGN, r"="),
|
| (TokenType.OP_ADD, r"\+"),
|
| (TokenType.LPAREN, r"\("),
|
| (TokenType.RPAREN, r"\)"),
|
| (TokenType.LBRACE, r"\{"),
|
| (TokenType.RBRACE, r"\}"),
|
| (TokenType.LBRACK, r"\["),
|
| (TokenType.RBRACK, r"\]"),
|
| (TokenType.COMMA, r","),
|
| (TokenType.COLON, r":"),
|
| (TokenType.SEMI, r";"),
|
| (TokenType.INTEGER, r"\b\d+\b"),
|
| (TokenType.IDENT, r"[a-zA-Z_][a-zA-Z0-9_]*"),
|
| )
|
|
|
| def __init__(self, source: str):
|
| self.source = re.sub(r"//.*", "", expand_unroll_directives(source))
|
| self.pos = 0
|
| self.line = 1
|
| self.col = 1
|
|
|
| def tokenize(self) -> List[Token]:
|
| tokens: List[Token] = []
|
| compiled = [(kind, re.compile(pattern)) for kind, pattern in self.TOKEN_REGEX]
|
| while self.pos < len(self.source):
|
| char = self.source[self.pos]
|
| if char == "\n":
|
| self.line += 1
|
| self.col = 1
|
| self.pos += 1
|
| continue
|
| if char.isspace():
|
| self.col += 1
|
| self.pos += 1
|
| continue
|
|
|
| for tok_type, regex in compiled:
|
| match = regex.match(self.source, self.pos)
|
| if match:
|
| value = match.group(0)
|
| tokens.append(Token(tok_type, value, self.line, self.col))
|
| self.pos += len(value)
|
| self.col += len(value)
|
| break
|
| else:
|
| raise SyntaxError(f"Unexpected character {char!r} at line {self.line}, col {self.col}")
|
| tokens.append(Token(TokenType.EOF, "", self.line, self.col))
|
| return tokens
|
|
|
|
|
| class ASTNode:
|
| """Base class for Q-Lambda AST nodes."""
|
|
|
|
|
| @dataclass(frozen=True)
|
| class TypeNode(ASTNode):
|
| name: str
|
| size: int
|
|
|
|
|
| class ExprNode(ASTNode):
|
| """Base class for Q-Lambda expressions."""
|
|
|
|
|
| @dataclass(frozen=True)
|
| class VarExpr(ExprNode):
|
| name: str
|
|
|
|
|
| @dataclass(frozen=True)
|
| class IntLiteralExpr(ExprNode):
|
| value: int
|
|
|
|
|
| @dataclass(frozen=True)
|
| class BinOpExpr(ExprNode):
|
| op: TokenType
|
| left: ExprNode
|
| right: ExprNode
|
|
|
|
|
| @dataclass(frozen=True)
|
| class UnOpExpr(ExprNode):
|
| op: TokenType
|
| operand: ExprNode
|
|
|
|
|
| class StmtNode(ASTNode):
|
| """Base class for Q-Lambda statements."""
|
|
|
|
|
| @dataclass(frozen=True)
|
| class LetStmt(StmtNode):
|
| var_name: str
|
| var_type: TypeNode
|
| value: ExprNode
|
|
|
|
|
| @dataclass(frozen=True)
|
| class WithDoStmt(StmtNode):
|
| bindings: List[LetStmt]
|
| body: List[StmtNode]
|
|
|
|
|
| @dataclass(frozen=True)
|
| class ReversedBlockStmt(StmtNode):
|
| body: List[StmtNode]
|
|
|
|
|
| @dataclass(frozen=True)
|
| class OracleDeclStmt(StmtNode):
|
| name: str
|
| params: List[Tuple[str, TypeNode]]
|
| returns: TypeNode
|
| body: List[StmtNode]
|
|
|
|
|
| class Parser:
|
| PRECEDENCE = {
|
| TokenType.OP_XOR: 1,
|
| TokenType.OP_OR: 1,
|
| TokenType.OP_AND: 2,
|
| TokenType.OP_ADD: 2,
|
| TokenType.OP_ROTR: 3,
|
| TokenType.OP_SHR: 3,
|
| }
|
|
|
| def __init__(self, tokens: List[Token]):
|
| self.tokens = tokens
|
| self.pos = 0
|
|
|
| def peek(self) -> Token:
|
| return self.tokens[self.pos]
|
|
|
| def consume(self, expected_type: TokenType) -> Token:
|
| current = self.peek()
|
| if current.type != expected_type:
|
| raise SyntaxError(
|
| f"Expected {expected_type.value}, got {current.type.value} "
|
| f"({current.value!r}) at line {current.line}"
|
| )
|
| self.pos += 1
|
| return current
|
|
|
| def parse(self) -> List[StmtNode]:
|
| nodes: List[StmtNode] = []
|
| while self.peek().type != TokenType.EOF:
|
| nodes.append(self.parse_statement())
|
| return nodes
|
|
|
| def parse_statement(self) -> StmtNode:
|
| token = self.peek()
|
| if token.type == TokenType.KW_LET:
|
| return self.parse_let()
|
| if token.type == TokenType.KW_WITH:
|
| return self.parse_with_do()
|
| if token.type == TokenType.KW_REVERSED:
|
| return self.parse_reversed()
|
| if token.type == TokenType.KW_ORACLE:
|
| return self.parse_oracle()
|
| raise SyntaxError(f"Unexpected statement starting with {token.value!r}")
|
|
|
| def parse_let(self) -> LetStmt:
|
| self.consume(TokenType.KW_LET)
|
| var_name = self.consume(TokenType.IDENT).value
|
| self.consume(TokenType.COLON)
|
| var_type = self.parse_type()
|
| self.consume(TokenType.OP_ASSIGN)
|
| value = self.parse_expr()
|
| self.consume(TokenType.SEMI)
|
| return LetStmt(var_name, var_type, value)
|
|
|
| def parse_type(self) -> TypeNode:
|
| type_name = self.consume(TokenType.KW_QBIT).value
|
| self.consume(TokenType.LBRACK)
|
| size = int(self.consume(TokenType.INTEGER).value)
|
| self.consume(TokenType.RBRACK)
|
| return TypeNode(type_name, size)
|
|
|
| def parse_with_do(self) -> WithDoStmt:
|
| self.consume(TokenType.KW_WITH)
|
| self.consume(TokenType.LPAREN)
|
| bindings: List[LetStmt] = []
|
| while self.peek().type != TokenType.RPAREN:
|
| bindings.append(self.parse_let())
|
| self.consume(TokenType.RPAREN)
|
| self.consume(TokenType.KW_DO)
|
| body = self.parse_block()
|
| return WithDoStmt(bindings, body)
|
|
|
| def parse_reversed(self) -> ReversedBlockStmt:
|
| self.consume(TokenType.KW_REVERSED)
|
| return ReversedBlockStmt(self.parse_block())
|
|
|
| def parse_oracle(self) -> OracleDeclStmt:
|
| self.consume(TokenType.KW_ORACLE)
|
| name = self.consume(TokenType.IDENT).value
|
| self.consume(TokenType.LPAREN)
|
| params: List[Tuple[str, TypeNode]] = []
|
| while self.peek().type != TokenType.RPAREN:
|
| param_name = self.consume(TokenType.IDENT).value
|
| self.consume(TokenType.COLON)
|
| params.append((param_name, self.parse_type()))
|
| if self.peek().type == TokenType.COMMA:
|
| self.consume(TokenType.COMMA)
|
| self.consume(TokenType.RPAREN)
|
| self.consume(TokenType.COLON)
|
| returns = self.parse_type()
|
| body = self.parse_block()
|
| return OracleDeclStmt(name, params, returns, body)
|
|
|
| def parse_block(self) -> List[StmtNode]:
|
| self.consume(TokenType.LBRACE)
|
| body: List[StmtNode] = []
|
| while self.peek().type != TokenType.RBRACE:
|
| body.append(self.parse_statement())
|
| self.consume(TokenType.RBRACE)
|
| return body
|
|
|
| def parse_expr(self) -> ExprNode:
|
| return self.parse_binary_expr(0)
|
|
|
| def parse_binary_expr(self, precedence: int) -> ExprNode:
|
| left = self.parse_primary()
|
| while True:
|
| op = self.peek().type
|
| if op not in self.PRECEDENCE or self.PRECEDENCE[op] < precedence:
|
| break
|
| self.consume(op)
|
| right = self.parse_binary_expr(self.PRECEDENCE[op] + 1)
|
| left = BinOpExpr(op, left, right)
|
| return left
|
|
|
| def parse_primary(self) -> ExprNode:
|
| token = self.peek()
|
| if token.type == TokenType.OP_NOT:
|
| self.consume(TokenType.OP_NOT)
|
| return UnOpExpr(TokenType.OP_NOT, self.parse_primary())
|
| if token.type == TokenType.IDENT:
|
| return VarExpr(self.consume(TokenType.IDENT).value)
|
| if token.type == TokenType.INTEGER:
|
| return IntLiteralExpr(int(self.consume(TokenType.INTEGER).value))
|
| if token.type == TokenType.LPAREN:
|
| self.consume(TokenType.LPAREN)
|
| expr = self.parse_expr()
|
| self.consume(TokenType.RPAREN)
|
| return expr
|
| raise SyntaxError(f"Unexpected expression token: {token.value!r}")
|
|
|
|
|
| @dataclass(frozen=True)
|
| class QIRInstruction:
|
| gate: str
|
| controls: Tuple[int, ...]
|
| targets: Tuple[int, ...]
|
| params: Tuple[Union[int, float], ...] = ()
|
|
|
|
|
| class QIREngine:
|
| def __init__(self):
|
| self.qubit_counter = 0
|
| self.instructions: List[QIRInstruction] = []
|
| self.scopes: List[Dict[str, List[int]]] = [{}]
|
|
|
| def allocate(self, name: str, size: int) -> List[int]:
|
| if size <= 0:
|
| raise ValueError("qbit register size must be positive")
|
| qids = list(range(self.qubit_counter, self.qubit_counter + size))
|
| self.qubit_counter += size
|
| self.scopes[-1][name] = qids
|
| return qids
|
|
|
| def resolve(self, name: str) -> List[int]:
|
| for scope in reversed(self.scopes):
|
| if name in scope:
|
| return scope[name]
|
| raise NameError(f"Quantum register {name!r} not found")
|
|
|
| def emit(
|
| self,
|
| gate: str,
|
| controls: Iterable[int] = (),
|
| targets: Iterable[int] = (),
|
| params: Iterable[Union[int, float]] = (),
|
| ) -> None:
|
| self.instructions.append(
|
| QIRInstruction(gate, tuple(controls), tuple(targets), tuple(params))
|
| )
|
|
|
| def push_scope(self) -> None:
|
| self.scopes.append({})
|
|
|
| def pop_scope(self) -> Dict[str, List[int]]:
|
| if len(self.scopes) == 1:
|
| raise RuntimeError("cannot pop root scope")
|
| return self.scopes.pop()
|
|
|
|
|
| class QLambdaCompiler:
|
| def __init__(self, ast: List[StmtNode]):
|
| self.ast = ast
|
| self.qir = QIREngine()
|
|
|
| def compile(self) -> List[QIRInstruction]:
|
| for node in self.ast:
|
| self.visit(node)
|
| return self.qir.instructions
|
|
|
| def visit(self, node: StmtNode) -> None:
|
| if isinstance(node, OracleDeclStmt):
|
| self.visit_oracle(node)
|
| elif isinstance(node, LetStmt):
|
| self.visit_let(node)
|
| elif isinstance(node, WithDoStmt):
|
| self.visit_with_do(node)
|
| elif isinstance(node, ReversedBlockStmt):
|
| self.visit_reversed(node)
|
| else:
|
| raise NotImplementedError(type(node).__name__)
|
|
|
| def visit_oracle(self, node: OracleDeclStmt) -> None:
|
| self.qir.push_scope()
|
| for param_name, param_type in node.params:
|
| self.qir.allocate(param_name, param_type.size)
|
| self.qir.allocate(f"{node.name}_out", node.returns.size)
|
| for stmt in node.body:
|
| self.visit(stmt)
|
| self.qir.pop_scope()
|
|
|
| def visit_let(self, node: LetStmt) -> None:
|
| target = self.qir.allocate(node.var_name, node.var_type.size)
|
| self.synthesize_expr(node.value, target)
|
|
|
| def visit_with_do(self, node: WithDoStmt) -> None:
|
| self.qir.push_scope()
|
| binding_start = len(self.qir.instructions)
|
| for binding in node.bindings:
|
| self.visit_let(binding)
|
| binding_end = len(self.qir.instructions)
|
| for stmt in node.body:
|
| self.visit(stmt)
|
| self.qir.instructions.extend(self.invert_circuit(self.qir.instructions[binding_start:binding_end]))
|
| self.qir.pop_scope()
|
|
|
| def visit_reversed(self, node: ReversedBlockStmt) -> None:
|
| marker = len(self.qir.instructions)
|
| for stmt in node.body:
|
| self.visit(stmt)
|
| self.qir.instructions = self.qir.instructions[:marker] + self.invert_circuit(
|
| self.qir.instructions[marker:]
|
| )
|
|
|
| def synthesize_expr(self, expr: ExprNode, target: List[int]) -> None:
|
| if isinstance(expr, VarExpr):
|
| self.copy_register(self.qir.resolve(expr.name), target)
|
| elif isinstance(expr, IntLiteralExpr):
|
| for index, qid in enumerate(target):
|
| if (expr.value >> index) & 1:
|
| self.qir.emit("X", targets=[qid])
|
| elif isinstance(expr, UnOpExpr) and expr.op == TokenType.OP_NOT:
|
| self.synthesize_expr(expr.operand, target)
|
| for qid in target:
|
| self.qir.emit("X", targets=[qid])
|
| elif isinstance(expr, BinOpExpr):
|
| self.synthesize_binop(expr, target)
|
| else:
|
| raise NotImplementedError(f"Cannot synthesize {expr!r}")
|
|
|
| def synthesize_binop(self, expr: BinOpExpr, target: List[int]) -> None:
|
| if expr.op == TokenType.OP_XOR:
|
| self.synthesize_expr(expr.left, target)
|
| self.synthesize_expr(expr.right, target)
|
| elif expr.op == TokenType.OP_AND:
|
| marker = len(self.qir.instructions)
|
| left = self.temp("_and_l", len(target))
|
| right = self.temp("_and_r", len(target))
|
| self.synthesize_expr(expr.left, left)
|
| self.synthesize_expr(expr.right, right)
|
| temp_program = self.qir.instructions[marker:]
|
| for lq, rq, tq in zip(left, right, target):
|
| self.qir.emit("CCX", controls=[lq, rq], targets=[tq])
|
| self.qir.instructions.extend(self.invert_circuit(temp_program))
|
| elif expr.op == TokenType.OP_ROTR:
|
| shift = self.literal_shift(expr.right)
|
| src = self.temp("_rotr", len(target))
|
| self.synthesize_expr(expr.left, src)
|
| width = len(target)
|
| for index, tq in enumerate(target):
|
| self.qir.emit("CX", controls=[src[(index + shift) % width]], targets=[tq])
|
| elif expr.op == TokenType.OP_SHR:
|
| shift = self.literal_shift(expr.right)
|
| src = self.temp("_shr", len(target))
|
| self.synthesize_expr(expr.left, src)
|
| for index, tq in enumerate(target):
|
| src_index = index + shift
|
| if src_index < len(src):
|
| self.qir.emit("CX", controls=[src[src_index]], targets=[tq])
|
| elif expr.op == TokenType.OP_ADD:
|
| self.synthesize_modular_add(expr.left, expr.right, target)
|
| else:
|
| raise NotImplementedError(f"Unsupported operator {expr.op.value}")
|
|
|
| def synthesize_modular_add(self, left_expr: ExprNode, right_expr: ExprNode, target: List[int]) -> None:
|
| width = len(target)
|
| left = self.temp("_add_l", width)
|
| right = self.temp("_add_r", width)
|
| carry = self.temp("_carry", width + 1)
|
| self.synthesize_expr(left_expr, left)
|
| self.synthesize_expr(right_expr, right)
|
| self.copy_register(left, target)
|
|
|
| for index in range(width):
|
| self.qir.emit("CCX", controls=[target[index], right[index]], targets=[carry[index + 1]])
|
| self.qir.emit("CX", controls=[target[index]], targets=[right[index]])
|
| self.qir.emit("CCX", controls=[right[index], carry[index]], targets=[carry[index + 1]])
|
| self.qir.emit("CX", controls=[right[index]], targets=[target[index]])
|
|
|
| for index in range(width - 1, -1, -1):
|
| self.qir.emit("CX", controls=[right[index]], targets=[target[index]])
|
| self.qir.emit("CCX", controls=[right[index], carry[index]], targets=[carry[index + 1]])
|
| self.qir.emit("CX", controls=[target[index]], targets=[right[index]])
|
| self.qir.emit("CCX", controls=[target[index], right[index]], targets=[carry[index + 1]])
|
|
|
| def copy_register(self, source: List[int], target: List[int]) -> None:
|
| if len(source) < len(target):
|
| raise ValueError("source register is narrower than target register")
|
| for src, dst in zip(source, target):
|
| self.qir.emit("CX", controls=[src], targets=[dst])
|
|
|
| def temp(self, prefix: str, size: int) -> List[int]:
|
| return self.qir.allocate(f"{prefix}_{self.qir.qubit_counter}", size)
|
|
|
| @staticmethod
|
| def literal_shift(expr: ExprNode) -> int:
|
| if not isinstance(expr, IntLiteralExpr):
|
| raise TypeError("shift/rotate amount must be an integer literal")
|
| return expr.value
|
|
|
| @staticmethod
|
| def invert_circuit(instructions: List[QIRInstruction]) -> List[QIRInstruction]:
|
| inverted: List[QIRInstruction] = []
|
| for inst in reversed(instructions):
|
| if inst.gate in {"X", "CX", "CCX", "H"}:
|
| inverted.append(inst)
|
| elif inst.gate == "T":
|
| inverted.append(QIRInstruction("TDG", inst.controls, inst.targets, inst.params))
|
| elif inst.gate == "TDG":
|
| inverted.append(QIRInstruction("T", inst.controls, inst.targets, inst.params))
|
| elif inst.gate == "ROTR":
|
| shift, width = int(inst.params[0]), int(inst.params[1])
|
| inverted.append(QIRInstruction("ROTR", inst.controls, inst.targets, ((width - shift) % width, width)))
|
| else:
|
| inverted.append(QIRInstruction(f"{inst.gate}_DAGGER", inst.controls, inst.targets, inst.params))
|
| return inverted
|
|
|
|
|
| def expand_unroll_directives(source: str) -> str:
|
| pattern = re.compile(r"#unroll\s+(\d+)\s+for\s+(\w+)\s+in\s+(\d+)\.\.(\d+)\s*\{", re.M)
|
| while True:
|
| match = pattern.search(source)
|
| if not match:
|
| return source
|
| count = int(match.group(1))
|
| var = match.group(2)
|
| start = int(match.group(3))
|
| end = int(match.group(4))
|
| body_start = match.end()
|
| depth = 1
|
| pos = body_start
|
| while pos < len(source) and depth:
|
| if source[pos] == "{":
|
| depth += 1
|
| elif source[pos] == "}":
|
| depth -= 1
|
| pos += 1
|
| body = source[body_start : pos - 1]
|
| if count != end - start + 1:
|
| raise ValueError("unroll count must match inclusive range length")
|
| expanded = "\n".join(
|
| body.replace(f"{{{var}}}", str(value)).replace(f"${var}", str(value))
|
| for value in range(start, end + 1)
|
| )
|
| source = source[: match.start()] + expanded + source[pos:]
|
|
|
|
|
| def compile_source(source: str) -> List[QIRInstruction]:
|
| return QLambdaCompiler(Parser(Lexer(source).tokenize()).parse()).compile()
|
|
|