machine-dialect 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. machine_dialect/__main__.py +667 -0
  2. machine_dialect/agent/__init__.py +5 -0
  3. machine_dialect/agent/agent.py +360 -0
  4. machine_dialect/ast/__init__.py +95 -0
  5. machine_dialect/ast/ast_node.py +35 -0
  6. machine_dialect/ast/call_expression.py +82 -0
  7. machine_dialect/ast/dict_extraction.py +60 -0
  8. machine_dialect/ast/expressions.py +439 -0
  9. machine_dialect/ast/literals.py +309 -0
  10. machine_dialect/ast/program.py +35 -0
  11. machine_dialect/ast/statements.py +1433 -0
  12. machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
  13. machine_dialect/ast/tests/test_boolean_literal.py +29 -0
  14. machine_dialect/ast/tests/test_collection_hir.py +138 -0
  15. machine_dialect/ast/tests/test_define_statement.py +142 -0
  16. machine_dialect/ast/tests/test_desugar.py +541 -0
  17. machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
  18. machine_dialect/cfg/__init__.py +6 -0
  19. machine_dialect/cfg/config.py +156 -0
  20. machine_dialect/cfg/examples.py +221 -0
  21. machine_dialect/cfg/generate_with_ai.py +187 -0
  22. machine_dialect/cfg/openai_generation.py +200 -0
  23. machine_dialect/cfg/parser.py +94 -0
  24. machine_dialect/cfg/tests/__init__.py +1 -0
  25. machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
  26. machine_dialect/cfg/tests/test_config.py +188 -0
  27. machine_dialect/cfg/tests/test_examples.py +391 -0
  28. machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
  29. machine_dialect/cfg/tests/test_openai_generation.py +256 -0
  30. machine_dialect/codegen/__init__.py +5 -0
  31. machine_dialect/codegen/bytecode_module.py +89 -0
  32. machine_dialect/codegen/bytecode_serializer.py +300 -0
  33. machine_dialect/codegen/opcodes.py +101 -0
  34. machine_dialect/codegen/register_codegen.py +1996 -0
  35. machine_dialect/codegen/symtab.py +208 -0
  36. machine_dialect/codegen/tests/__init__.py +1 -0
  37. machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
  38. machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
  39. machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
  40. machine_dialect/codegen/tests/test_symtab.py +418 -0
  41. machine_dialect/codegen/vm_serializer.py +621 -0
  42. machine_dialect/compiler/__init__.py +18 -0
  43. machine_dialect/compiler/compiler.py +197 -0
  44. machine_dialect/compiler/config.py +149 -0
  45. machine_dialect/compiler/context.py +149 -0
  46. machine_dialect/compiler/phases/__init__.py +19 -0
  47. machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
  48. machine_dialect/compiler/phases/codegen.py +40 -0
  49. machine_dialect/compiler/phases/hir_generation.py +39 -0
  50. machine_dialect/compiler/phases/mir_generation.py +86 -0
  51. machine_dialect/compiler/phases/optimization.py +110 -0
  52. machine_dialect/compiler/phases/parsing.py +39 -0
  53. machine_dialect/compiler/pipeline.py +143 -0
  54. machine_dialect/compiler/tests/__init__.py +1 -0
  55. machine_dialect/compiler/tests/test_compiler.py +568 -0
  56. machine_dialect/compiler/vm_runner.py +173 -0
  57. machine_dialect/errors/__init__.py +32 -0
  58. machine_dialect/errors/exceptions.py +369 -0
  59. machine_dialect/errors/messages.py +82 -0
  60. machine_dialect/errors/tests/__init__.py +0 -0
  61. machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
  62. machine_dialect/errors/tests/test_name_errors.py +118 -0
  63. machine_dialect/helpers/__init__.py +0 -0
  64. machine_dialect/helpers/stopwords.py +225 -0
  65. machine_dialect/helpers/validators.py +30 -0
  66. machine_dialect/lexer/__init__.py +9 -0
  67. machine_dialect/lexer/constants.py +23 -0
  68. machine_dialect/lexer/lexer.py +907 -0
  69. machine_dialect/lexer/tests/__init__.py +0 -0
  70. machine_dialect/lexer/tests/helpers.py +86 -0
  71. machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
  72. machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
  73. machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
  74. machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
  75. machine_dialect/lexer/tests/test_comments.py +200 -0
  76. machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
  77. machine_dialect/lexer/tests/test_lexer_position.py +113 -0
  78. machine_dialect/lexer/tests/test_list_tokens.py +282 -0
  79. machine_dialect/lexer/tests/test_stopwords.py +80 -0
  80. machine_dialect/lexer/tests/test_strict_equality.py +129 -0
  81. machine_dialect/lexer/tests/test_token.py +41 -0
  82. machine_dialect/lexer/tests/test_tokenization.py +294 -0
  83. machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
  84. machine_dialect/lexer/tests/test_url_literals.py +169 -0
  85. machine_dialect/lexer/tokens.py +487 -0
  86. machine_dialect/linter/__init__.py +10 -0
  87. machine_dialect/linter/__main__.py +144 -0
  88. machine_dialect/linter/linter.py +154 -0
  89. machine_dialect/linter/rules/__init__.py +8 -0
  90. machine_dialect/linter/rules/base.py +112 -0
  91. machine_dialect/linter/rules/statement_termination.py +99 -0
  92. machine_dialect/linter/tests/__init__.py +1 -0
  93. machine_dialect/linter/tests/mdrules/__init__.py +0 -0
  94. machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
  95. machine_dialect/linter/tests/test_linter.py +81 -0
  96. machine_dialect/linter/tests/test_rules.py +110 -0
  97. machine_dialect/linter/tests/test_violations.py +71 -0
  98. machine_dialect/linter/violations.py +51 -0
  99. machine_dialect/mir/__init__.py +69 -0
  100. machine_dialect/mir/analyses/__init__.py +20 -0
  101. machine_dialect/mir/analyses/alias_analysis.py +315 -0
  102. machine_dialect/mir/analyses/dominance_analysis.py +49 -0
  103. machine_dialect/mir/analyses/escape_analysis.py +286 -0
  104. machine_dialect/mir/analyses/loop_analysis.py +272 -0
  105. machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
  106. machine_dialect/mir/analyses/type_analysis.py +448 -0
  107. machine_dialect/mir/analyses/use_def_chains.py +232 -0
  108. machine_dialect/mir/basic_block.py +385 -0
  109. machine_dialect/mir/dataflow.py +445 -0
  110. machine_dialect/mir/debug_info.py +208 -0
  111. machine_dialect/mir/hir_to_mir.py +1738 -0
  112. machine_dialect/mir/mir_dumper.py +366 -0
  113. machine_dialect/mir/mir_function.py +167 -0
  114. machine_dialect/mir/mir_instructions.py +1877 -0
  115. machine_dialect/mir/mir_interpreter.py +556 -0
  116. machine_dialect/mir/mir_module.py +225 -0
  117. machine_dialect/mir/mir_printer.py +480 -0
  118. machine_dialect/mir/mir_transformer.py +410 -0
  119. machine_dialect/mir/mir_types.py +367 -0
  120. machine_dialect/mir/mir_validation.py +455 -0
  121. machine_dialect/mir/mir_values.py +268 -0
  122. machine_dialect/mir/optimization_config.py +233 -0
  123. machine_dialect/mir/optimization_pass.py +251 -0
  124. machine_dialect/mir/optimization_pipeline.py +355 -0
  125. machine_dialect/mir/optimizations/__init__.py +84 -0
  126. machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
  127. machine_dialect/mir/optimizations/branch_prediction.py +372 -0
  128. machine_dialect/mir/optimizations/constant_propagation.py +634 -0
  129. machine_dialect/mir/optimizations/cse.py +398 -0
  130. machine_dialect/mir/optimizations/dce.py +288 -0
  131. machine_dialect/mir/optimizations/inlining.py +551 -0
  132. machine_dialect/mir/optimizations/jump_threading.py +487 -0
  133. machine_dialect/mir/optimizations/licm.py +405 -0
  134. machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
  135. machine_dialect/mir/optimizations/strength_reduction.py +422 -0
  136. machine_dialect/mir/optimizations/tail_call.py +207 -0
  137. machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
  138. machine_dialect/mir/optimizations/type_narrowing.py +397 -0
  139. machine_dialect/mir/optimizations/type_specialization.py +447 -0
  140. machine_dialect/mir/optimizations/type_specific.py +906 -0
  141. machine_dialect/mir/optimize_mir.py +89 -0
  142. machine_dialect/mir/pass_manager.py +391 -0
  143. machine_dialect/mir/profiling/__init__.py +26 -0
  144. machine_dialect/mir/profiling/profile_collector.py +318 -0
  145. machine_dialect/mir/profiling/profile_data.py +372 -0
  146. machine_dialect/mir/profiling/profile_reader.py +272 -0
  147. machine_dialect/mir/profiling/profile_writer.py +226 -0
  148. machine_dialect/mir/register_allocation.py +302 -0
  149. machine_dialect/mir/reporting/__init__.py +17 -0
  150. machine_dialect/mir/reporting/optimization_reporter.py +314 -0
  151. machine_dialect/mir/reporting/report_formatter.py +289 -0
  152. machine_dialect/mir/ssa_construction.py +342 -0
  153. machine_dialect/mir/tests/__init__.py +1 -0
  154. machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
  155. machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
  156. machine_dialect/mir/tests/test_algebraic_division.py +126 -0
  157. machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
  158. machine_dialect/mir/tests/test_basic_block.py +425 -0
  159. machine_dialect/mir/tests/test_branch_prediction.py +459 -0
  160. machine_dialect/mir/tests/test_call_lowering.py +168 -0
  161. machine_dialect/mir/tests/test_collection_lowering.py +604 -0
  162. machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
  163. machine_dialect/mir/tests/test_custom_passes.py +166 -0
  164. machine_dialect/mir/tests/test_debug_info.py +285 -0
  165. machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
  166. machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
  167. machine_dialect/mir/tests/test_double_negation.py +231 -0
  168. machine_dialect/mir/tests/test_escape_analysis.py +233 -0
  169. machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
  170. machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
  171. machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
  172. machine_dialect/mir/tests/test_inlining.py +435 -0
  173. machine_dialect/mir/tests/test_licm.py +472 -0
  174. machine_dialect/mir/tests/test_mir_dumper.py +313 -0
  175. machine_dialect/mir/tests/test_mir_instructions.py +445 -0
  176. machine_dialect/mir/tests/test_mir_module.py +860 -0
  177. machine_dialect/mir/tests/test_mir_printer.py +387 -0
  178. machine_dialect/mir/tests/test_mir_types.py +123 -0
  179. machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
  180. machine_dialect/mir/tests/test_mir_validation.py +378 -0
  181. machine_dialect/mir/tests/test_mir_values.py +168 -0
  182. machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
  183. machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
  184. machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
  185. machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
  186. machine_dialect/mir/tests/test_pass_manager.py +294 -0
  187. machine_dialect/mir/tests/test_pass_registration.py +64 -0
  188. machine_dialect/mir/tests/test_profiling.py +356 -0
  189. machine_dialect/mir/tests/test_register_allocation.py +307 -0
  190. machine_dialect/mir/tests/test_report_formatters.py +372 -0
  191. machine_dialect/mir/tests/test_ssa_construction.py +433 -0
  192. machine_dialect/mir/tests/test_tail_call.py +236 -0
  193. machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
  194. machine_dialect/mir/tests/test_type_narrowing.py +277 -0
  195. machine_dialect/mir/tests/test_type_specialization.py +421 -0
  196. machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
  197. machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
  198. machine_dialect/mir/type_inference.py +368 -0
  199. machine_dialect/parser/__init__.py +12 -0
  200. machine_dialect/parser/enums.py +45 -0
  201. machine_dialect/parser/parser.py +3655 -0
  202. machine_dialect/parser/protocols.py +11 -0
  203. machine_dialect/parser/symbol_table.py +169 -0
  204. machine_dialect/parser/tests/__init__.py +0 -0
  205. machine_dialect/parser/tests/helper_functions.py +193 -0
  206. machine_dialect/parser/tests/test_action_statements.py +334 -0
  207. machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
  208. machine_dialect/parser/tests/test_call_statements.py +154 -0
  209. machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
  210. machine_dialect/parser/tests/test_collection_mutations.py +264 -0
  211. machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
  212. machine_dialect/parser/tests/test_define_integration.py +468 -0
  213. machine_dialect/parser/tests/test_define_statements.py +311 -0
  214. machine_dialect/parser/tests/test_dict_extraction.py +115 -0
  215. machine_dialect/parser/tests/test_empty_literal.py +155 -0
  216. machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
  217. machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
  218. machine_dialect/parser/tests/test_if_empty_block.py +61 -0
  219. machine_dialect/parser/tests/test_if_statements.py +299 -0
  220. machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
  221. machine_dialect/parser/tests/test_infix_expressions.py +680 -0
  222. machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
  223. machine_dialect/parser/tests/test_interaction_statements.py +269 -0
  224. machine_dialect/parser/tests/test_list_literals.py +277 -0
  225. machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
  226. machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
  227. machine_dialect/parser/tests/test_parse_errors.py +114 -0
  228. machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
  229. machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
  230. machine_dialect/parser/tests/test_program.py +13 -0
  231. machine_dialect/parser/tests/test_return_statements.py +89 -0
  232. machine_dialect/parser/tests/test_set_statements.py +152 -0
  233. machine_dialect/parser/tests/test_strict_equality.py +258 -0
  234. machine_dialect/parser/tests/test_symbol_table.py +217 -0
  235. machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
  236. machine_dialect/parser/tests/test_utility_statements.py +423 -0
  237. machine_dialect/parser/token_buffer.py +159 -0
  238. machine_dialect/repl/__init__.py +3 -0
  239. machine_dialect/repl/repl.py +426 -0
  240. machine_dialect/repl/tests/__init__.py +0 -0
  241. machine_dialect/repl/tests/test_repl.py +606 -0
  242. machine_dialect/semantic/__init__.py +12 -0
  243. machine_dialect/semantic/analyzer.py +906 -0
  244. machine_dialect/semantic/error_messages.py +189 -0
  245. machine_dialect/semantic/tests/__init__.py +1 -0
  246. machine_dialect/semantic/tests/test_analyzer.py +364 -0
  247. machine_dialect/semantic/tests/test_error_messages.py +104 -0
  248. machine_dialect/tests/edge_cases/__init__.py +10 -0
  249. machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
  250. machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
  251. machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
  252. machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
  253. machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
  254. machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
  255. machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
  256. machine_dialect/tests/integration/test_list_compilation.py +395 -0
  257. machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
  258. machine_dialect/type_checking/__init__.py +21 -0
  259. machine_dialect/type_checking/tests/__init__.py +1 -0
  260. machine_dialect/type_checking/tests/test_type_system.py +230 -0
  261. machine_dialect/type_checking/type_system.py +270 -0
  262. machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
  263. machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
  264. machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
  265. machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
  266. machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
  267. machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
  268. machine_dialect_vm/__init__.pyi +15 -0
