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,137 @@
|
|
1
|
+
"""Tests for parsing integer literal expressions."""
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from machine_dialect.ast import ExpressionStatement
|
6
|
+
from machine_dialect.parser import Parser
|
7
|
+
from machine_dialect.parser.tests.helper_functions import (
|
8
|
+
assert_literal_expression,
|
9
|
+
assert_program_statements,
|
10
|
+
)
|
11
|
+
|
12
|
+
|
13
|
+
class TestWholeNumberLiteralExpressions:
|
14
|
+
"""Test cases for parsing integer literal expressions."""
|
15
|
+
|
16
|
+
@pytest.mark.parametrize(
|
17
|
+
"source,expected_value",
|
18
|
+
[
|
19
|
+
# Single digit integers
|
20
|
+
("0", 0),
|
21
|
+
("1", 1),
|
22
|
+
("5", 5),
|
23
|
+
("9", 9),
|
24
|
+
# Multi-digit integers
|
25
|
+
("10", 10),
|
26
|
+
("42", 42),
|
27
|
+
("123", 123),
|
28
|
+
("999", 999),
|
29
|
+
# Large integers
|
30
|
+
("1000", 1000),
|
31
|
+
("12345", 12345),
|
32
|
+
("99999", 99999),
|
33
|
+
("1000000", 1000000),
|
34
|
+
# Edge cases
|
35
|
+
("2147483647", 2147483647), # Max 32-bit signed int
|
36
|
+
("9223372036854775807", 9223372036854775807), # Max 64-bit signed int
|
37
|
+
# Integers with underscores (one on each side)
|
38
|
+
("_42_", 42),
|
39
|
+
("_123_", 123),
|
40
|
+
("_1_", 1),
|
41
|
+
("_999_", 999),
|
42
|
+
("_12345_", 12345),
|
43
|
+
("_0_", 0),
|
44
|
+
],
|
45
|
+
)
|
46
|
+
def test_integer_literal_expression(self, source: str, expected_value: int) -> None:
|
47
|
+
"""Test parsing various integer literal expressions.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
source: The source code to parse.
|
51
|
+
expected_value: The expected integer value.
|
52
|
+
"""
|
53
|
+
parser = Parser()
|
54
|
+
|
55
|
+
program = parser.parse(source)
|
56
|
+
|
57
|
+
assert_program_statements(parser, program)
|
58
|
+
|
59
|
+
statement = program.statements[0]
|
60
|
+
assert isinstance(statement, ExpressionStatement)
|
61
|
+
assert statement.expression is not None
|
62
|
+
|
63
|
+
assert_literal_expression(statement.expression, expected_value)
|
64
|
+
|
65
|
+
def test_integer_with_period(self) -> None:
|
66
|
+
"""Test parsing integer literal followed by period."""
|
67
|
+
source = "42."
|
68
|
+
parser = Parser()
|
69
|
+
|
70
|
+
program = parser.parse(source)
|
71
|
+
|
72
|
+
assert_program_statements(parser, program)
|
73
|
+
|
74
|
+
statement = program.statements[0]
|
75
|
+
assert isinstance(statement, ExpressionStatement)
|
76
|
+
assert statement.expression is not None
|
77
|
+
|
78
|
+
assert_literal_expression(statement.expression, 42)
|
79
|
+
|
80
|
+
def test_multiple_integer_statements(self) -> None:
|
81
|
+
"""Test parsing multiple integer literal statements."""
|
82
|
+
source = "1. 2. 3."
|
83
|
+
parser = Parser()
|
84
|
+
|
85
|
+
program = parser.parse(source)
|
86
|
+
|
87
|
+
assert len(parser.errors) == 0
|
88
|
+
assert len(program.statements) == 3
|
89
|
+
|
90
|
+
# Check each statement
|
91
|
+
for i, expected_value in enumerate([1, 2, 3]):
|
92
|
+
statement = program.statements[i]
|
93
|
+
assert isinstance(statement, ExpressionStatement)
|
94
|
+
assert statement.expression is not None
|
95
|
+
assert_literal_expression(statement.expression, expected_value)
|
96
|
+
|
97
|
+
@pytest.mark.parametrize(
|
98
|
+
"source,error_substring",
|
99
|
+
[
|
100
|
+
# Underscore only on left side
|
101
|
+
("_42", "_42"),
|
102
|
+
("_123", "_123"),
|
103
|
+
("_1", "_1"),
|
104
|
+
# Underscore only on right side
|
105
|
+
("42_", "42_"),
|
106
|
+
("123_", "123_"),
|
107
|
+
("1_", "1_"),
|
108
|
+
# Multiple underscores
|
109
|
+
("__42__", "__42__"),
|
110
|
+
("___123___", "___123___"),
|
111
|
+
("__1__", "__1__"),
|
112
|
+
# Mixed multiple and single
|
113
|
+
("__42_", "__42_"),
|
114
|
+
("_42__", "_42__"),
|
115
|
+
],
|
116
|
+
)
|
117
|
+
def test_invalid_underscore_formats_produce_errors(self, source: str, error_substring: str) -> None:
|
118
|
+
"""Test that invalid underscore formats produce lexer errors.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
source: The source code with invalid underscore format.
|
122
|
+
error_substring: Expected substring in the error message.
|
123
|
+
"""
|
124
|
+
# Lexer instantiation moved to Parser.parse()
|
125
|
+
parser = Parser()
|
126
|
+
|
127
|
+
# Parse the program (parser collects lexer errors)
|
128
|
+
_ = parser.parse(source)
|
129
|
+
|
130
|
+
# Should have at least one error
|
131
|
+
assert len(parser.errors) >= 1, f"Expected error for invalid format: {source}"
|
132
|
+
|
133
|
+
# Check that the error mentions the invalid token
|
134
|
+
error_messages = [str(error) for error in parser.errors]
|
135
|
+
assert any(error_substring in msg for msg in error_messages), (
|
136
|
+
f"Expected error to mention '{error_substring}', got errors: {error_messages}"
|
137
|
+
)
|
@@ -0,0 +1,269 @@
|
|
1
|
+
"""Tests for Interaction statements (public methods) in Machine Dialect™."""
|
2
|
+
|
3
|
+
from machine_dialect.ast import (
|
4
|
+
BlockStatement,
|
5
|
+
EmptyLiteral,
|
6
|
+
IfStatement,
|
7
|
+
InteractionStatement,
|
8
|
+
Output,
|
9
|
+
)
|
10
|
+
from machine_dialect.parser import Parser
|
11
|
+
|
12
|
+
|
13
|
+
class TestInteractionStatements:
|
14
|
+
"""Test parsing of Interaction statements (public methods)."""
|
15
|
+
|
16
|
+
def test_simple_interaction_without_parameters(self) -> None:
|
17
|
+
"""Test parsing a simple interaction without parameters."""
|
18
|
+
source = """### **Interaction**: `turn alarm off`
|
19
|
+
|
20
|
+
<details>
|
21
|
+
<summary>Turns off the alarm when it is on.</summary>
|
22
|
+
|
23
|
+
> Define `alarm is on` as Yes/No.
|
24
|
+
> Set `alarm is on` to _Yes_.
|
25
|
+
> **if** `alarm is on` **then**:
|
26
|
+
> >
|
27
|
+
> > **Set** `alarm is on` **to** _No_.
|
28
|
+
> > Say _"Alarm has been turned off"_.
|
29
|
+
|
30
|
+
</details>"""
|
31
|
+
|
32
|
+
parser = Parser()
|
33
|
+
program = parser.parse(source, check_semantics=False)
|
34
|
+
|
35
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
36
|
+
assert len(program.statements) == 1
|
37
|
+
|
38
|
+
interaction_stmt = program.statements[0]
|
39
|
+
assert isinstance(interaction_stmt, InteractionStatement)
|
40
|
+
assert interaction_stmt.name.value == "turn alarm off"
|
41
|
+
assert len(interaction_stmt.inputs) == 0
|
42
|
+
assert len(interaction_stmt.outputs) == 0
|
43
|
+
assert isinstance(interaction_stmt.body, BlockStatement)
|
44
|
+
|
45
|
+
# The body should contain define + set + if statement
|
46
|
+
assert len(interaction_stmt.body.statements) == 3
|
47
|
+
if_stmt = interaction_stmt.body.statements[2]
|
48
|
+
assert isinstance(if_stmt, IfStatement)
|
49
|
+
|
50
|
+
# The if statement should have a consequence block with 2 statements
|
51
|
+
assert isinstance(if_stmt.consequence, BlockStatement)
|
52
|
+
assert len(if_stmt.consequence.statements) == 2
|
53
|
+
|
54
|
+
def test_interaction_with_heading_level(self) -> None:
|
55
|
+
"""Test that interaction heading level (###) is parsed correctly."""
|
56
|
+
source = """### **Interaction**: `get status`
|
57
|
+
|
58
|
+
<details>
|
59
|
+
<summary>Returns the current status.</summary>
|
60
|
+
|
61
|
+
> Say _"Status: OK"_.
|
62
|
+
|
63
|
+
</details>"""
|
64
|
+
|
65
|
+
parser = Parser()
|
66
|
+
program = parser.parse(source, check_semantics=False)
|
67
|
+
|
68
|
+
assert len(parser.errors) == 0
|
69
|
+
assert len(program.statements) == 1
|
70
|
+
|
71
|
+
interaction_stmt = program.statements[0]
|
72
|
+
assert isinstance(interaction_stmt, InteractionStatement)
|
73
|
+
assert interaction_stmt.name.value == "get status"
|
74
|
+
|
75
|
+
def test_interaction_with_multi_word_name(self) -> None:
|
76
|
+
"""Test interaction with multi-word name in backticks."""
|
77
|
+
source = """### **Interaction**: `check system health`
|
78
|
+
|
79
|
+
<details>
|
80
|
+
<summary>Checks if the system is healthy.</summary>
|
81
|
+
|
82
|
+
> Define `health` as Text.
|
83
|
+
> Set `health` to _"Good"_.
|
84
|
+
> Say `health`.
|
85
|
+
|
86
|
+
</details>"""
|
87
|
+
|
88
|
+
parser = Parser()
|
89
|
+
program = parser.parse(source, check_semantics=False)
|
90
|
+
|
91
|
+
assert len(parser.errors) == 0
|
92
|
+
assert len(program.statements) == 1
|
93
|
+
|
94
|
+
interaction_stmt = program.statements[0]
|
95
|
+
assert isinstance(interaction_stmt, InteractionStatement)
|
96
|
+
assert interaction_stmt.name.value == "check system health"
|
97
|
+
|
98
|
+
def test_interaction_plural_form(self) -> None:
|
99
|
+
"""Test that 'Interactions' keyword also works."""
|
100
|
+
source = """### **Interactions**: `say hello`
|
101
|
+
|
102
|
+
<details>
|
103
|
+
<summary>Greets the user.</summary>
|
104
|
+
|
105
|
+
> Say _"Hello!"_.
|
106
|
+
|
107
|
+
</details>"""
|
108
|
+
|
109
|
+
parser = Parser()
|
110
|
+
program = parser.parse(source, check_semantics=False)
|
111
|
+
|
112
|
+
assert len(parser.errors) == 0
|
113
|
+
assert len(program.statements) == 1
|
114
|
+
|
115
|
+
interaction_stmt = program.statements[0]
|
116
|
+
assert isinstance(interaction_stmt, InteractionStatement)
|
117
|
+
assert interaction_stmt.name.value == "say hello"
|
118
|
+
|
119
|
+
def test_interaction_with_empty_body(self) -> None:
|
120
|
+
"""Test interaction with no statements in body."""
|
121
|
+
source = """### **Interaction**: `noop`
|
122
|
+
|
123
|
+
<details>
|
124
|
+
<summary>No operation.</summary>
|
125
|
+
|
126
|
+
</details>"""
|
127
|
+
|
128
|
+
parser = Parser()
|
129
|
+
program = parser.parse(source, check_semantics=False)
|
130
|
+
|
131
|
+
assert len(parser.errors) == 0
|
132
|
+
assert len(program.statements) == 1
|
133
|
+
|
134
|
+
interaction_stmt = program.statements[0]
|
135
|
+
assert isinstance(interaction_stmt, InteractionStatement)
|
136
|
+
assert interaction_stmt.name.value == "noop"
|
137
|
+
assert len(interaction_stmt.body.statements) == 0
|
138
|
+
|
139
|
+
def test_multiple_interactions(self) -> None:
|
140
|
+
"""Test parsing multiple interactions in one program."""
|
141
|
+
source = """### **Interaction**: `start process`
|
142
|
+
|
143
|
+
<details>
|
144
|
+
<summary>Starts the process.</summary>
|
145
|
+
|
146
|
+
> Define `running` as Yes/No.
|
147
|
+
> Set `running` to _Yes_.
|
148
|
+
|
149
|
+
</details>
|
150
|
+
|
151
|
+
### **Interaction**: `stop process`
|
152
|
+
|
153
|
+
<details>
|
154
|
+
<summary>Stops the process.</summary>
|
155
|
+
|
156
|
+
> Define `is_running` as Yes/No.
|
157
|
+
> Set `is_running` to _No_.
|
158
|
+
|
159
|
+
</details>"""
|
160
|
+
|
161
|
+
parser = Parser()
|
162
|
+
program = parser.parse(source, check_semantics=False)
|
163
|
+
|
164
|
+
assert len(parser.errors) == 0
|
165
|
+
assert len(program.statements) == 2
|
166
|
+
|
167
|
+
first_interaction = program.statements[0]
|
168
|
+
assert isinstance(first_interaction, InteractionStatement)
|
169
|
+
assert first_interaction.name.value == "start process"
|
170
|
+
|
171
|
+
second_interaction = program.statements[1]
|
172
|
+
assert isinstance(second_interaction, InteractionStatement)
|
173
|
+
assert second_interaction.name.value == "stop process"
|
174
|
+
|
175
|
+
def test_interaction_with_parameters(self) -> None:
|
176
|
+
"""Test parsing an interaction with input and output parameters."""
|
177
|
+
source = """### **Interaction**: `get user info`
|
178
|
+
|
179
|
+
<details>
|
180
|
+
<summary>Gets user information.</summary>
|
181
|
+
|
182
|
+
> Define `user` as Text.
|
183
|
+
> Define `age` as Number.
|
184
|
+
> Set `user` to _"John Doe"_.
|
185
|
+
> Set `age` to _25_.
|
186
|
+
> Give back `user`.
|
187
|
+
|
188
|
+
</details>
|
189
|
+
|
190
|
+
#### Inputs:
|
191
|
+
- `userId` **as** Text (required)
|
192
|
+
|
193
|
+
#### Outputs:
|
194
|
+
- `user` **as** Text
|
195
|
+
- `age` **as** Number"""
|
196
|
+
|
197
|
+
parser = Parser()
|
198
|
+
program = parser.parse(source, check_semantics=False)
|
199
|
+
|
200
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
201
|
+
assert len(program.statements) == 1
|
202
|
+
|
203
|
+
interaction_stmt = program.statements[0]
|
204
|
+
assert isinstance(interaction_stmt, InteractionStatement)
|
205
|
+
assert interaction_stmt.name.value == "get user info"
|
206
|
+
|
207
|
+
# Check inputs
|
208
|
+
assert len(interaction_stmt.inputs) == 1
|
209
|
+
input_param = interaction_stmt.inputs[0]
|
210
|
+
assert input_param.name.value == "userId"
|
211
|
+
assert input_param.type_name == "Text"
|
212
|
+
assert input_param.is_required is True
|
213
|
+
|
214
|
+
# Check outputs
|
215
|
+
assert len(interaction_stmt.outputs) == 2
|
216
|
+
|
217
|
+
user_param = interaction_stmt.outputs[0]
|
218
|
+
assert isinstance(user_param, Output)
|
219
|
+
assert user_param.name.value == "user"
|
220
|
+
assert user_param.type_name == "Text"
|
221
|
+
# All outputs have Empty as default when not specified
|
222
|
+
assert isinstance(user_param.default_value, EmptyLiteral)
|
223
|
+
|
224
|
+
age_param = interaction_stmt.outputs[1]
|
225
|
+
assert isinstance(age_param, Output)
|
226
|
+
assert age_param.name.value == "age"
|
227
|
+
assert age_param.type_name == "Number"
|
228
|
+
# All outputs have Empty as default when not specified
|
229
|
+
assert isinstance(age_param.default_value, EmptyLiteral)
|
230
|
+
|
231
|
+
def test_mixed_actions_and_interactions(self) -> None:
|
232
|
+
"""Test parsing both actions and interactions in same program."""
|
233
|
+
source = """### **Action**: `internal process`
|
234
|
+
|
235
|
+
<details>
|
236
|
+
<summary>Internal processing.</summary>
|
237
|
+
|
238
|
+
> Define `data` as Text.
|
239
|
+
> Set `data` to _"processed"_.
|
240
|
+
|
241
|
+
</details>
|
242
|
+
|
243
|
+
### **Interaction**: `get data`
|
244
|
+
|
245
|
+
<details>
|
246
|
+
<summary>Returns processed data.</summary>
|
247
|
+
|
248
|
+
> Define `current_data` as Text.
|
249
|
+
> Say `current_data`.
|
250
|
+
|
251
|
+
</details>"""
|
252
|
+
|
253
|
+
parser = Parser()
|
254
|
+
program = parser.parse(source, check_semantics=False)
|
255
|
+
|
256
|
+
assert len(parser.errors) == 0
|
257
|
+
assert len(program.statements) == 2
|
258
|
+
|
259
|
+
# First should be an Action
|
260
|
+
from machine_dialect.ast import ActionStatement
|
261
|
+
|
262
|
+
action = program.statements[0]
|
263
|
+
assert isinstance(action, ActionStatement)
|
264
|
+
assert action.name.value == "internal process"
|
265
|
+
|
266
|
+
# Second should be an Interaction
|
267
|
+
interaction = program.statements[1]
|
268
|
+
assert isinstance(interaction, InteractionStatement)
|
269
|
+
assert interaction.name.value == "get data"
|
@@ -0,0 +1,277 @@
|
|
1
|
+
"""Test parsing of list literals (unordered, ordered, and named)."""
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from machine_dialect.ast import (
|
6
|
+
FloatLiteral,
|
7
|
+
NamedListLiteral,
|
8
|
+
OrderedListLiteral,
|
9
|
+
SetStatement,
|
10
|
+
StringLiteral,
|
11
|
+
UnorderedListLiteral,
|
12
|
+
WholeNumberLiteral,
|
13
|
+
)
|
14
|
+
from machine_dialect.parser import Parser
|
15
|
+
|
16
|
+
|
17
|
+
class TestUnorderedLists:
|
18
|
+
"""Test parsing of unordered lists (dash-prefixed)."""
|
19
|
+
|
20
|
+
def test_simple_unordered_list(self) -> None:
|
21
|
+
"""Test parsing a simple unordered list."""
|
22
|
+
source = """
|
23
|
+
Define `fruits` as an unordered list.
|
24
|
+
Set `fruits` to:
|
25
|
+
- _"apple"_.
|
26
|
+
- _"banana"_.
|
27
|
+
- _"cherry"_.
|
28
|
+
"""
|
29
|
+
parser = Parser()
|
30
|
+
program = parser.parse(source)
|
31
|
+
|
32
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
33
|
+
assert len(program.statements) == 2 # Define and Set statements
|
34
|
+
|
35
|
+
set_stmt = program.statements[1] # Second statement is the Set
|
36
|
+
assert isinstance(set_stmt, SetStatement)
|
37
|
+
assert set_stmt.name and set_stmt.name.value == "fruits"
|
38
|
+
|
39
|
+
# Check the list literal
|
40
|
+
assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, UnorderedListLiteral)
|
41
|
+
list_literal = set_stmt.value
|
42
|
+
assert len(list_literal.elements) == 3
|
43
|
+
|
44
|
+
# Check elements
|
45
|
+
assert isinstance(list_literal.elements[0], StringLiteral)
|
46
|
+
assert list_literal.elements[0].value == "apple"
|
47
|
+
assert isinstance(list_literal.elements[1], StringLiteral)
|
48
|
+
assert list_literal.elements[1].value == "banana"
|
49
|
+
assert isinstance(list_literal.elements[2], StringLiteral)
|
50
|
+
assert list_literal.elements[2].value == "cherry"
|
51
|
+
|
52
|
+
def test_mixed_type_unordered_list(self) -> None:
|
53
|
+
"""Test parsing an unordered list with mixed types."""
|
54
|
+
source = """
|
55
|
+
Define `mixed` as unordered list.
|
56
|
+
Set `mixed` to:
|
57
|
+
- _"text"_.
|
58
|
+
- _42_.
|
59
|
+
- _3.14_.
|
60
|
+
"""
|
61
|
+
parser = Parser()
|
62
|
+
program = parser.parse(source)
|
63
|
+
|
64
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
65
|
+
assert len(program.statements) == 2
|
66
|
+
|
67
|
+
set_stmt = program.statements[1]
|
68
|
+
assert isinstance(set_stmt, SetStatement)
|
69
|
+
|
70
|
+
# Check the list literal
|
71
|
+
assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, UnorderedListLiteral)
|
72
|
+
list_literal = set_stmt.value
|
73
|
+
assert len(list_literal.elements) == 3
|
74
|
+
|
75
|
+
# Check mixed types
|
76
|
+
assert isinstance(list_literal.elements[0], StringLiteral)
|
77
|
+
assert list_literal.elements[0].value == "text"
|
78
|
+
assert isinstance(list_literal.elements[1], WholeNumberLiteral)
|
79
|
+
assert list_literal.elements[1].value == 42
|
80
|
+
assert isinstance(list_literal.elements[2], FloatLiteral)
|
81
|
+
assert list_literal.elements[2].value == 3.14
|
82
|
+
|
83
|
+
def test_list_with_negative_numbers(self) -> None:
|
84
|
+
"""Test that lists can contain negative numbers."""
|
85
|
+
source = """
|
86
|
+
Define `numbers` as unordered list.
|
87
|
+
Set `numbers` to:
|
88
|
+
- _-5_.
|
89
|
+
- _10_.
|
90
|
+
- _-3.14_.
|
91
|
+
"""
|
92
|
+
parser = Parser()
|
93
|
+
program = parser.parse(source)
|
94
|
+
|
95
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
96
|
+
assert len(program.statements) == 2
|
97
|
+
|
98
|
+
set_stmt = program.statements[1]
|
99
|
+
assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, UnorderedListLiteral)
|
100
|
+
list_literal = set_stmt.value
|
101
|
+
assert len(list_literal.elements) == 3
|
102
|
+
|
103
|
+
# Check negative numbers
|
104
|
+
assert isinstance(list_literal.elements[0], WholeNumberLiteral)
|
105
|
+
assert list_literal.elements[0].value == -5
|
106
|
+
assert isinstance(list_literal.elements[1], WholeNumberLiteral)
|
107
|
+
assert list_literal.elements[1].value == 10
|
108
|
+
assert isinstance(list_literal.elements[2], FloatLiteral)
|
109
|
+
assert list_literal.elements[2].value == -3.14
|
110
|
+
|
111
|
+
|
112
|
+
class TestOrderedLists:
|
113
|
+
"""Test parsing of ordered lists (numbered)."""
|
114
|
+
|
115
|
+
def test_simple_ordered_list(self) -> None:
|
116
|
+
"""Test parsing a simple ordered list."""
|
117
|
+
source = """
|
118
|
+
Define `steps` as ordered list.
|
119
|
+
Set `steps` to:
|
120
|
+
1. _"First step"_.
|
121
|
+
2. _"Second step"_.
|
122
|
+
3. _"Third step"_.
|
123
|
+
"""
|
124
|
+
parser = Parser()
|
125
|
+
program = parser.parse(source)
|
126
|
+
|
127
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
128
|
+
assert len(program.statements) == 2
|
129
|
+
|
130
|
+
set_stmt = program.statements[1]
|
131
|
+
assert isinstance(set_stmt, SetStatement)
|
132
|
+
assert set_stmt.name and set_stmt.name.value == "steps"
|
133
|
+
|
134
|
+
# Check the list literal
|
135
|
+
assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, OrderedListLiteral)
|
136
|
+
list_literal = set_stmt.value
|
137
|
+
assert len(list_literal.elements) == 3
|
138
|
+
|
139
|
+
# Check elements
|
140
|
+
assert isinstance(list_literal.elements[0], StringLiteral)
|
141
|
+
assert list_literal.elements[0].value == "First step"
|
142
|
+
assert isinstance(list_literal.elements[1], StringLiteral)
|
143
|
+
assert list_literal.elements[1].value == "Second step"
|
144
|
+
assert isinstance(list_literal.elements[2], StringLiteral)
|
145
|
+
assert list_literal.elements[2].value == "Third step"
|
146
|
+
|
147
|
+
def test_non_sequential_ordered_list(self) -> None:
|
148
|
+
"""Test that ordered lists can have non-sequential numbers."""
|
149
|
+
source = """
|
150
|
+
Define `priorities` as ordered list.
|
151
|
+
Set `priorities` to:
|
152
|
+
1. _"High priority"_.
|
153
|
+
5. _"Medium priority"_.
|
154
|
+
10. _"Low priority"_.
|
155
|
+
"""
|
156
|
+
parser = Parser()
|
157
|
+
program = parser.parse(source)
|
158
|
+
|
159
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
160
|
+
assert len(program.statements) == 2
|
161
|
+
|
162
|
+
set_stmt = program.statements[1]
|
163
|
+
assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, OrderedListLiteral)
|
164
|
+
list_literal = set_stmt.value
|
165
|
+
assert len(list_literal.elements) == 3
|
166
|
+
|
167
|
+
|
168
|
+
class TestNamedLists:
|
169
|
+
"""Test parsing of named lists (dictionaries)."""
|
170
|
+
|
171
|
+
def test_simple_named_list(self) -> None:
|
172
|
+
"""Test parsing a simple named list."""
|
173
|
+
source = """
|
174
|
+
Define `person` as named list.
|
175
|
+
Set `person` to:
|
176
|
+
- _"name"_: _"Alice"_.
|
177
|
+
- _"profession"_: _"Software Engineer"_.
|
178
|
+
- _"age"_: _30_.
|
179
|
+
"""
|
180
|
+
parser = Parser()
|
181
|
+
program = parser.parse(source)
|
182
|
+
|
183
|
+
assert len(parser.errors) == 0, f"Parser errors: {parser.errors}"
|
184
|
+
assert len(program.statements) == 2
|
185
|
+
|
186
|
+
set_stmt = program.statements[1]
|
187
|
+
assert isinstance(set_stmt, SetStatement)
|
188
|
+
assert set_stmt.name and set_stmt.name.value == "person"
|
189
|
+
|
190
|
+
# Check the list literal
|
191
|
+
assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, NamedListLiteral)
|
192
|
+
list_literal = set_stmt.value
|
193
|
+
assert len(list_literal.entries) == 3
|
194
|
+
|
195
|
+
# Check name-value pairs
|
196
|
+
assert list_literal.entries[0][0] == "name"
|
197
|
+
assert isinstance(list_literal.entries[0][1], StringLiteral)
|
198
|
+
assert list_literal.entries[0][1].value == "Alice"
|
199
|
+
|
200
|
+
assert list_literal.entries[1][0] == "profession"
|
201
|
+
assert isinstance(list_literal.entries[1][1], StringLiteral)
|
202
|
+
assert list_literal.entries[1][1].value == "Software Engineer"
|
203
|
+
|
204
|
+
assert list_literal.entries[2][0] == "age"
|
205
|
+
assert isinstance(list_literal.entries[2][1], WholeNumberLiteral)
|
206
|
+
assert list_literal.entries[2][1].value == 30
|
207
|
+
|
208
|
+
|
209
|
+
class TestListParsingErrors:
|
210
|
+
"""Test error handling in list parsing."""
|
211
|
+
|
212
|
+
@pytest.mark.parametrize(
|
213
|
+
"source",
|
214
|
+
[
|
215
|
+
"""
|
216
|
+
Define `mixed` as list.
|
217
|
+
Set `mixed` to:
|
218
|
+
- _"unordered item"_.
|
219
|
+
1. _"ordered item"_.
|
220
|
+
""",
|
221
|
+
"""
|
222
|
+
Define `mixed` as list.
|
223
|
+
Set `mixed` to:
|
224
|
+
1. _"unordered item"_.
|
225
|
+
- _"ordered item"_.
|
226
|
+
""",
|
227
|
+
],
|
228
|
+
)
|
229
|
+
def test_mixed_list_types_not_allowed(self, source: str) -> None:
|
230
|
+
"""Test that mixing list types is not allowed."""
|
231
|
+
parser = Parser()
|
232
|
+
program = parser.parse(source)
|
233
|
+
|
234
|
+
# The parser should handle mixed list types gracefully
|
235
|
+
# It may produce an error expression or a partial list
|
236
|
+
assert len(program.statements) >= 2 # At least Define and Set
|
237
|
+
|
238
|
+
set_stmt = program.statements[1]
|
239
|
+
assert isinstance(set_stmt, SetStatement)
|
240
|
+
|
241
|
+
# Check what the parser produced
|
242
|
+
if hasattr(set_stmt, "value"):
|
243
|
+
# If it's an ErrorExpression, that's acceptable
|
244
|
+
from machine_dialect.ast import ErrorExpression
|
245
|
+
|
246
|
+
if isinstance(set_stmt.value, ErrorExpression):
|
247
|
+
# Parser correctly identified an error
|
248
|
+
pass
|
249
|
+
elif isinstance(set_stmt.value, UnorderedListLiteral | OrderedListLiteral):
|
250
|
+
# Parser created a list with only the consistent items
|
251
|
+
list_literal = set_stmt.value
|
252
|
+
# Should have at most the first type's items
|
253
|
+
assert len(list_literal.elements) <= 1
|
254
|
+
|
255
|
+
# The main requirement is that the parser doesn't crash
|
256
|
+
# and produces some reasonable output
|
257
|
+
assert program is not None
|
258
|
+
|
259
|
+
def test_incomplete_named_list(self) -> None:
|
260
|
+
"""Test parsing incomplete named list elements."""
|
261
|
+
source = """
|
262
|
+
Define `incomplete` as named list.
|
263
|
+
Set `incomplete` to:
|
264
|
+
- "name": _"test"_.
|
265
|
+
"""
|
266
|
+
parser = Parser()
|
267
|
+
program = parser.parse(source)
|
268
|
+
|
269
|
+
# Should still create a named list
|
270
|
+
assert len(program.statements) == 2
|
271
|
+
set_stmt = program.statements[1]
|
272
|
+
assert isinstance(set_stmt, SetStatement)
|
273
|
+
|
274
|
+
# Should be a named list with error for missing content
|
275
|
+
assert hasattr(set_stmt, "value") and isinstance(set_stmt.value, NamedListLiteral)
|
276
|
+
list_literal = set_stmt.value
|
277
|
+
assert len(list_literal.entries) == 1
|