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,439 @@
|
|
1
|
+
"""AST nodes for expression types in Machine Dialect™.
|
2
|
+
|
3
|
+
This module defines the expression nodes used in the Abstract Syntax Tree (AST)
|
4
|
+
for Machine Dialect™. Expressions are constructs that can be evaluated to produce
|
5
|
+
a value, unlike statements which perform actions.
|
6
|
+
|
7
|
+
Expressions include:
|
8
|
+
- Identifier: Variable names and references
|
9
|
+
- Literals: Numbers, strings, booleans (to be added)
|
10
|
+
- Operations: Mathematical, logical, and other operations (to be added)
|
11
|
+
"""
|
12
|
+
|
13
|
+
from typing import Any
|
14
|
+
|
15
|
+
from machine_dialect.ast import ASTNode
|
16
|
+
from machine_dialect.lexer import Token
|
17
|
+
|
18
|
+
|
19
|
+
class Expression(ASTNode):
|
20
|
+
"""Base class for all expression nodes in the AST.
|
21
|
+
|
22
|
+
An expression represents a construct that can be evaluated to produce
|
23
|
+
a value. This includes identifiers, literals, operations, and function calls.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(self, token: Token) -> None:
|
27
|
+
"""Initialize an Expression node.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
token: The token that begins this expression.
|
31
|
+
"""
|
32
|
+
self.token = token
|
33
|
+
|
34
|
+
def desugar(self) -> "Expression":
|
35
|
+
"""Default desugar for expressions returns self.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
The expression unchanged.
|
39
|
+
"""
|
40
|
+
return self
|
41
|
+
|
42
|
+
|
43
|
+
class Identifier(Expression):
|
44
|
+
"""An identifier expression representing a variable or name.
|
45
|
+
|
46
|
+
Identifiers are names that refer to variables, functions, or other
|
47
|
+
named entities in the program. In Machine Dialect™, identifiers can
|
48
|
+
be written with or without backticks (e.g., `x` or x).
|
49
|
+
|
50
|
+
Attributes:
|
51
|
+
value: The string value of the identifier name.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(self, token: Token, value: str) -> None:
|
55
|
+
"""Initialize an Identifier node.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
token: The token containing the identifier.
|
59
|
+
value: The string value of the identifier name.
|
60
|
+
"""
|
61
|
+
super().__init__(token)
|
62
|
+
self.value = value
|
63
|
+
|
64
|
+
def __str__(self) -> str:
|
65
|
+
"""Return the string representation of the identifier.
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
The identifier wrapped in backticks, e.g., "`variable`".
|
69
|
+
"""
|
70
|
+
return f"`{self.value}`"
|
71
|
+
|
72
|
+
def desugar(self) -> "Identifier":
|
73
|
+
"""Identifiers are already in simplest form.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
Self unchanged.
|
77
|
+
"""
|
78
|
+
return self
|
79
|
+
|
80
|
+
|
81
|
+
class PrefixExpression(Expression):
|
82
|
+
"""A prefix expression with an operator applied to an expression.
|
83
|
+
|
84
|
+
Prefix expressions consist of a prefix operator followed by an expression.
|
85
|
+
Examples include negative numbers (-42), boolean negation (not True),
|
86
|
+
and other unary operations.
|
87
|
+
|
88
|
+
Attributes:
|
89
|
+
operator: The prefix operator as a string (e.g., "-", "not").
|
90
|
+
right: The expression that the operator is applied to.
|
91
|
+
"""
|
92
|
+
|
93
|
+
def __init__(self, token: Token, operator: str) -> None:
|
94
|
+
"""Initialize a PrefixExpression node.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
token: The token containing the prefix operator.
|
98
|
+
operator: The operator string (e.g., "-", "not").
|
99
|
+
"""
|
100
|
+
super().__init__(token)
|
101
|
+
self.operator = operator
|
102
|
+
self.right: Expression | None = None
|
103
|
+
|
104
|
+
def __str__(self) -> str:
|
105
|
+
"""Return the string representation of the prefix expression.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
The expression in the format "(operator right)", e.g., "(-42)".
|
109
|
+
"""
|
110
|
+
if self.operator == "not":
|
111
|
+
return f"({self.operator} {self.right})"
|
112
|
+
return f"({self.operator}{self.right})"
|
113
|
+
|
114
|
+
def desugar(self) -> "PrefixExpression":
|
115
|
+
"""Desugar prefix expression by recursively desugaring operand.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
A new PrefixExpression with desugared right operand.
|
119
|
+
"""
|
120
|
+
if self.right is None:
|
121
|
+
return self
|
122
|
+
|
123
|
+
desugared = PrefixExpression(self.token, self.operator)
|
124
|
+
desugared.right = self.right.desugar()
|
125
|
+
return desugared
|
126
|
+
|
127
|
+
|
128
|
+
class InfixExpression(Expression):
|
129
|
+
"""An infix expression with an operator between two expressions.
|
130
|
+
|
131
|
+
Infix expressions consist of a left expression, an infix operator, and a
|
132
|
+
right expression. Examples include arithmetic (5 + 3), comparisons (x > y),
|
133
|
+
and logical operations (a and b).
|
134
|
+
|
135
|
+
Attributes:
|
136
|
+
left: The left operand expression.
|
137
|
+
operator: The infix operator as a string (e.g., "+", "==", "and").
|
138
|
+
right: The right operand expression.
|
139
|
+
"""
|
140
|
+
|
141
|
+
# Map token types to canonical operator strings
|
142
|
+
# Used by both desugar and canonicalize to normalize operators
|
143
|
+
_OPERATOR_MAP = None
|
144
|
+
|
145
|
+
@classmethod
|
146
|
+
def _get_operator_map(cls) -> dict[Any, str]:
|
147
|
+
"""Get the operator mapping, creating it lazily if needed."""
|
148
|
+
if cls._OPERATOR_MAP is None:
|
149
|
+
from machine_dialect.lexer import TokenType
|
150
|
+
|
151
|
+
cls._OPERATOR_MAP = {
|
152
|
+
TokenType.OP_PLUS: "+",
|
153
|
+
TokenType.OP_MINUS: "-",
|
154
|
+
TokenType.OP_STAR: "*",
|
155
|
+
TokenType.OP_DIVISION: "/",
|
156
|
+
TokenType.OP_EQ: "==",
|
157
|
+
TokenType.OP_NOT_EQ: "!=",
|
158
|
+
TokenType.OP_STRICT_EQ: "===",
|
159
|
+
TokenType.OP_STRICT_NOT_EQ: "!==",
|
160
|
+
TokenType.OP_LT: "<",
|
161
|
+
TokenType.OP_GT: ">",
|
162
|
+
TokenType.OP_LTE: "<=",
|
163
|
+
TokenType.OP_GTE: ">=",
|
164
|
+
TokenType.OP_CARET: "^",
|
165
|
+
}
|
166
|
+
return cls._OPERATOR_MAP
|
167
|
+
|
168
|
+
def __init__(self, token: Token, operator: str, left: Expression) -> None:
|
169
|
+
"""Initialize an InfixExpression node.
|
170
|
+
|
171
|
+
Args:
|
172
|
+
token: The token containing the infix operator.
|
173
|
+
operator: The operator string (e.g., "+", "-", "==").
|
174
|
+
left: The left-hand expression.
|
175
|
+
"""
|
176
|
+
super().__init__(token)
|
177
|
+
self.operator = operator
|
178
|
+
self.left = left
|
179
|
+
self.right: Expression | None = None
|
180
|
+
|
181
|
+
def __str__(self) -> str:
|
182
|
+
"""Return the string representation of the infix expression.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
The expression in the format "(left operator right)", e.g., "(5 + 3)".
|
186
|
+
"""
|
187
|
+
return f"({self.left} {self.operator} {self.right})"
|
188
|
+
|
189
|
+
def desugar(self) -> "InfixExpression":
|
190
|
+
"""Desugar infix expression by normalizing operators and recursively desugaring operands.
|
191
|
+
|
192
|
+
Normalizes operators based on their token type to symbolic equivalents.
|
193
|
+
|
194
|
+
Returns:
|
195
|
+
A new InfixExpression with normalized operator and desugared operands.
|
196
|
+
"""
|
197
|
+
# Get the shared operator mapping
|
198
|
+
operator_map = self._get_operator_map()
|
199
|
+
|
200
|
+
# Normalize the operator based on token type
|
201
|
+
normalized_op = operator_map.get(self.token.type, self.operator)
|
202
|
+
|
203
|
+
# Create new expression with normalized operator
|
204
|
+
desugared = InfixExpression(self.token, normalized_op, self.left.desugar())
|
205
|
+
if self.right:
|
206
|
+
desugared.right = self.right.desugar()
|
207
|
+
return desugared
|
208
|
+
|
209
|
+
|
210
|
+
class Arguments(Expression):
|
211
|
+
"""Represents arguments for a function call.
|
212
|
+
|
213
|
+
Arguments can be positional, named (keyword), or a mix of both.
|
214
|
+
Positional arguments must come before named arguments.
|
215
|
+
|
216
|
+
Attributes:
|
217
|
+
positional: List of positional argument expressions.
|
218
|
+
named: List of tuples (name, value) for named arguments.
|
219
|
+
"""
|
220
|
+
|
221
|
+
def __init__(self, token: Token) -> None:
|
222
|
+
"""Initialize an Arguments node.
|
223
|
+
|
224
|
+
Args:
|
225
|
+
token: The token that begins the arguments (usually 'with').
|
226
|
+
"""
|
227
|
+
super().__init__(token)
|
228
|
+
self.positional: list[Expression] = []
|
229
|
+
self.named: list[tuple[Identifier, Expression]] = []
|
230
|
+
|
231
|
+
def __str__(self) -> str:
|
232
|
+
"""Return the string representation of the arguments.
|
233
|
+
|
234
|
+
Returns:
|
235
|
+
A comma-separated list of arguments.
|
236
|
+
"""
|
237
|
+
parts = []
|
238
|
+
# Add positional arguments
|
239
|
+
for arg in self.positional:
|
240
|
+
parts.append(str(arg))
|
241
|
+
# Add named arguments
|
242
|
+
for name, value in self.named:
|
243
|
+
parts.append(f"{name}: {value}")
|
244
|
+
return ", ".join(parts)
|
245
|
+
|
246
|
+
def desugar(self) -> "Arguments":
|
247
|
+
"""Desugar arguments by recursively desugaring all argument expressions.
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
A new Arguments node with desugared expressions.
|
251
|
+
"""
|
252
|
+
desugared = Arguments(self.token)
|
253
|
+
desugared.positional = [arg.desugar() for arg in self.positional]
|
254
|
+
desugared.named = [
|
255
|
+
(name.desugar() if isinstance(name, Expression) else name, value.desugar()) for name, value in self.named
|
256
|
+
]
|
257
|
+
return desugared
|
258
|
+
|
259
|
+
|
260
|
+
class ConditionalExpression(Expression):
|
261
|
+
"""A conditional (ternary) expression.
|
262
|
+
|
263
|
+
Conditional expressions evaluate to one of two values based on a condition.
|
264
|
+
In Machine Dialect™, they follow the pattern:
|
265
|
+
"value_if_true if/when condition, else/otherwise value_if_false"
|
266
|
+
|
267
|
+
Attributes:
|
268
|
+
condition: The boolean expression to evaluate.
|
269
|
+
consequence: The expression to return if condition is true.
|
270
|
+
alternative: The expression to return if condition is false.
|
271
|
+
"""
|
272
|
+
|
273
|
+
def __init__(self, token: Token, consequence: Expression) -> None:
|
274
|
+
"""Initialize a ConditionalExpression node.
|
275
|
+
|
276
|
+
Args:
|
277
|
+
token: The token where the expression begins.
|
278
|
+
consequence: The expression to return if condition is true.
|
279
|
+
"""
|
280
|
+
super().__init__(token)
|
281
|
+
self.consequence = consequence
|
282
|
+
self.condition: Expression | None = None
|
283
|
+
self.alternative: Expression | None = None
|
284
|
+
|
285
|
+
def __str__(self) -> str:
|
286
|
+
"""Return the string representation of the conditional expression.
|
287
|
+
|
288
|
+
Returns:
|
289
|
+
The expression in the format "(consequence if condition else alternative)".
|
290
|
+
"""
|
291
|
+
return f"({self.consequence} if {self.condition} else {self.alternative})"
|
292
|
+
|
293
|
+
def desugar(self) -> "ConditionalExpression":
|
294
|
+
"""Desugar conditional expression by recursively desugaring all parts.
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
A new ConditionalExpression with desugared components.
|
298
|
+
"""
|
299
|
+
desugared = ConditionalExpression(self.token, self.consequence.desugar())
|
300
|
+
if self.condition:
|
301
|
+
desugared.condition = self.condition.desugar()
|
302
|
+
if self.alternative:
|
303
|
+
desugared.alternative = self.alternative.desugar()
|
304
|
+
return desugared
|
305
|
+
|
306
|
+
|
307
|
+
class CollectionAccessExpression(Expression):
|
308
|
+
"""Access collection element by index or name.
|
309
|
+
|
310
|
+
Supports multiple access patterns:
|
311
|
+
- Ordinal access: `the first item of list`, `the second item of list`
|
312
|
+
- Numeric access: `item _5_ of list` (one-based indexing)
|
313
|
+
- Property access: `dict`'s name` for named lists
|
314
|
+
- Name access: Direct name access for dictionaries
|
315
|
+
|
316
|
+
Attributes:
|
317
|
+
collection: The collection being accessed.
|
318
|
+
accessor: The index, ordinal, or name used for access.
|
319
|
+
access_type: Type of access ('ordinal', 'numeric', 'name', 'property').
|
320
|
+
"""
|
321
|
+
|
322
|
+
def __init__(
|
323
|
+
self, token: Token, collection: Expression, accessor: Expression | str | int, access_type: str
|
324
|
+
) -> None:
|
325
|
+
"""Initialize a CollectionAccessExpression.
|
326
|
+
|
327
|
+
Args:
|
328
|
+
token: The token that begins this expression.
|
329
|
+
collection: The collection being accessed.
|
330
|
+
accessor: The index, ordinal, or name used for access.
|
331
|
+
access_type: Type of access ('ordinal', 'numeric', 'name', 'property').
|
332
|
+
"""
|
333
|
+
super().__init__(token)
|
334
|
+
self.collection = collection
|
335
|
+
self.accessor = accessor
|
336
|
+
self.access_type = access_type
|
337
|
+
|
338
|
+
def __str__(self) -> str:
|
339
|
+
"""Return the string representation of the collection access.
|
340
|
+
|
341
|
+
Returns:
|
342
|
+
A string representing the collection access pattern.
|
343
|
+
"""
|
344
|
+
if self.access_type == "ordinal":
|
345
|
+
return f"the {self.accessor} item of {self.collection}"
|
346
|
+
elif self.access_type == "numeric":
|
347
|
+
return f"item _{self.accessor}_ of {self.collection}"
|
348
|
+
elif self.access_type == "property":
|
349
|
+
return f"{self.collection}'s {self.accessor}"
|
350
|
+
else: # name
|
351
|
+
return f"{self.collection}[{self.accessor}]"
|
352
|
+
|
353
|
+
def desugar(self) -> "CollectionAccessExpression":
|
354
|
+
"""Desugar collection access by recursively desugaring the collection.
|
355
|
+
|
356
|
+
Returns:
|
357
|
+
A new CollectionAccessExpression with desugared collection.
|
358
|
+
"""
|
359
|
+
desugared = CollectionAccessExpression(self.token, self.collection.desugar(), self.accessor, self.access_type)
|
360
|
+
# If accessor is an expression, desugar it too
|
361
|
+
if isinstance(self.accessor, Expression):
|
362
|
+
desugared.accessor = self.accessor.desugar()
|
363
|
+
return desugared
|
364
|
+
|
365
|
+
def to_hir(self) -> "CollectionAccessExpression":
|
366
|
+
"""Convert collection access to HIR representation.
|
367
|
+
|
368
|
+
Converts one-based user indices to zero-based for internal use.
|
369
|
+
|
370
|
+
Returns:
|
371
|
+
HIR representation with adjusted indices.
|
372
|
+
"""
|
373
|
+
# Convert collection to HIR
|
374
|
+
hir_collection = self.collection.to_hir() if hasattr(self.collection, "to_hir") else self.collection
|
375
|
+
|
376
|
+
# Process accessor based on type
|
377
|
+
hir_accessor = self.accessor
|
378
|
+
if self.access_type == "ordinal":
|
379
|
+
# Convert ordinals to zero-based numeric indices
|
380
|
+
ordinal_map = {"first": 0, "second": 1, "third": 2}
|
381
|
+
if isinstance(self.accessor, str) and self.accessor.lower() in ordinal_map:
|
382
|
+
hir_accessor = ordinal_map[self.accessor.lower()]
|
383
|
+
# Change type to numeric since we converted
|
384
|
+
return CollectionAccessExpression(self.token, hir_collection, hir_accessor, "numeric")
|
385
|
+
elif self.accessor == "last":
|
386
|
+
# Keep "last" as special case - will be handled in MIR generation
|
387
|
+
hir_accessor = "last"
|
388
|
+
elif self.access_type == "numeric":
|
389
|
+
# Convert one-based to zero-based index
|
390
|
+
if isinstance(self.accessor, int):
|
391
|
+
hir_accessor = self.accessor - 1 # Convert to 0-based
|
392
|
+
elif isinstance(self.accessor, Expression):
|
393
|
+
# For expressions, we'll need to handle this in MIR generation
|
394
|
+
# by subtracting 1 at runtime
|
395
|
+
hir_accessor = self.accessor.to_hir() if hasattr(self.accessor, "to_hir") else self.accessor
|
396
|
+
elif isinstance(self.accessor, Expression):
|
397
|
+
hir_accessor = self.accessor.to_hir() if hasattr(self.accessor, "to_hir") else self.accessor
|
398
|
+
|
399
|
+
return CollectionAccessExpression(self.token, hir_collection, hir_accessor, self.access_type)
|
400
|
+
|
401
|
+
|
402
|
+
class ErrorExpression(Expression):
|
403
|
+
"""An expression that failed to parse correctly.
|
404
|
+
|
405
|
+
ErrorExpressions preserve the AST structure even when parsing fails,
|
406
|
+
allowing the parser to continue and collect multiple errors. They
|
407
|
+
contain information about what went wrong during parsing.
|
408
|
+
|
409
|
+
Attributes:
|
410
|
+
message: Human-readable error message describing what went wrong.
|
411
|
+
"""
|
412
|
+
|
413
|
+
def __init__(self, token: Token, message: str = "") -> None:
|
414
|
+
"""Initialize an ErrorExpression node.
|
415
|
+
|
416
|
+
Args:
|
417
|
+
token: The token where the error began.
|
418
|
+
message: Error message describing the parsing failure.
|
419
|
+
"""
|
420
|
+
super().__init__(token)
|
421
|
+
self.message = message
|
422
|
+
|
423
|
+
def __str__(self) -> str:
|
424
|
+
"""Return the string representation of the error expression.
|
425
|
+
|
426
|
+
Returns:
|
427
|
+
A string like "<error: message>".
|
428
|
+
"""
|
429
|
+
if self.message:
|
430
|
+
return f"<error: {self.message}>"
|
431
|
+
return "<error>"
|
432
|
+
|
433
|
+
def desugar(self) -> "ErrorExpression":
|
434
|
+
"""Error expressions remain unchanged.
|
435
|
+
|
436
|
+
Returns:
|
437
|
+
Self unchanged.
|
438
|
+
"""
|
439
|
+
return self
|