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.
- machine_dialect/__main__.py +667 -0
- machine_dialect/agent/__init__.py +5 -0
- machine_dialect/agent/agent.py +360 -0
- machine_dialect/ast/__init__.py +95 -0
- machine_dialect/ast/ast_node.py +35 -0
- machine_dialect/ast/call_expression.py +82 -0
- machine_dialect/ast/dict_extraction.py +60 -0
- machine_dialect/ast/expressions.py +439 -0
- machine_dialect/ast/literals.py +309 -0
- machine_dialect/ast/program.py +35 -0
- machine_dialect/ast/statements.py +1433 -0
- machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
- machine_dialect/ast/tests/test_boolean_literal.py +29 -0
- machine_dialect/ast/tests/test_collection_hir.py +138 -0
- machine_dialect/ast/tests/test_define_statement.py +142 -0
- machine_dialect/ast/tests/test_desugar.py +541 -0
- machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
- machine_dialect/cfg/__init__.py +6 -0
- machine_dialect/cfg/config.py +156 -0
- machine_dialect/cfg/examples.py +221 -0
- machine_dialect/cfg/generate_with_ai.py +187 -0
- machine_dialect/cfg/openai_generation.py +200 -0
- machine_dialect/cfg/parser.py +94 -0
- machine_dialect/cfg/tests/__init__.py +1 -0
- machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
- machine_dialect/cfg/tests/test_config.py +188 -0
- machine_dialect/cfg/tests/test_examples.py +391 -0
- machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
- machine_dialect/cfg/tests/test_openai_generation.py +256 -0
- machine_dialect/codegen/__init__.py +5 -0
- machine_dialect/codegen/bytecode_module.py +89 -0
- machine_dialect/codegen/bytecode_serializer.py +300 -0
- machine_dialect/codegen/opcodes.py +101 -0
- machine_dialect/codegen/register_codegen.py +1996 -0
- machine_dialect/codegen/symtab.py +208 -0
- machine_dialect/codegen/tests/__init__.py +1 -0
- machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
- machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
- machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
- machine_dialect/codegen/tests/test_symtab.py +418 -0
- machine_dialect/codegen/vm_serializer.py +621 -0
- machine_dialect/compiler/__init__.py +18 -0
- machine_dialect/compiler/compiler.py +197 -0
- machine_dialect/compiler/config.py +149 -0
- machine_dialect/compiler/context.py +149 -0
- machine_dialect/compiler/phases/__init__.py +19 -0
- machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
- machine_dialect/compiler/phases/codegen.py +40 -0
- machine_dialect/compiler/phases/hir_generation.py +39 -0
- machine_dialect/compiler/phases/mir_generation.py +86 -0
- machine_dialect/compiler/phases/optimization.py +110 -0
- machine_dialect/compiler/phases/parsing.py +39 -0
- machine_dialect/compiler/pipeline.py +143 -0
- machine_dialect/compiler/tests/__init__.py +1 -0
- machine_dialect/compiler/tests/test_compiler.py +568 -0
- machine_dialect/compiler/vm_runner.py +173 -0
- machine_dialect/errors/__init__.py +32 -0
- machine_dialect/errors/exceptions.py +369 -0
- machine_dialect/errors/messages.py +82 -0
- machine_dialect/errors/tests/__init__.py +0 -0
- machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
- machine_dialect/errors/tests/test_name_errors.py +118 -0
- machine_dialect/helpers/__init__.py +0 -0
- machine_dialect/helpers/stopwords.py +225 -0
- machine_dialect/helpers/validators.py +30 -0
- machine_dialect/lexer/__init__.py +9 -0
- machine_dialect/lexer/constants.py +23 -0
- machine_dialect/lexer/lexer.py +907 -0
- machine_dialect/lexer/tests/__init__.py +0 -0
- machine_dialect/lexer/tests/helpers.py +86 -0
- machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
- machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
- machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
- machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
- machine_dialect/lexer/tests/test_comments.py +200 -0
- machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
- machine_dialect/lexer/tests/test_lexer_position.py +113 -0
- machine_dialect/lexer/tests/test_list_tokens.py +282 -0
- machine_dialect/lexer/tests/test_stopwords.py +80 -0
- machine_dialect/lexer/tests/test_strict_equality.py +129 -0
- machine_dialect/lexer/tests/test_token.py +41 -0
- machine_dialect/lexer/tests/test_tokenization.py +294 -0
- machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
- machine_dialect/lexer/tests/test_url_literals.py +169 -0
- machine_dialect/lexer/tokens.py +487 -0
- machine_dialect/linter/__init__.py +10 -0
- machine_dialect/linter/__main__.py +144 -0
- machine_dialect/linter/linter.py +154 -0
- machine_dialect/linter/rules/__init__.py +8 -0
- machine_dialect/linter/rules/base.py +112 -0
- machine_dialect/linter/rules/statement_termination.py +99 -0
- machine_dialect/linter/tests/__init__.py +1 -0
- machine_dialect/linter/tests/mdrules/__init__.py +0 -0
- machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
- machine_dialect/linter/tests/test_linter.py +81 -0
- machine_dialect/linter/tests/test_rules.py +110 -0
- machine_dialect/linter/tests/test_violations.py +71 -0
- machine_dialect/linter/violations.py +51 -0
- machine_dialect/mir/__init__.py +69 -0
- machine_dialect/mir/analyses/__init__.py +20 -0
- machine_dialect/mir/analyses/alias_analysis.py +315 -0
- machine_dialect/mir/analyses/dominance_analysis.py +49 -0
- machine_dialect/mir/analyses/escape_analysis.py +286 -0
- machine_dialect/mir/analyses/loop_analysis.py +272 -0
- machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
- machine_dialect/mir/analyses/type_analysis.py +448 -0
- machine_dialect/mir/analyses/use_def_chains.py +232 -0
- machine_dialect/mir/basic_block.py +385 -0
- machine_dialect/mir/dataflow.py +445 -0
- machine_dialect/mir/debug_info.py +208 -0
- machine_dialect/mir/hir_to_mir.py +1738 -0
- machine_dialect/mir/mir_dumper.py +366 -0
- machine_dialect/mir/mir_function.py +167 -0
- machine_dialect/mir/mir_instructions.py +1877 -0
- machine_dialect/mir/mir_interpreter.py +556 -0
- machine_dialect/mir/mir_module.py +225 -0
- machine_dialect/mir/mir_printer.py +480 -0
- machine_dialect/mir/mir_transformer.py +410 -0
- machine_dialect/mir/mir_types.py +367 -0
- machine_dialect/mir/mir_validation.py +455 -0
- machine_dialect/mir/mir_values.py +268 -0
- machine_dialect/mir/optimization_config.py +233 -0
- machine_dialect/mir/optimization_pass.py +251 -0
- machine_dialect/mir/optimization_pipeline.py +355 -0
- machine_dialect/mir/optimizations/__init__.py +84 -0
- machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
- machine_dialect/mir/optimizations/branch_prediction.py +372 -0
- machine_dialect/mir/optimizations/constant_propagation.py +634 -0
- machine_dialect/mir/optimizations/cse.py +398 -0
- machine_dialect/mir/optimizations/dce.py +288 -0
- machine_dialect/mir/optimizations/inlining.py +551 -0
- machine_dialect/mir/optimizations/jump_threading.py +487 -0
- machine_dialect/mir/optimizations/licm.py +405 -0
- machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
- machine_dialect/mir/optimizations/strength_reduction.py +422 -0
- machine_dialect/mir/optimizations/tail_call.py +207 -0
- machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
- machine_dialect/mir/optimizations/type_narrowing.py +397 -0
- machine_dialect/mir/optimizations/type_specialization.py +447 -0
- machine_dialect/mir/optimizations/type_specific.py +906 -0
- machine_dialect/mir/optimize_mir.py +89 -0
- machine_dialect/mir/pass_manager.py +391 -0
- machine_dialect/mir/profiling/__init__.py +26 -0
- machine_dialect/mir/profiling/profile_collector.py +318 -0
- machine_dialect/mir/profiling/profile_data.py +372 -0
- machine_dialect/mir/profiling/profile_reader.py +272 -0
- machine_dialect/mir/profiling/profile_writer.py +226 -0
- machine_dialect/mir/register_allocation.py +302 -0
- machine_dialect/mir/reporting/__init__.py +17 -0
- machine_dialect/mir/reporting/optimization_reporter.py +314 -0
- machine_dialect/mir/reporting/report_formatter.py +289 -0
- machine_dialect/mir/ssa_construction.py +342 -0
- machine_dialect/mir/tests/__init__.py +1 -0
- machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
- machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
- machine_dialect/mir/tests/test_algebraic_division.py +126 -0
- machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
- machine_dialect/mir/tests/test_basic_block.py +425 -0
- machine_dialect/mir/tests/test_branch_prediction.py +459 -0
- machine_dialect/mir/tests/test_call_lowering.py +168 -0
- machine_dialect/mir/tests/test_collection_lowering.py +604 -0
- machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
- machine_dialect/mir/tests/test_custom_passes.py +166 -0
- machine_dialect/mir/tests/test_debug_info.py +285 -0
- machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
- machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
- machine_dialect/mir/tests/test_double_negation.py +231 -0
- machine_dialect/mir/tests/test_escape_analysis.py +233 -0
- machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
- machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
- machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
- machine_dialect/mir/tests/test_inlining.py +435 -0
- machine_dialect/mir/tests/test_licm.py +472 -0
- machine_dialect/mir/tests/test_mir_dumper.py +313 -0
- machine_dialect/mir/tests/test_mir_instructions.py +445 -0
- machine_dialect/mir/tests/test_mir_module.py +860 -0
- machine_dialect/mir/tests/test_mir_printer.py +387 -0
- machine_dialect/mir/tests/test_mir_types.py +123 -0
- machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
- machine_dialect/mir/tests/test_mir_validation.py +378 -0
- machine_dialect/mir/tests/test_mir_values.py +168 -0
- machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
- machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
- machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
- machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
- machine_dialect/mir/tests/test_pass_manager.py +294 -0
- machine_dialect/mir/tests/test_pass_registration.py +64 -0
- machine_dialect/mir/tests/test_profiling.py +356 -0
- machine_dialect/mir/tests/test_register_allocation.py +307 -0
- machine_dialect/mir/tests/test_report_formatters.py +372 -0
- machine_dialect/mir/tests/test_ssa_construction.py +433 -0
- machine_dialect/mir/tests/test_tail_call.py +236 -0
- machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
- machine_dialect/mir/tests/test_type_narrowing.py +277 -0
- machine_dialect/mir/tests/test_type_specialization.py +421 -0
- machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
- machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
- machine_dialect/mir/type_inference.py +368 -0
- machine_dialect/parser/__init__.py +12 -0
- machine_dialect/parser/enums.py +45 -0
- machine_dialect/parser/parser.py +3655 -0
- machine_dialect/parser/protocols.py +11 -0
- machine_dialect/parser/symbol_table.py +169 -0
- machine_dialect/parser/tests/__init__.py +0 -0
- machine_dialect/parser/tests/helper_functions.py +193 -0
- machine_dialect/parser/tests/test_action_statements.py +334 -0
- machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
- machine_dialect/parser/tests/test_call_statements.py +154 -0
- machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
- machine_dialect/parser/tests/test_collection_mutations.py +264 -0
- machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
- machine_dialect/parser/tests/test_define_integration.py +468 -0
- machine_dialect/parser/tests/test_define_statements.py +311 -0
- machine_dialect/parser/tests/test_dict_extraction.py +115 -0
- machine_dialect/parser/tests/test_empty_literal.py +155 -0
- machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
- machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
- machine_dialect/parser/tests/test_if_empty_block.py +61 -0
- machine_dialect/parser/tests/test_if_statements.py +299 -0
- machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
- machine_dialect/parser/tests/test_infix_expressions.py +680 -0
- machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
- machine_dialect/parser/tests/test_interaction_statements.py +269 -0
- machine_dialect/parser/tests/test_list_literals.py +277 -0
- machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
- machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
- machine_dialect/parser/tests/test_parse_errors.py +114 -0
- machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
- machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
- machine_dialect/parser/tests/test_program.py +13 -0
- machine_dialect/parser/tests/test_return_statements.py +89 -0
- machine_dialect/parser/tests/test_set_statements.py +152 -0
- machine_dialect/parser/tests/test_strict_equality.py +258 -0
- machine_dialect/parser/tests/test_symbol_table.py +217 -0
- machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
- machine_dialect/parser/tests/test_utility_statements.py +423 -0
- machine_dialect/parser/token_buffer.py +159 -0
- machine_dialect/repl/__init__.py +3 -0
- machine_dialect/repl/repl.py +426 -0
- machine_dialect/repl/tests/__init__.py +0 -0
- machine_dialect/repl/tests/test_repl.py +606 -0
- machine_dialect/semantic/__init__.py +12 -0
- machine_dialect/semantic/analyzer.py +906 -0
- machine_dialect/semantic/error_messages.py +189 -0
- machine_dialect/semantic/tests/__init__.py +1 -0
- machine_dialect/semantic/tests/test_analyzer.py +364 -0
- machine_dialect/semantic/tests/test_error_messages.py +104 -0
- machine_dialect/tests/edge_cases/__init__.py +10 -0
- machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
- machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
- machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
- machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
- machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
- machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
- machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
- machine_dialect/tests/integration/test_list_compilation.py +395 -0
- machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
- machine_dialect/type_checking/__init__.py +21 -0
- machine_dialect/type_checking/tests/__init__.py +1 -0
- machine_dialect/type_checking/tests/test_type_system.py +230 -0
- machine_dialect/type_checking/type_system.py +270 -0
- machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
- machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
- machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
- machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
- machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
- machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
- 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)
|