diff --git a/elements/neuron/appunti/old/simple_interpreter/example.snip b/elements/neuron/appunti/old/simple_interpreter/example.snip new file mode 100644 index 0000000..0099244 --- /dev/null +++ b/elements/neuron/appunti/old/simple_interpreter/example.snip @@ -0,0 +1,40 @@ +# example.snip — demonstrates the Snippet language + +snippet init + set x = 5 + set y = 10 + print x + print y + +snippet adjust_x + # If x is small, bump it up; otherwise pull it back down + if x < 8 + increase x + increase x + increase x + if x > 6 + increase x + else + decrease x + else + decrease x + print x + +snippet adjust_y + if y > 8 + decrease y + if y > 5 + decrease y + decrease y + else + increase y + else + increase y + print y + +snippet final + # Both snippets share variables — x and y from above are visible here + if x > 7 + print x + else + print y diff --git a/elements/neuron/appunti/old/simple_interpreter/interpreter.py b/elements/neuron/appunti/old/simple_interpreter/interpreter.py new file mode 100644 index 0000000..a6e6bb5 --- /dev/null +++ b/elements/neuron/appunti/old/simple_interpreter/interpreter.py @@ -0,0 +1,480 @@ +#!/usr/bin/env python3 +""" +╔══════════════════════════════════════════════════════════════╗ +║ S N I P P E T L A N G U A G E ║ +║ Interpreter v1.0 ║ +╠══════════════════════════════════════════════════════════════╣ +║ Syntax: ║ +║ snippet ║ +║ set = # assign a variable ║ +║ increase # var += 1 ║ +║ decrease # var -= 1 ║ +║ print # print variable value ║ +║ if > # conditional (> or <) ║ +║ ... ║ +║ else ║ +║ ... ║ +║ ║ +║ Rules: ║ +║ • Snippets run top-to-bottom and share all variables ║ +║ • Indentation defines scope (spaces or tabs, be consistent) +║ • No loops ║ +║ • Numbers only (integers and decimals) ║ +║ • Lines starting with # are comments ║ +╚══════════════════════════════════════════════════════════════╝ + +Usage: + python interpreter.py program.snip # run a file + python interpreter.py # REPL (blank line to run) +""" + +import sys +import re +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Tuple + + +# ══════════════════════════════════════════════════════════════ +# TOKEN TYPES +# ══════════════════════════════════════════════════════════════ + +class TT: + SNIPPET = 'SNIPPET' + IF = 'IF' + ELSE = 'ELSE' + INCREASE = 'INCREASE' + DECREASE = 'DECREASE' + SET = 'SET' + PRINT = 'PRINT' + NAME = 'NAME' + NUMBER = 'NUMBER' + GT = 'GT' + LT = 'LT' + EQ = 'EQ' + +KEYWORDS: Dict[str, str] = { + 'snippet': TT.SNIPPET, + 'if': TT.IF, + 'else': TT.ELSE, + 'increase': TT.INCREASE, + 'decrease': TT.DECREASE, + 'set': TT.SET, + 'print': TT.PRINT, +} + + +@dataclass +class Token: + type: str + value: Any + line: int + + def __repr__(self) -> str: + return f"Token({self.type}, {self.value!r}, line={self.line})" + + +# ══════════════════════════════════════════════════════════════ +# LEXER → list of (indent, [Token …], line_num) +# ══════════════════════════════════════════════════════════════ + +class LexerError(Exception): + pass + + +class Lexer: + def __init__(self, source: str): + self.source = source + + def tokenize(self) -> List[Tuple[int, List[Token], int]]: + result: List[Tuple[int, List[Token], int]] = [] + for line_num, raw in enumerate(self.source.splitlines(), 1): + stripped = raw.rstrip() + if not stripped or stripped.lstrip().startswith('#'): + continue + indent = len(raw) - len(raw.lstrip(' \t')) + tokens = self._lex_words(stripped.lstrip().split(), line_num) + if tokens: + result.append((indent, tokens, line_num)) + return result + + def _lex_words(self, words: List[str], line_num: int) -> List[Token]: + tokens: List[Token] = [] + for word in words: + if word in KEYWORDS: + tokens.append(Token(KEYWORDS[word], word, line_num)) + elif re.fullmatch(r'-?\d+(\.\d+)?', word): + tokens.append(Token(TT.NUMBER, float(word), line_num)) + elif word == '>': + tokens.append(Token(TT.GT, '>', line_num)) + elif word == '<': + tokens.append(Token(TT.LT, '<', line_num)) + elif word == '=': + tokens.append(Token(TT.EQ, '=', line_num)) + elif re.fullmatch(r'[a-zA-Z_]\w*', word): + tokens.append(Token(TT.NAME, word, line_num)) + else: + raise LexerError(f"[Line {line_num}] Unknown token: {word!r}") + return tokens + + +# ══════════════════════════════════════════════════════════════ +# AST NODES +# ══════════════════════════════════════════════════════════════ + +@dataclass +class ProgramNode: + snippets: List + +@dataclass +class SnippetNode: + name: str + body: List + line: int + +@dataclass +class ConditionNode: + left: str # variable name + op: str # '>' or '<' + right: float # literal number + line: int + +@dataclass +class IfNode: + condition: ConditionNode + then_body: List + else_body: List + line: int + +@dataclass +class IncreaseNode: + name: str + line: int + +@dataclass +class DecreaseNode: + name: str + line: int + +@dataclass +class SetNode: + name: str + value: float + line: int + +@dataclass +class PrintNode: + name: str + line: int + + +# ══════════════════════════════════════════════════════════════ +# PARSER +# ══════════════════════════════════════════════════════════════ + +class ParseError(Exception): + pass + + +# Shorthand type for a lexed line +LexLine = Tuple[int, List[Token], int] + + +class Parser: + def __init__(self, lines: List[LexLine]): + self.lines = lines + self.pos = 0 + + # ── low-level helpers ───────────────────────────────────── + + def _peek(self) -> Optional[LexLine]: + return self.lines[self.pos] if self.pos < len(self.lines) else None + + def _consume(self) -> LexLine: + line = self.lines[self.pos] + self.pos += 1 + return line + + # ── top level ───────────────────────────────────────────── + + def parse(self) -> ProgramNode: + snippets = [] + while self._peek(): + indent, tokens, line_num = self._peek() + if tokens[0].type != TT.SNIPPET: + raise ParseError( + f"[Line {line_num}] Expected 'snippet', got {tokens[0].value!r}" + ) + snippets.append(self._parse_snippet()) + return ProgramNode(snippets) + + # ── snippet ─────────────────────────────────────────────── + + def _parse_snippet(self) -> SnippetNode: + indent, tokens, line_num = self._consume() + if len(tokens) < 2 or tokens[1].type != TT.NAME: + raise ParseError(f"[Line {line_num}] Expected a name after 'snippet'") + name = tokens[1].value + body = self._parse_block(parent_indent=indent) + if not body: + raise ParseError(f"[Line {line_num}] Snippet '{name}' has an empty body") + return SnippetNode(name, body, line_num) + + # ── block ───────────────────────────────────────────────── + + def _parse_block(self, parent_indent: int) -> List: + """ + Collect statements whose indentation is strictly greater than + parent_indent and all equal to the first line's indentation. + """ + nxt = self._peek() + if nxt is None or nxt[0] <= parent_indent: + return [] + + block_indent = nxt[0] + stmts = [] + + while True: + nxt = self._peek() + if nxt is None or nxt[0] < block_indent: + break + indent, tokens, line_num = nxt + if indent > block_indent: + raise ParseError( + f"[Line {line_num}] Unexpected indentation " + f"(expected {block_indent}, got {indent})" + ) + if tokens[0].type == TT.ELSE: + break # parent if-parser will handle this + stmts.append(self._parse_statement(block_indent)) + + return stmts + + # ── statements ──────────────────────────────────────────── + + def _parse_statement(self, block_indent: int): + indent, tokens, line_num = self._peek() + tt = tokens[0].type + dispatch = { + TT.IF: self._parse_if, + TT.INCREASE: self._parse_increase, + TT.DECREASE: self._parse_decrease, + TT.SET: self._parse_set, + TT.PRINT: self._parse_print, + } + if tt not in dispatch: + raise ParseError( + f"[Line {line_num}] Unexpected keyword: {tokens[0].value!r}" + ) + return dispatch[tt](block_indent) if tt == TT.IF else dispatch[tt]() + + def _parse_if(self, current_indent: int) -> IfNode: + indent, tokens, line_num = self._consume() + # Expected shape: IF NAME (GT|LT) NUMBER + if len(tokens) < 4: + raise ParseError(f"[Line {line_num}] Incomplete 'if' condition") + if tokens[1].type != TT.NAME: + raise ParseError(f"[Line {line_num}] Expected variable name after 'if'") + if tokens[2].type not in (TT.GT, TT.LT): + raise ParseError(f"[Line {line_num}] Expected '>' or '<' in condition") + if tokens[3].type != TT.NUMBER: + raise ParseError(f"[Line {line_num}] Expected a number in condition") + + cond = ConditionNode( + left = tokens[1].value, + op = tokens[2].value, + right = tokens[3].value, + line = line_num, + ) + then_body = self._parse_block(parent_indent=indent) + + # Optional else at the SAME indent level as the if + else_body: List = [] + nxt = self._peek() + if nxt and nxt[0] == indent and nxt[1][0].type == TT.ELSE: + self._consume() # eat 'else' + else_body = self._parse_block(parent_indent=indent) + + return IfNode(cond, then_body, else_body, line_num) + + def _parse_increase(self) -> IncreaseNode: + indent, tokens, line_num = self._consume() + if len(tokens) < 2 or tokens[1].type != TT.NAME: + raise ParseError(f"[Line {line_num}] Expected variable name after 'increase'") + return IncreaseNode(tokens[1].value, line_num) + + def _parse_decrease(self) -> DecreaseNode: + indent, tokens, line_num = self._consume() + if len(tokens) < 2 or tokens[1].type != TT.NAME: + raise ParseError(f"[Line {line_num}] Expected variable name after 'decrease'") + return DecreaseNode(tokens[1].value, line_num) + + def _parse_set(self) -> SetNode: + indent, tokens, line_num = self._consume() + # set NAME = NUMBER + if len(tokens) < 4: + raise ParseError(f"[Line {line_num}] Syntax: set = ") + if tokens[1].type != TT.NAME: + raise ParseError(f"[Line {line_num}] Expected variable name after 'set'") + if tokens[2].type != TT.EQ: + raise ParseError(f"[Line {line_num}] Expected '=' after variable name") + if tokens[3].type != TT.NUMBER: + raise ParseError(f"[Line {line_num}] Expected a number after '='") + return SetNode(tokens[1].value, tokens[3].value, line_num) + + def _parse_print(self) -> PrintNode: + indent, tokens, line_num = self._consume() + if len(tokens) < 2 or tokens[1].type != TT.NAME: + raise ParseError(f"[Line {line_num}] Expected variable name after 'print'") + return PrintNode(tokens[1].value, line_num) + + +# ══════════════════════════════════════════════════════════════ +# INTERPRETER (tree-walk evaluator) +# ══════════════════════════════════════════════════════════════ + +class SnippetRuntimeError(Exception): + pass + + +class Interpreter: + def __init__(self): + # All snippets share this variable store + self.variables: Dict[str, float] = {} + + def run(self, program: ProgramNode) -> None: + for snippet in program.snippets: + self._exec_snippet(snippet) + + # ── snippet ─────────────────────────────────────────────── + + def _exec_snippet(self, node: SnippetNode) -> None: + print(f"── snippet {node.name} ──") + self._exec_block(node.body) + + # ── block / dispatch ────────────────────────────────────── + + def _exec_block(self, stmts: List) -> None: + for stmt in stmts: + self._exec(stmt) + + def _exec(self, node) -> None: + dispatch = { + IfNode: self._exec_if, + IncreaseNode: self._exec_increase, + DecreaseNode: self._exec_decrease, + SetNode: self._exec_set, + PrintNode: self._exec_print, + } + handler = dispatch.get(type(node)) + if handler is None: + raise SnippetRuntimeError(f"Unknown AST node: {type(node).__name__}") + handler(node) + + # ── statements ──────────────────────────────────────────── + + def _exec_if(self, node: IfNode) -> None: + if self._eval_condition(node.condition): + self._exec_block(node.then_body) + else: + self._exec_block(node.else_body) + + def _eval_condition(self, cond: ConditionNode) -> bool: + val = self._get_var(cond.left, cond.line) + if cond.op == '>': + return val > cond.right + if cond.op == '<': + return val < cond.right + raise SnippetRuntimeError(f"Unknown operator: {cond.op!r}") + + def _exec_increase(self, node: IncreaseNode) -> None: + self.variables[node.name] = self._get_var(node.name, node.line) + 1 + + def _exec_decrease(self, node: DecreaseNode) -> None: + self.variables[node.name] = self._get_var(node.name, node.line) - 1 + + def _exec_set(self, node: SetNode) -> None: + self.variables[node.name] = node.value + + def _exec_print(self, node: PrintNode) -> None: + val = self._get_var(node.name, node.line) + # Show as int when the value is a whole number + display = int(val) if val == int(val) else val + print(f" {node.name} = {display}") + + # ── helpers ─────────────────────────────────────────────── + + def _get_var(self, name: str, line: int) -> float: + if name not in self.variables: + raise SnippetRuntimeError( + f"[Line {line}] Undefined variable: '{name}'" + ) + return self.variables[name] + + +# ══════════════════════════════════════════════════════════════ +# PUBLIC ENTRY POINT +# ══════════════════════════════════════════════════════════════ + +def run_source(source: str) -> None: + """Lex → parse → interpret a source string.""" + try: + lines = Lexer(source).tokenize() + ast = Parser(lines).parse() + Interpreter().run(ast) + except (LexerError, ParseError, SnippetRuntimeError) as err: + print(f"\n ✖ {err}", file=sys.stderr) + sys.exit(1) + + +# ══════════════════════════════════════════════════════════════ +# COMMAND LINE +# ══════════════════════════════════════════════════════════════ + +REPL_BANNER = """\ +╔════════════════════════════════╗ +║ Snippet Language REPL v1.0 ║ +║ blank line → run ║ +║ 'exit' → quit ║ +╚════════════════════════════════╝ +""" + +def _repl() -> None: + print(REPL_BANNER) + while True: + collected: List[str] = [] + prompt = '> ' + try: + while True: + line = input(prompt) + if line.strip().lower() == 'exit': + sys.exit(0) + if line == '' and collected: + break + collected.append(line) + prompt = ' ' + except EOFError: + break + if collected: + run_source('\n'.join(collected)) + print() + + +def main() -> None: + if len(sys.argv) == 1: + _repl() + elif len(sys.argv) == 2: + path = sys.argv[1] + try: + with open(path) as fh: + source = fh.read() + except FileNotFoundError: + print(f"File not found: {path}", file=sys.stderr) + sys.exit(1) + run_source(source) + else: + print(f"Usage: {sys.argv[0]} [file.snip]", file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main()