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,311 @@
|
|
1
|
+
from machine_dialect.ast import DefineStatement
|
2
|
+
from machine_dialect.parser import Parser
|
3
|
+
|
4
|
+
|
5
|
+
class TestDefineStatements:
|
6
|
+
"""Test parsing of Define statements."""
|
7
|
+
|
8
|
+
def test_parse_simple_define(self) -> None:
|
9
|
+
"""Test parsing basic Define statement."""
|
10
|
+
source = "Define `count` as Whole Number."
|
11
|
+
parser = Parser()
|
12
|
+
|
13
|
+
# Debug: Add print before parsing
|
14
|
+
from machine_dialect.lexer import Lexer
|
15
|
+
|
16
|
+
lexer = Lexer(source)
|
17
|
+
tokens = []
|
18
|
+
while True:
|
19
|
+
tok = lexer.next_token()
|
20
|
+
tokens.append(tok)
|
21
|
+
if tok.type.name == "MISC_EOF":
|
22
|
+
break
|
23
|
+
print(f"Tokens: {[(t.type.name, t.literal) for t in tokens]}")
|
24
|
+
|
25
|
+
program = parser.parse(source)
|
26
|
+
|
27
|
+
# Debug: Print any parser errors
|
28
|
+
if parser.errors:
|
29
|
+
for error in parser.errors:
|
30
|
+
print(f"Parser error: {error}")
|
31
|
+
|
32
|
+
assert len(program.statements) == 1
|
33
|
+
stmt = program.statements[0]
|
34
|
+
assert isinstance(stmt, DefineStatement)
|
35
|
+
assert stmt.name.value == "count"
|
36
|
+
assert stmt.type_spec == ["Whole Number"]
|
37
|
+
assert stmt.initial_value is None
|
38
|
+
|
39
|
+
def test_parse_define_with_default(self) -> None:
|
40
|
+
"""Test parsing Define with default value."""
|
41
|
+
source = 'Define `message` as Text (default: _"Hello World"_).'
|
42
|
+
|
43
|
+
# Debug: Print tokens first
|
44
|
+
from machine_dialect.lexer import Lexer
|
45
|
+
|
46
|
+
lexer = Lexer(source)
|
47
|
+
tokens = []
|
48
|
+
while True:
|
49
|
+
tok = lexer.next_token()
|
50
|
+
tokens.append(tok)
|
51
|
+
if tok.type.name == "MISC_EOF":
|
52
|
+
break
|
53
|
+
print(f"Tokens: {[(t.type.name, t.literal) for t in tokens]}")
|
54
|
+
|
55
|
+
parser = Parser()
|
56
|
+
program = parser.parse(source)
|
57
|
+
|
58
|
+
# Debug: Print statements
|
59
|
+
print(f"Got {len(program.statements)} statements:")
|
60
|
+
for i, stmt in enumerate(program.statements):
|
61
|
+
print(f" {i}: {type(stmt).__name__}: {stmt}")
|
62
|
+
|
63
|
+
assert len(program.statements) == 1
|
64
|
+
stmt = program.statements[0]
|
65
|
+
assert isinstance(stmt, DefineStatement)
|
66
|
+
assert stmt.name.value == "message"
|
67
|
+
assert stmt.type_spec == ["Text"]
|
68
|
+
assert stmt.initial_value is not None
|
69
|
+
assert str(stmt.initial_value) == '_"Hello World"_'
|
70
|
+
|
71
|
+
def test_parse_define_with_integer_default(self) -> None:
|
72
|
+
"""Test parsing Define with integer default."""
|
73
|
+
source = "Define `age` as Whole Number (default: _25_)."
|
74
|
+
parser = Parser()
|
75
|
+
program = parser.parse(source)
|
76
|
+
|
77
|
+
assert len(program.statements) == 1
|
78
|
+
stmt = program.statements[0]
|
79
|
+
assert isinstance(stmt, DefineStatement)
|
80
|
+
assert stmt.name.value == "age"
|
81
|
+
assert stmt.type_spec == ["Whole Number"]
|
82
|
+
assert str(stmt.initial_value) == "_25_"
|
83
|
+
|
84
|
+
def test_parse_define_with_boolean_default(self) -> None:
|
85
|
+
"""Test parsing Define with boolean default."""
|
86
|
+
source = "Define `is_active` as Yes/No (default: _yes_)."
|
87
|
+
parser = Parser()
|
88
|
+
program = parser.parse(source)
|
89
|
+
|
90
|
+
assert len(program.statements) == 1
|
91
|
+
stmt = program.statements[0]
|
92
|
+
assert isinstance(stmt, DefineStatement)
|
93
|
+
assert stmt.name.value == "is_active"
|
94
|
+
assert stmt.type_spec == ["Yes/No"]
|
95
|
+
assert str(stmt.initial_value) == "_Yes_"
|
96
|
+
|
97
|
+
def test_parse_union_type(self) -> None:
|
98
|
+
"""Test parsing Define with union types."""
|
99
|
+
source = "Define `value` as Whole Number or Text."
|
100
|
+
parser = Parser()
|
101
|
+
program = parser.parse(source)
|
102
|
+
|
103
|
+
assert len(program.statements) == 1
|
104
|
+
stmt = program.statements[0]
|
105
|
+
assert isinstance(stmt, DefineStatement)
|
106
|
+
assert stmt.name.value == "value"
|
107
|
+
assert stmt.type_spec == ["Whole Number", "Text"]
|
108
|
+
|
109
|
+
def test_parse_multiple_union_types(self) -> None:
|
110
|
+
"""Test parsing Define with multiple union types."""
|
111
|
+
source = "Define `data` as Number or Yes/No or Text or Empty."
|
112
|
+
|
113
|
+
# Debug: Check tokens
|
114
|
+
from machine_dialect.lexer import Lexer
|
115
|
+
|
116
|
+
lexer = Lexer(source)
|
117
|
+
tokens = []
|
118
|
+
while True:
|
119
|
+
tok = lexer.next_token()
|
120
|
+
tokens.append(tok)
|
121
|
+
if tok.type.name == "MISC_EOF":
|
122
|
+
break
|
123
|
+
print(f"Tokens: {[(t.type.name, t.literal) for t in tokens]}")
|
124
|
+
|
125
|
+
parser = Parser()
|
126
|
+
program = parser.parse(source)
|
127
|
+
|
128
|
+
assert len(program.statements) == 1
|
129
|
+
stmt = program.statements[0]
|
130
|
+
assert isinstance(stmt, DefineStatement)
|
131
|
+
print(f"Got type_spec: {stmt.type_spec}")
|
132
|
+
assert stmt.type_spec == ["Number", "Yes/No", "Text", "Empty"]
|
133
|
+
|
134
|
+
def test_parse_various_type_names(self) -> None:
|
135
|
+
"""Test parsing various type names."""
|
136
|
+
test_cases = [
|
137
|
+
("Define `a` as Text.", ["Text"]),
|
138
|
+
("Define `b` as Whole Number.", ["Whole Number"]),
|
139
|
+
("Define `c` as Float.", ["Float"]),
|
140
|
+
("Define `d` as Number.", ["Number"]),
|
141
|
+
("Define `e` as Yes/No.", ["Yes/No"]),
|
142
|
+
("Define `f` as URL.", ["URL"]),
|
143
|
+
("Define `g` as Date.", ["Date"]),
|
144
|
+
("Define `h` as DateTime.", ["DateTime"]),
|
145
|
+
("Define `i` as Time.", ["Time"]),
|
146
|
+
("Define `j` as List.", ["List"]),
|
147
|
+
("Define `k` as Empty.", ["Empty"]),
|
148
|
+
]
|
149
|
+
|
150
|
+
for source, expected_types in test_cases:
|
151
|
+
parser = Parser()
|
152
|
+
program = parser.parse(source)
|
153
|
+
assert len(program.statements) == 1
|
154
|
+
stmt = program.statements[0]
|
155
|
+
assert isinstance(stmt, DefineStatement)
|
156
|
+
assert stmt.type_spec == expected_types
|
157
|
+
|
158
|
+
def test_error_missing_variable_name(self) -> None:
|
159
|
+
"""Test error when variable name is missing."""
|
160
|
+
source = "Define as Whole Number."
|
161
|
+
parser = Parser()
|
162
|
+
_ = parser.parse(source)
|
163
|
+
|
164
|
+
assert len(parser.errors) > 0
|
165
|
+
# The error message or structure may vary, just check we got errors
|
166
|
+
|
167
|
+
def test_error_missing_as_keyword(self) -> None:
|
168
|
+
"""Test error when 'as' keyword is missing."""
|
169
|
+
source = "Define `count` Whole Number."
|
170
|
+
parser = Parser()
|
171
|
+
_ = parser.parse(source)
|
172
|
+
|
173
|
+
assert len(parser.errors) > 0
|
174
|
+
|
175
|
+
def test_error_missing_type(self) -> None:
|
176
|
+
"""Test error when type is missing."""
|
177
|
+
source = "Define `count` as."
|
178
|
+
parser = Parser()
|
179
|
+
_ = parser.parse(source)
|
180
|
+
|
181
|
+
assert len(parser.errors) > 0
|
182
|
+
|
183
|
+
def test_error_invalid_default_syntax(self) -> None:
|
184
|
+
"""Test error with invalid default syntax."""
|
185
|
+
source = "Define `x` as Whole Number (default _5_)." # Missing colon
|
186
|
+
parser = Parser()
|
187
|
+
_ = parser.parse(source)
|
188
|
+
|
189
|
+
assert len(parser.errors) > 0
|
190
|
+
|
191
|
+
def test_define_with_stopwords(self) -> None:
|
192
|
+
"""Test that stopwords are properly skipped."""
|
193
|
+
source = "Define the `count` as a Whole Number."
|
194
|
+
parser = Parser()
|
195
|
+
program = parser.parse(source)
|
196
|
+
|
197
|
+
assert len(program.statements) == 1
|
198
|
+
stmt = program.statements[0]
|
199
|
+
assert isinstance(stmt, DefineStatement)
|
200
|
+
assert stmt.name.value == "count"
|
201
|
+
assert stmt.type_spec == ["Whole Number"]
|
202
|
+
|
203
|
+
def test_multiple_define_statements(self) -> None:
|
204
|
+
"""Test parsing multiple Define statements."""
|
205
|
+
source = """
|
206
|
+
Define `name` as Text.
|
207
|
+
Define `age` as Whole Number.
|
208
|
+
Define `active` as Yes/No (default: _yes_).
|
209
|
+
"""
|
210
|
+
parser = Parser()
|
211
|
+
program = parser.parse(source)
|
212
|
+
|
213
|
+
assert len(program.statements) == 3
|
214
|
+
|
215
|
+
# Check first statement
|
216
|
+
stmt1 = program.statements[0]
|
217
|
+
assert isinstance(stmt1, DefineStatement)
|
218
|
+
assert stmt1.name.value == "name"
|
219
|
+
assert stmt1.type_spec == ["Text"]
|
220
|
+
assert stmt1.initial_value is None
|
221
|
+
|
222
|
+
# Check second statement
|
223
|
+
stmt2 = program.statements[1]
|
224
|
+
assert isinstance(stmt2, DefineStatement)
|
225
|
+
assert stmt2.name.value == "age"
|
226
|
+
assert stmt2.type_spec == ["Whole Number"]
|
227
|
+
assert stmt2.initial_value is None
|
228
|
+
|
229
|
+
# Check third statement
|
230
|
+
stmt3 = program.statements[2]
|
231
|
+
assert isinstance(stmt3, DefineStatement)
|
232
|
+
assert stmt3.name.value == "active"
|
233
|
+
assert stmt3.type_spec == ["Yes/No"]
|
234
|
+
assert stmt3.initial_value is not None
|
235
|
+
|
236
|
+
def test_complex_nested_default_expressions(self) -> None:
|
237
|
+
"""Test parsing Define with complex nested default expressions."""
|
238
|
+
# Test arithmetic expression as default
|
239
|
+
source1 = "Define `result` as Number (default: _70_)."
|
240
|
+
parser1 = Parser()
|
241
|
+
program1 = parser1.parse(source1)
|
242
|
+
assert len(program1.statements) == 1
|
243
|
+
stmt1 = program1.statements[0]
|
244
|
+
assert isinstance(stmt1, DefineStatement)
|
245
|
+
assert stmt1.name.value == "result"
|
246
|
+
assert stmt1.type_spec == ["Number"]
|
247
|
+
assert stmt1.initial_value is not None
|
248
|
+
|
249
|
+
# Test string literal as default
|
250
|
+
source2 = 'Define `value` as Text (default: _"Hello World"_).'
|
251
|
+
parser2 = Parser()
|
252
|
+
program2 = parser2.parse(source2)
|
253
|
+
assert len(program2.statements) == 1
|
254
|
+
stmt2 = program2.statements[0]
|
255
|
+
assert isinstance(stmt2, DefineStatement)
|
256
|
+
assert stmt2.initial_value is not None
|
257
|
+
|
258
|
+
# Test boolean expression as default
|
259
|
+
source3 = "Define `flag` as Yes/No (default: _yes_)."
|
260
|
+
parser3 = Parser()
|
261
|
+
program3 = parser3.parse(source3)
|
262
|
+
assert len(program3.statements) == 1
|
263
|
+
stmt3 = program3.statements[0]
|
264
|
+
assert isinstance(stmt3, DefineStatement)
|
265
|
+
assert stmt3.type_spec == ["Yes/No"]
|
266
|
+
assert stmt3.initial_value is not None
|
267
|
+
|
268
|
+
def test_error_recovery_paths(self) -> None:
|
269
|
+
"""Test error recovery when parsing malformed Define statements."""
|
270
|
+
# Missing closing parenthesis in default
|
271
|
+
source1 = "Define `x` as Number (default: _5_. Define `y` as Text."
|
272
|
+
parser1 = Parser()
|
273
|
+
_ = parser1.parse(source1)
|
274
|
+
# Should have errors but still parse the second Define
|
275
|
+
assert len(parser1.errors) > 0
|
276
|
+
# May still have some statements parsed
|
277
|
+
|
278
|
+
# Malformed type specification
|
279
|
+
source2 = "Define `z` as or Text."
|
280
|
+
parser2 = Parser()
|
281
|
+
_ = parser2.parse(source2)
|
282
|
+
assert len(parser2.errors) > 0
|
283
|
+
|
284
|
+
# Multiple errors in one statement
|
285
|
+
source3 = "Define as (default: )."
|
286
|
+
parser3 = Parser()
|
287
|
+
_ = parser3.parse(source3)
|
288
|
+
assert len(parser3.errors) > 0
|
289
|
+
|
290
|
+
def test_edge_case_union_types(self) -> None:
|
291
|
+
"""Test edge cases for union type specifications."""
|
292
|
+
# Very long union type list
|
293
|
+
source1 = "Define `data` as Text or Number or Yes/No or Date or Time or URL or List or Empty."
|
294
|
+
parser1 = Parser()
|
295
|
+
program1 = parser1.parse(source1)
|
296
|
+
assert len(program1.statements) == 1
|
297
|
+
stmt1 = program1.statements[0]
|
298
|
+
assert isinstance(stmt1, DefineStatement)
|
299
|
+
assert len(stmt1.type_spec) == 8
|
300
|
+
assert "Text" in stmt1.type_spec
|
301
|
+
assert "Empty" in stmt1.type_spec
|
302
|
+
|
303
|
+
# Union type with default value
|
304
|
+
source2 = "Define `flexible` as Text or Number (default: _42_)."
|
305
|
+
parser2 = Parser()
|
306
|
+
program2 = parser2.parse(source2)
|
307
|
+
assert len(program2.statements) == 1
|
308
|
+
stmt2 = program2.statements[0]
|
309
|
+
assert isinstance(stmt2, DefineStatement)
|
310
|
+
assert stmt2.type_spec == ["Text", "Number"]
|
311
|
+
assert stmt2.initial_value is not None
|
@@ -0,0 +1,115 @@
|
|
1
|
+
"""Tests for dictionary keys/values extraction parsing."""
|
2
|
+
|
3
|
+
from machine_dialect.ast.dict_extraction import DictExtraction
|
4
|
+
from machine_dialect.ast.expressions import Identifier
|
5
|
+
from machine_dialect.ast.statements import SetStatement
|
6
|
+
from machine_dialect.lexer.tokens import Token, TokenType
|
7
|
+
from machine_dialect.parser.parser import Parser
|
8
|
+
|
9
|
+
|
10
|
+
class TestDictExtraction:
|
11
|
+
"""Test parsing of dictionary extraction expressions."""
|
12
|
+
|
13
|
+
def test_parse_names_extraction(self) -> None:
|
14
|
+
"""Test parsing 'the names of `dict`'."""
|
15
|
+
source = "Set `result` to the names of `person`."
|
16
|
+
parser = Parser()
|
17
|
+
program = parser.parse(source)
|
18
|
+
assert len(program.statements) == 1
|
19
|
+
stmt = program.statements[0]
|
20
|
+
assert isinstance(stmt, SetStatement) # Type assertion for MyPy
|
21
|
+
expr = stmt.value
|
22
|
+
assert isinstance(expr, DictExtraction)
|
23
|
+
assert expr.extract_type == "names"
|
24
|
+
assert isinstance(expr.dictionary, Identifier)
|
25
|
+
assert expr.dictionary.value == "person"
|
26
|
+
assert str(expr) == "the names of `person`"
|
27
|
+
|
28
|
+
def test_parse_contents_extraction(self) -> None:
|
29
|
+
"""Test parsing 'the contents of `dict`'."""
|
30
|
+
source = "Set `result` to the contents of `config`."
|
31
|
+
parser = Parser()
|
32
|
+
program = parser.parse(source)
|
33
|
+
|
34
|
+
assert len(program.statements) == 1
|
35
|
+
stmt = program.statements[0]
|
36
|
+
assert isinstance(stmt, SetStatement) # Type assertion for MyPy
|
37
|
+
expr = stmt.value
|
38
|
+
|
39
|
+
assert isinstance(expr, DictExtraction)
|
40
|
+
assert expr.extract_type == "contents"
|
41
|
+
assert isinstance(expr.dictionary, Identifier)
|
42
|
+
assert expr.dictionary.value == "config"
|
43
|
+
assert str(expr) == "the contents of `config`"
|
44
|
+
|
45
|
+
def test_parse_names_in_set_statement(self) -> None:
|
46
|
+
"""Test parsing 'Set `names` to the names of `person`.'"""
|
47
|
+
source = "Set `my_names` to the names of `person`."
|
48
|
+
parser = Parser()
|
49
|
+
program = parser.parse(source)
|
50
|
+
|
51
|
+
assert len(program.statements) == 1
|
52
|
+
stmt = program.statements[0]
|
53
|
+
assert stmt.__class__.__name__ == "SetStatement"
|
54
|
+
assert isinstance(stmt, SetStatement) # Type assertion for MyPy
|
55
|
+
|
56
|
+
# Check the value is a DictExtraction
|
57
|
+
assert isinstance(stmt.value, DictExtraction)
|
58
|
+
assert stmt.value.extract_type == "names"
|
59
|
+
dict_val = stmt.value.dictionary
|
60
|
+
assert isinstance(dict_val, Identifier)
|
61
|
+
assert dict_val.value == "person"
|
62
|
+
|
63
|
+
def test_parse_contents_in_set_statement(self) -> None:
|
64
|
+
"""Test parsing 'Set `values` to the contents of `dict`.'"""
|
65
|
+
source = "Set `my_values` to the contents of `settings`."
|
66
|
+
parser = Parser()
|
67
|
+
program = parser.parse(source)
|
68
|
+
|
69
|
+
assert len(program.statements) == 1
|
70
|
+
stmt = program.statements[0]
|
71
|
+
assert stmt.__class__.__name__ == "SetStatement"
|
72
|
+
assert isinstance(stmt, SetStatement) # Type assertion for MyPy
|
73
|
+
|
74
|
+
# Check the value is a DictExtraction
|
75
|
+
assert isinstance(stmt.value, DictExtraction)
|
76
|
+
assert stmt.value.extract_type == "contents"
|
77
|
+
dict_val = stmt.value.dictionary
|
78
|
+
assert isinstance(dict_val, Identifier)
|
79
|
+
assert dict_val.value == "settings"
|
80
|
+
|
81
|
+
def test_parse_invalid_extraction_missing_dict(self) -> None:
|
82
|
+
"""Test error when dictionary is missing."""
|
83
|
+
source = "Set `result` to the names of."
|
84
|
+
parser = Parser()
|
85
|
+
program = parser.parse(source)
|
86
|
+
|
87
|
+
# The parser should handle the missing dictionary gracefully
|
88
|
+
assert len(program.statements) == 1
|
89
|
+
stmt = program.statements[0]
|
90
|
+
# The value might be an error expression or partial parse
|
91
|
+
assert stmt is not None
|
92
|
+
|
93
|
+
def test_dict_extraction_to_hir(self) -> None:
|
94
|
+
"""Test that DictExtraction converts to HIR properly."""
|
95
|
+
token = Token(TokenType.MISC_STOPWORD, "the", 1, 1)
|
96
|
+
dict_ident = Identifier(Token(TokenType.MISC_IDENT, "person", 1, 5), "person")
|
97
|
+
|
98
|
+
extraction = DictExtraction(token, dict_ident, "names")
|
99
|
+
hir = extraction.to_hir()
|
100
|
+
|
101
|
+
assert isinstance(hir, DictExtraction)
|
102
|
+
assert hir.extract_type == "names"
|
103
|
+
assert isinstance(hir.dictionary, Identifier)
|
104
|
+
|
105
|
+
def test_dict_extraction_desugar(self) -> None:
|
106
|
+
"""Test that DictExtraction desugars properly."""
|
107
|
+
token = Token(TokenType.MISC_STOPWORD, "the", 1, 1)
|
108
|
+
dict_ident = Identifier(Token(TokenType.MISC_IDENT, "person", 1, 5), "person")
|
109
|
+
|
110
|
+
extraction = DictExtraction(token, dict_ident, "contents")
|
111
|
+
desugared = extraction.desugar()
|
112
|
+
|
113
|
+
assert isinstance(desugared, DictExtraction)
|
114
|
+
assert desugared.extract_type == "contents"
|
115
|
+
assert isinstance(desugared.dictionary, Identifier)
|
@@ -0,0 +1,155 @@
|
|
1
|
+
"""Tests for parsing the empty literal."""
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from machine_dialect.ast import EmptyLiteral, ExpressionStatement
|
6
|
+
from machine_dialect.parser import Parser
|
7
|
+
|
8
|
+
|
9
|
+
class TestEmptyLiteral:
|
10
|
+
"""Test parsing of the empty literal."""
|
11
|
+
|
12
|
+
def test_parse_empty_literal(self) -> None:
|
13
|
+
"""Test parsing the empty keyword as a literal."""
|
14
|
+
source = "empty"
|
15
|
+
parser = Parser()
|
16
|
+
program = parser.parse(source, check_semantics=False)
|
17
|
+
|
18
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
19
|
+
assert len(program.statements) == 1
|
20
|
+
|
21
|
+
statement = program.statements[0]
|
22
|
+
assert isinstance(statement, ExpressionStatement)
|
23
|
+
assert isinstance(statement.expression, EmptyLiteral)
|
24
|
+
assert statement.expression.value is None
|
25
|
+
assert str(statement.expression) == "empty"
|
26
|
+
|
27
|
+
def test_empty_in_set_statement(self) -> None:
|
28
|
+
"""Test using empty in a set statement."""
|
29
|
+
source = """Define `result` as Empty.
|
30
|
+
Set `result` to empty."""
|
31
|
+
parser = Parser()
|
32
|
+
program = parser.parse(source, check_semantics=False)
|
33
|
+
|
34
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
35
|
+
assert len(program.statements) == 2
|
36
|
+
|
37
|
+
from machine_dialect.ast import DefineStatement, SetStatement
|
38
|
+
|
39
|
+
# Check Define statement
|
40
|
+
assert isinstance(program.statements[0], DefineStatement)
|
41
|
+
|
42
|
+
# Check Set statement
|
43
|
+
statement = program.statements[1]
|
44
|
+
assert isinstance(statement, SetStatement)
|
45
|
+
assert isinstance(statement.value, EmptyLiteral)
|
46
|
+
|
47
|
+
def test_empty_in_comparison(self) -> None:
|
48
|
+
"""Test using empty in comparison expressions."""
|
49
|
+
test_cases = [
|
50
|
+
("x equals empty", "`x`", "equals", "empty"),
|
51
|
+
("`value` is not empty", "`value`", "is not", "empty"),
|
52
|
+
("result is strictly equal to empty", "`result`", "is strictly equal to", "empty"),
|
53
|
+
]
|
54
|
+
|
55
|
+
for source, expected_left, expected_op, expected_right in test_cases:
|
56
|
+
parser = Parser()
|
57
|
+
program = parser.parse(source, check_semantics=False)
|
58
|
+
|
59
|
+
assert len(parser.errors) == 0, f"Parser errors for '{source}': {parser.errors}"
|
60
|
+
assert len(program.statements) == 1
|
61
|
+
|
62
|
+
statement = program.statements[0]
|
63
|
+
assert isinstance(statement, ExpressionStatement)
|
64
|
+
|
65
|
+
from machine_dialect.ast import InfixExpression
|
66
|
+
|
67
|
+
expr = statement.expression
|
68
|
+
assert isinstance(expr, InfixExpression)
|
69
|
+
assert str(expr.left) == expected_left
|
70
|
+
assert expr.operator == expected_op
|
71
|
+
assert str(expr.right) == expected_right
|
72
|
+
|
73
|
+
def test_empty_in_if_condition(self) -> None:
|
74
|
+
"""Test using empty in if statement conditions."""
|
75
|
+
source = """
|
76
|
+
Define `value` as Whole Number or Empty.
|
77
|
+
Define `result` as Whole Number.
|
78
|
+
if `value` equals empty then:
|
79
|
+
> Set `result` to _0_.
|
80
|
+
"""
|
81
|
+
parser = Parser()
|
82
|
+
program = parser.parse(source, check_semantics=False)
|
83
|
+
|
84
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
85
|
+
assert len(program.statements) == 3
|
86
|
+
|
87
|
+
from machine_dialect.ast import DefineStatement, IfStatement, InfixExpression
|
88
|
+
|
89
|
+
# Check Define statements
|
90
|
+
assert isinstance(program.statements[0], DefineStatement)
|
91
|
+
assert isinstance(program.statements[1], DefineStatement)
|
92
|
+
|
93
|
+
# Check If statement
|
94
|
+
if_stmt = program.statements[2]
|
95
|
+
assert isinstance(if_stmt, IfStatement)
|
96
|
+
assert isinstance(if_stmt.condition, InfixExpression)
|
97
|
+
assert str(if_stmt.condition.right) == "empty"
|
98
|
+
|
99
|
+
def test_empty_in_return_statement(self) -> None:
|
100
|
+
"""Test using empty in a return statement."""
|
101
|
+
source = "give back empty."
|
102
|
+
parser = Parser()
|
103
|
+
program = parser.parse(source, check_semantics=False)
|
104
|
+
|
105
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
106
|
+
assert len(program.statements) == 1
|
107
|
+
|
108
|
+
from machine_dialect.ast import ReturnStatement
|
109
|
+
|
110
|
+
statement = program.statements[0]
|
111
|
+
assert isinstance(statement, ReturnStatement)
|
112
|
+
assert isinstance(statement.return_value, EmptyLiteral)
|
113
|
+
|
114
|
+
def test_empty_not_confused_with_identifier(self) -> None:
|
115
|
+
"""Test that 'empty' is recognized as a keyword, not an identifier."""
|
116
|
+
# Test that 'empty' is parsed as EmptyLiteral
|
117
|
+
parser1 = Parser()
|
118
|
+
program1 = parser1.parse("empty", check_semantics=False)
|
119
|
+
assert len(parser1.errors) == 0
|
120
|
+
stmt1 = program1.statements[0]
|
121
|
+
assert isinstance(stmt1, ExpressionStatement)
|
122
|
+
assert isinstance(stmt1.expression, EmptyLiteral)
|
123
|
+
|
124
|
+
# Test that similar words are parsed as identifiers
|
125
|
+
parser2 = Parser()
|
126
|
+
program2 = parser2.parse("empties", check_semantics=False)
|
127
|
+
assert len(parser2.errors) == 0
|
128
|
+
|
129
|
+
from machine_dialect.ast import Identifier
|
130
|
+
|
131
|
+
stmt2 = program2.statements[0]
|
132
|
+
assert isinstance(stmt2, ExpressionStatement)
|
133
|
+
assert isinstance(stmt2.expression, Identifier)
|
134
|
+
|
135
|
+
@pytest.mark.parametrize(
|
136
|
+
"source",
|
137
|
+
[
|
138
|
+
"Empty", # Different case
|
139
|
+
"EMPTY", # All caps
|
140
|
+
"eMpTy", # Mixed case
|
141
|
+
],
|
142
|
+
)
|
143
|
+
def test_empty_case_insensitive(self, source: str) -> None:
|
144
|
+
"""Test that empty keyword is case-insensitive."""
|
145
|
+
parser = Parser()
|
146
|
+
program = parser.parse(source, check_semantics=False)
|
147
|
+
|
148
|
+
assert len(parser.errors) == 0, f"Parser errors for '{source}': {parser.errors}"
|
149
|
+
assert len(program.statements) == 1
|
150
|
+
|
151
|
+
statement = program.statements[0]
|
152
|
+
assert isinstance(statement, ExpressionStatement)
|
153
|
+
assert isinstance(statement.expression, EmptyLiteral)
|
154
|
+
# String representation should always be lowercase
|
155
|
+
assert str(statement.expression) == "empty"
|