@@ -0,0 +1,11 @@
1
+ from collections.abc import Callable
2
+
3
+ from machine_dialect.ast import Expression
4
+ from machine_dialect.lexer import TokenType
5
+
6
+ PrefixParseFunc = Callable[[], Expression]
7
+ InfixParseFunc = Callable[[Expression], Expression]
8
+ PostfixParseFunc = Callable[[Expression], Expression]
9
+ PrefixParseFuncs = dict[TokenType, PrefixParseFunc]
10
+ InfixParseFuncs = dict[TokenType, InfixParseFunc]
11
+ PostfixParseFuncs = dict[TokenType, PostfixParseFunc]
@@ -0,0 +1,169 @@
1
+ """Symbol table for tracking variable definitions in Machine Dialect™.
2
+
3
+ This module provides the symbol table implementation for tracking variable
4
+ definitions and their types. The symbol table supports nested scopes and
5
+ type checking for variable assignments.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from machine_dialect.ast import ASTNode
13
+
14
+
15
+ @dataclass
16
+ class VariableInfo:
17
+ """Information about a defined variable.
18
+
19
+ Attributes:
20
+ type_spec: List of allowed type names (for union types)
21
+ defined: Whether the variable has been defined
22
+ initialized: Whether the variable has been assigned a value
23
+ definition_line: Line number where variable was defined
24
+ definition_pos: Column position where variable was defined
25
+ return_type: For functions, the return type (optional)
26
+ last_assigned_value: The last value assigned to this variable (for type tracking)
27
+ inferred_element_types: For collections, the inferred element types
28
+ """
29
+
30
+ type_spec: list[str]
31
+ defined: bool = True
32
+ initialized: bool = False
33
+ definition_line: int = 0
34
+ definition_pos: int = 0
35
+ return_type: str | None = None
36
+ last_assigned_value: "ASTNode | None" = None # Stores the AST node of the last assigned value
37
+ inferred_element_types: list[str] | None = None # For tracking collection element types
38
+
39
+ def allows_type(self, type_name: str) -> bool:
40
+ """Check if this variable allows the given type.
41
+
42
+ Args:
43
+ type_name: The type to check
44
+
45
+ Returns:
46
+ True if type is allowed, False otherwise
47
+ """
48
+ return type_name in self.type_spec
49
+
50
+ def __str__(self) -> str:
51
+ """Return string representation."""
52
+ type_str = " or ".join(self.type_spec)
53
+ status = "initialized" if self.initialized else "uninitialized"
54
+ return f"VariableInfo(types={type_str}, {status})"
55
+
56
+
57
+ class SymbolTable:
58
+ """Symbol table for tracking variable definitions.
59
+
60
+ Maintains a mapping of variable names to their type information
61
+ and tracks scoping through parent/child relationships.
62
+
63
+ Attributes:
64
+ symbols: Dictionary mapping variable names to their info
65
+ parent: Parent symbol table for outer scope (if any)
66
+ """
67
+
68
+ def __init__(self, parent: "SymbolTable | None" = None) -> None:
69
+ """Initialize a symbol table.
70
+
71
+ Args:
72
+ parent: Optional parent symbol table for outer scope
73
+ """
74
+ self.symbols: dict[str, VariableInfo] = {}
75
+ self.parent = parent
76
+
77
+ def define(self, name: str, type_spec: list[str], line: int = 0, position: int = 0) -> None:
78
+ """Define a new variable.
79
+
80
+ Args:
81
+ name: Variable name
82
+ type_spec: List of allowed types
83
+ line: Line number of definition
84
+ position: Column position of definition
85
+
86
+ Raises:
87
+ NameError: If variable is already defined in this scope
88
+ """
89
+ if name in self.symbols:
90
+ existing = self.symbols[name]
91
+ raise NameError(f"Variable '{name}' is already defined at line {existing.definition_line}")
92
+
93
+ self.symbols[name] = VariableInfo(
94
+ type_spec=type_spec, defined=True, initialized=False, definition_line=line, definition_pos=position
95
+ )
96
+
97
+ def lookup(self, name: str) -> VariableInfo | None:
98
+ """Look up a variable definition.
99
+
100
+ Searches this scope and parent scopes for the variable.
101
+
102
+ Args:
103
+ name: Variable name to look up
104
+
105
+ Returns:
106
+ VariableInfo if found, None otherwise
107
+ """
108
+ if name in self.symbols:
109
+ return self.symbols[name]
110
+
111
+ if self.parent:
112
+ return self.parent.lookup(name)
113
+
114
+ return None
115
+
116
+ def mark_initialized(self, name: str) -> None:
117
+ """Mark a variable as initialized.
118
+
119
+ Args:
120
+ name: Variable name to mark as initialized
121
+
122
+ Raises:
123
+ NameError: If variable is not defined
124
+ """
125
+ info = self.lookup(name)
126
+ if not info:
127
+ raise NameError(f"Variable '{name}' is not defined")
128
+
129
+ # Mark in the scope where it's defined
130
+ if name in self.symbols:
131
+ self.symbols[name].initialized = True
132
+ elif self.parent:
133
+ self.parent.mark_initialized(name)
134
+
135
+ def enter_scope(self) -> "SymbolTable":
136
+ """Create a new child scope.
137
+
138
+ Returns:
139
+ A new SymbolTable with this table as parent
140
+ """
141
+ return SymbolTable(parent=self)
142
+
143
+ def exit_scope(self) -> "SymbolTable | None":
144
+ """Return to parent scope.
145
+
146
+ Returns:
147
+ The parent symbol table, or None if at global scope
148
+ """
149
+ return self.parent
150
+
151
+ def is_defined_in_current_scope(self, name: str) -> bool:
152
+ """Check if variable is defined in current scope only.
153
+
154
+ Args:
155
+ name: Variable name to check
156
+
157
+ Returns:
158
+ True if defined in this scope, False otherwise
159
+ """
160
+ return name in self.symbols
161
+
162
+ def __str__(self) -> str:
163
+ """Return string representation of symbol table."""
164
+ lines = ["Symbol Table:"]
165
+ for name, info in self.symbols.items():
166
+ lines.append(f" {name}: {info}")
167
+ if self.parent:
168
+ lines.append(" (has parent scope)")
169
+ return "\n".join(lines)
File without changes
@@ -0,0 +1,193 @@
1
+ """Helper functions for parser tests.
2
+
3
+ This module provides utility functions used across parser tests to verify
4
+ program structure, statements, and expressions. These helpers reduce code
5
+ duplication and make tests more readable.
6
+ """
7
+
8
+ from typing import Any, cast
9
+
10
+ from machine_dialect.ast import (
11
+ Expression,
12
+ ExpressionStatement,
13
+ FloatLiteral,
14
+ Identifier,
15
+ InfixExpression,
16
+ Program,
17
+ WholeNumberLiteral,
18
+ YesNoLiteral,
19
+ )
20
+ from machine_dialect.parser import Parser
21
+
22
+
23
+ def assert_program_statements(parser: Parser, program: Program, expected_statement_count: int = 1) -> None:
24
+ """Verify that a program has the expected number of statements and no errors.
25
+
26
+ Args:
27
+ parser: The parser instance to check for errors.
28
+ program: The parsed program to verify.
29
+ expected_statement_count: Expected number of statements in the program.
30
+
31
+ Raises:
32
+ AssertionError: If parser has errors, statement count is wrong, or
33
+ first statement is not an ExpressionStatement.
34
+ """
35
+ assert len(parser.errors) == 0, f"Program statement errors found: {parser.errors}"
36
+ assert len(program.statements) == expected_statement_count
37
+ assert isinstance(program.statements[0], ExpressionStatement)
38
+
39
+
40
+ def assert_literal_expression(
41
+ expression: Expression,
42
+ expected_value: Any,
43
+ ) -> None:
44
+ """Test that a literal expression has the expected value.
45
+
46
+ This function dispatches to the appropriate test function based on the
47
+ type of the expected value. Currently only handles string identifiers.
48
+
49
+ Args:
50
+ expression: The expression to test.
51
+ expected_value: The expected value of the literal.
52
+
53
+ Raises:
54
+ AssertionError: If the expression doesn't match the expected value
55
+ or if the value type is not handled.
56
+ """
57
+ value_type: type = type(expected_value)
58
+
59
+ if value_type is str:
60
+ _assert_identifier(expression, expected_value)
61
+ elif value_type is int:
62
+ _assert_integer_literal(expression, expected_value)
63
+ elif value_type is float:
64
+ _assert_float_literal(expression, expected_value)
65
+ elif value_type is bool:
66
+ _assert_boolean_literal(expression, expected_value)
67
+ else:
68
+ raise AssertionError(f"Unhandled literal expression: {expression}. Got={value_type}")
69
+
70
+
71
+ def _assert_identifier(expression: Expression, expected_value: str) -> None:
72
+ """Test that an identifier expression has the expected value.
73
+
74
+ Verifies both the identifier's value attribute and its token's literal
75
+ match the expected value.
76
+
77
+ Args:
78
+ expression: The expression to test (must be an Identifier).
79
+ expected_value: The expected string value of the identifier.
80
+
81
+ Raises:
82
+ AssertionError: If the identifier's value or token literal don't
83
+ match the expected value.
84
+ """
85
+ identifier: Identifier = cast(Identifier, expression)
86
+ assert identifier.value == expected_value, f"Identifier value={identifier.value} != {expected_value}"
87
+ assert identifier.token.literal == expected_value, f"Identifier token={identifier.token} != {expected_value}"
88
+
89
+
90
+ def _assert_integer_literal(expression: Expression, expected_value: int) -> None:
91
+ """Test that an integer literal expression has the expected value.
92
+
93
+ Verifies both the integer literal's value attribute and its token's literal
94
+ match the expected value.
95
+
96
+ Args:
97
+ expression: The expression to test (must be a WholeNumberLiteral).
98
+ expected_value: The expected integer value.
99
+
100
+ Raises:
101
+ AssertionError: If the expression is not a WholeNumberLiteral or if
102
+ the value doesn't match the expected value.
103
+ """
104
+ assert isinstance(expression, WholeNumberLiteral), f"Expected WholeNumberLiteral, got {type(expression).__name__}"
105
+ integer_literal = expression
106
+ assert integer_literal.value == expected_value, f"Integer value={integer_literal.value} != {expected_value}"
107
+ assert integer_literal.token.literal == str(expected_value), (
108
+ f"Integer token literal={integer_literal.token.literal} != {expected_value}"
109
+ )
110
+
111
+
112
+ def _assert_float_literal(expression: Expression, expected_value: float) -> None:
113
+ """Test that a float literal expression has the expected value.
114
+
115
+ Verifies both the float literal's value attribute and its token's literal
116
+ match the expected value.
117
+
118
+ Args:
119
+ expression: The expression to test (must be a FloatLiteral).
120
+ expected_value: The expected float value.
121
+
122
+ Raises:
123
+ AssertionError: If the expression is not a FloatLiteral or if
124
+ the value doesn't match the expected value.
125
+ """
126
+ assert isinstance(expression, FloatLiteral), f"Expected FloatLiteral, got {type(expression).__name__}"
127
+ float_literal = expression
128
+ assert float_literal.value == expected_value, f"Float value={float_literal.value} != {expected_value}"
129
+ # For float literals, we compare the string representation to avoid precision issues
130
+ expected_str = str(expected_value)
131
+ actual_str = float_literal.token.literal
132
+ # Handle cases like 3.0 vs 3.0 or 3.14 vs 3.14
133
+ assert float(actual_str) == float(expected_str), f"Float token literal={actual_str} != {expected_str}"
134
+
135
+
136
+ def _assert_boolean_literal(expression: Expression, expected_value: bool) -> None:
137
+ """Test that a boolean literal expression has the expected value.
138
+
139
+ Verifies both the boolean literal's value attribute and its token's literal
140
+ match the expected value. The token literal should be in canonical form
141
+ ("Yes" or "No") regardless of the original case in the source.
142
+
143
+ Args:
144
+ expression: The expression to test (must be a YesNoLiteral).
145
+ expected_value: The expected boolean value.
146
+
147
+ Raises:
148
+ AssertionError: If the expression is not a YesNoLiteral or if
149
+ the value doesn't match the expected value.
150
+ """
151
+ assert isinstance(expression, YesNoLiteral), f"Expected YesNoLiteral, got {type(expression).__name__}"
152
+ boolean_literal = expression
153
+ assert boolean_literal.value == expected_value, f"Boolean value={boolean_literal.value} != {expected_value}"
154
+ # Check that the token literal is in canonical form (Yes/No)
155
+ expected_literal = "Yes" if expected_value else "No"
156
+ actual_literal = boolean_literal.token.literal
157
+ assert actual_literal == expected_literal, f"Boolean token literal={actual_literal} != {expected_literal}"
158
+
159
+
160
+ def assert_infix_expression(
161
+ expression: Expression,
162
+ left_value: Any,
163
+ operator: str,
164
+ right_value: Any,
165
+ ) -> None:
166
+ """Assert that an infix expression has the expected values and operator.
167
+
168
+ This function verifies that an expression is an InfixExpression with the
169
+ correct operator and operand values. It uses assert_literal_expression to
170
+ check the operands.
171
+
172
+ Args:
173
+ expression: The expression to verify (must be an InfixExpression).
174
+ left_value: Expected value of the left operand.
175
+ operator: Expected operator string.
176
+ right_value: Expected value of the right operand.
177
+
178
+ Raises:
179
+ AssertionError: If the expression is not an InfixExpression or if any
180
+ part of the expression doesn't match expectations.
181
+ """
182
+ assert isinstance(expression, InfixExpression), f"Expected InfixExpression, got {type(expression).__name__}"
183
+
184
+ # Check the operator
185
+ assert expression.operator == operator, f"Expected operator '{operator}', got '{expression.operator}'"
186
+
187
+ # Check left operand
188
+ assert expression.left is not None, "Left operand is None"
189
+ assert_literal_expression(expression.left, left_value)
190
+
191
+ # Check right operand
192
+ assert expression.right is not None, "Right operand is None"
193
+ assert_literal_expression(expression.right, right_value)