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,667 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""Machine Dialect™ CLI.
|
3
|
+
|
4
|
+
Main command-line interface for the Machine Dialect™ language.
|
5
|
+
Provides commands for compiling, running, and interacting with Machine Dialect™ programs.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import sys
|
9
|
+
from pathlib import Path
|
10
|
+
|
11
|
+
import click
|
12
|
+
|
13
|
+
from machine_dialect.compiler import Compiler, CompilerConfig
|
14
|
+
from machine_dialect.repl.repl import REPL
|
15
|
+
|
16
|
+
|
17
|
+
@click.group()
|
18
|
+
@click.version_option(version="0.1.0", prog_name="Machine Dialect™")
|
19
|
+
def cli() -> None:
|
20
|
+
"""Machine Dialect™ - Natural language programming in Markdown."""
|
21
|
+
pass
|
22
|
+
|
23
|
+
|
24
|
+
@cli.command()
|
25
|
+
@click.argument("source_file", type=click.Path(exists=True))
|
26
|
+
@click.option(
|
27
|
+
"-o",
|
28
|
+
"--output",
|
29
|
+
type=click.Path(),
|
30
|
+
help="Output file path (default: source.mdbc)",
|
31
|
+
)
|
32
|
+
@click.option(
|
33
|
+
"-d",
|
34
|
+
"--disassemble",
|
35
|
+
is_flag=True,
|
36
|
+
help="Show disassembly after compilation",
|
37
|
+
)
|
38
|
+
@click.option(
|
39
|
+
"-v",
|
40
|
+
"--verbose",
|
41
|
+
is_flag=True,
|
42
|
+
help="Show detailed compilation information",
|
43
|
+
)
|
44
|
+
@click.option(
|
45
|
+
"-m",
|
46
|
+
"--module-name",
|
47
|
+
type=str,
|
48
|
+
help="Name for the compiled module (default: source file name)",
|
49
|
+
)
|
50
|
+
@click.option(
|
51
|
+
"--dump-mir",
|
52
|
+
is_flag=True,
|
53
|
+
help="Dump MIR representation after generation",
|
54
|
+
)
|
55
|
+
@click.option(
|
56
|
+
"--show-cfg",
|
57
|
+
type=click.Path(),
|
58
|
+
help="Export control flow graph to DOT file",
|
59
|
+
)
|
60
|
+
@click.option(
|
61
|
+
"--opt-report",
|
62
|
+
is_flag=True,
|
63
|
+
help="Show optimization report after compilation",
|
64
|
+
)
|
65
|
+
@click.option(
|
66
|
+
"--mir-phase",
|
67
|
+
is_flag=True,
|
68
|
+
help="Stop after MIR generation (no bytecode)",
|
69
|
+
)
|
70
|
+
@click.option(
|
71
|
+
"--opt-level",
|
72
|
+
type=click.Choice(["0", "1", "2", "3"]),
|
73
|
+
default="2",
|
74
|
+
help="Optimization level (0=none, 3=aggressive)",
|
75
|
+
)
|
76
|
+
def compile(
|
77
|
+
source_file: str,
|
78
|
+
output: str | None,
|
79
|
+
disassemble: bool,
|
80
|
+
verbose: bool,
|
81
|
+
module_name: str | None,
|
82
|
+
dump_mir: bool,
|
83
|
+
show_cfg: str | None,
|
84
|
+
opt_report: bool,
|
85
|
+
mir_phase: bool,
|
86
|
+
opt_level: str,
|
87
|
+
) -> None:
|
88
|
+
"""Compile a Machine Dialect™ source file to bytecode."""
|
89
|
+
# Create compiler configuration from CLI options
|
90
|
+
config = CompilerConfig.from_cli_options(
|
91
|
+
opt_level=opt_level,
|
92
|
+
dump_mir=dump_mir,
|
93
|
+
show_cfg=show_cfg,
|
94
|
+
opt_report=opt_report,
|
95
|
+
verbose=verbose or disassemble,
|
96
|
+
mir_phase=mir_phase,
|
97
|
+
output=output,
|
98
|
+
module_name=module_name,
|
99
|
+
)
|
100
|
+
|
101
|
+
# Create compiler instance
|
102
|
+
compiler = Compiler(config)
|
103
|
+
|
104
|
+
# Compile the file
|
105
|
+
success = compiler.compile_file(source_file, output)
|
106
|
+
|
107
|
+
# Exit with appropriate code
|
108
|
+
sys.exit(0 if success else 1)
|
109
|
+
|
110
|
+
|
111
|
+
@cli.command()
|
112
|
+
@click.argument("file", type=click.Path(exists=True))
|
113
|
+
@click.option(
|
114
|
+
"-d",
|
115
|
+
"--debug",
|
116
|
+
is_flag=True,
|
117
|
+
help="Enable debug mode",
|
118
|
+
)
|
119
|
+
def run(file: str, debug: bool) -> None:
|
120
|
+
"""Run a Machine Dialect™ file (source .md or compiled .mdbc)."""
|
121
|
+
from pathlib import Path
|
122
|
+
|
123
|
+
file_path = Path(file)
|
124
|
+
extension = file_path.suffix.lower()
|
125
|
+
|
126
|
+
if extension == ".md":
|
127
|
+
# Run source file with MIR interpreter
|
128
|
+
_run_interpreted(file_path, debug)
|
129
|
+
elif extension == ".mdbc":
|
130
|
+
# Run compiled bytecode with Rust VM
|
131
|
+
_run_compiled(file, debug)
|
132
|
+
else:
|
133
|
+
click.echo(f"Error: Unsupported file type '{extension}'", err=True)
|
134
|
+
click.echo("Supported extensions: .md (source), .mdbc (compiled bytecode)", err=True)
|
135
|
+
sys.exit(1)
|
136
|
+
|
137
|
+
|
138
|
+
def _run_interpreted(source_path: Path, debug: bool) -> None:
|
139
|
+
"""Run source file with MIR interpreter."""
|
140
|
+
from machine_dialect.compiler.config import CompilerConfig
|
141
|
+
from machine_dialect.compiler.context import CompilationContext
|
142
|
+
from machine_dialect.compiler.phases.hir_generation import HIRGenerationPhase
|
143
|
+
from machine_dialect.compiler.phases.mir_generation import MIRGenerationPhase
|
144
|
+
from machine_dialect.mir.mir_interpreter import MIRInterpreter
|
145
|
+
from machine_dialect.parser.parser import Parser
|
146
|
+
|
147
|
+
try:
|
148
|
+
# Read source file
|
149
|
+
with open(source_path, encoding="utf-8") as f:
|
150
|
+
source = f.read()
|
151
|
+
|
152
|
+
if debug:
|
153
|
+
click.echo(f"Interpreting {source_path}...")
|
154
|
+
|
155
|
+
# Parse to AST
|
156
|
+
parser = Parser()
|
157
|
+
ast = parser.parse(source)
|
158
|
+
if ast is None:
|
159
|
+
# Check if parser has errors and display them
|
160
|
+
if parser.has_errors():
|
161
|
+
for error in parser.errors:
|
162
|
+
click.echo(f"Parse error: {error}", err=True)
|
163
|
+
else:
|
164
|
+
click.echo("Error: Failed to parse source", err=True)
|
165
|
+
sys.exit(1)
|
166
|
+
|
167
|
+
# Generate HIR
|
168
|
+
config = CompilerConfig(verbose=debug)
|
169
|
+
context = CompilationContext(source_path=source_path, config=config, source_content=source)
|
170
|
+
hir_phase = HIRGenerationPhase()
|
171
|
+
hir = hir_phase.run(context, ast)
|
172
|
+
if hir is None:
|
173
|
+
click.echo("Error: Failed to generate HIR", err=True)
|
174
|
+
sys.exit(1)
|
175
|
+
|
176
|
+
# Generate MIR
|
177
|
+
mir_phase = MIRGenerationPhase()
|
178
|
+
mir_module = mir_phase.run(context, hir)
|
179
|
+
if mir_module is None:
|
180
|
+
click.echo("Error: Failed to generate MIR", err=True)
|
181
|
+
sys.exit(1)
|
182
|
+
|
183
|
+
# TODO: Fix optimizer bug where it removes necessary variable stores in SSA form
|
184
|
+
# The optimizer incorrectly identifies stores like `n_minus_1.0 = t6` as dead code
|
185
|
+
# and removes them, but keeps the corresponding loads like `t10 = n_minus_1.0`,
|
186
|
+
# causing "Undefined variable" errors at runtime.
|
187
|
+
#
|
188
|
+
# Example of the bug:
|
189
|
+
# BEFORE optimization:
|
190
|
+
# n_minus_1.0 = t6 # This gets removed
|
191
|
+
# t10 = n_minus_1.0 # This stays, causing error
|
192
|
+
#
|
193
|
+
# Temporarily disable optimization until the dead code elimination pass
|
194
|
+
# is fixed to properly track SSA variable usage across basic blocks.
|
195
|
+
#
|
196
|
+
# To reproduce the bug, uncomment the line below and run:
|
197
|
+
# python -m machine_dialect run benchmark/fibonacci.md
|
198
|
+
#
|
199
|
+
# mir_module, _ = optimize_mir(mir_module, optimization_level=2)
|
200
|
+
|
201
|
+
# Interpret MIR
|
202
|
+
interpreter = MIRInterpreter()
|
203
|
+
result = interpreter.interpret_module(mir_module, trace=debug)
|
204
|
+
|
205
|
+
# Display result only in debug mode
|
206
|
+
if debug and result is not None:
|
207
|
+
click.echo(f"Result: {result}")
|
208
|
+
|
209
|
+
# Display output if any
|
210
|
+
output = interpreter.get_output()
|
211
|
+
if output:
|
212
|
+
for line in output:
|
213
|
+
click.echo(line)
|
214
|
+
|
215
|
+
except Exception as e:
|
216
|
+
click.echo(f"Runtime error: {e}", err=True)
|
217
|
+
if debug:
|
218
|
+
import traceback
|
219
|
+
|
220
|
+
traceback.print_exc()
|
221
|
+
sys.exit(1)
|
222
|
+
|
223
|
+
|
224
|
+
def _run_compiled(bytecode_file: str, debug: bool) -> None:
|
225
|
+
"""Run compiled bytecode with Rust VM."""
|
226
|
+
try:
|
227
|
+
# Import the Rust VM module
|
228
|
+
# Add the site-packages path to ensure we get the compiled module
|
229
|
+
import site
|
230
|
+
import sys as _sys
|
231
|
+
|
232
|
+
site_packages = site.getsitepackages()
|
233
|
+
for path in site_packages:
|
234
|
+
if path not in _sys.path:
|
235
|
+
_sys.path.insert(0, path)
|
236
|
+
|
237
|
+
import machine_dialect_vm
|
238
|
+
|
239
|
+
# Create VM instance
|
240
|
+
vm = machine_dialect_vm.RustVM()
|
241
|
+
|
242
|
+
# Enable debug mode if requested
|
243
|
+
if debug:
|
244
|
+
vm.set_debug(True)
|
245
|
+
click.echo("Debug mode enabled")
|
246
|
+
|
247
|
+
# Load and execute bytecode
|
248
|
+
if debug:
|
249
|
+
click.echo(f"Loading bytecode from: {bytecode_file}")
|
250
|
+
vm.load_bytecode(bytecode_file)
|
251
|
+
|
252
|
+
if debug:
|
253
|
+
click.echo("Executing bytecode...")
|
254
|
+
result = vm.execute()
|
255
|
+
|
256
|
+
# Display result only in debug mode
|
257
|
+
if debug and result is not None:
|
258
|
+
click.echo(f"Result: {result}")
|
259
|
+
|
260
|
+
if debug:
|
261
|
+
click.echo(f"Instructions executed: {vm.instruction_count()}")
|
262
|
+
|
263
|
+
except ImportError as e:
|
264
|
+
click.echo("Error: Rust VM module not found", err=True)
|
265
|
+
click.echo("Please build the Rust VM first:", err=True)
|
266
|
+
click.echo(" ./build_vm.sh", err=True)
|
267
|
+
click.echo(f"Details: {e}", err=True)
|
268
|
+
sys.exit(1)
|
269
|
+
except Exception as e:
|
270
|
+
click.echo(f"Runtime error: {e}", err=True)
|
271
|
+
sys.exit(1)
|
272
|
+
|
273
|
+
|
274
|
+
@cli.command()
|
275
|
+
@click.option(
|
276
|
+
"--tokens",
|
277
|
+
is_flag=True,
|
278
|
+
help="Run in token debug mode",
|
279
|
+
)
|
280
|
+
@click.option(
|
281
|
+
"--ast",
|
282
|
+
is_flag=True,
|
283
|
+
help="Run in AST mode (show AST instead of evaluating)",
|
284
|
+
)
|
285
|
+
def shell(tokens: bool, ast: bool) -> None:
|
286
|
+
"""Start an interactive Machine Dialect™ shell (REPL)."""
|
287
|
+
if tokens and ast:
|
288
|
+
click.echo("Error: --tokens and --ast flags are not compatible", err=True)
|
289
|
+
sys.exit(1)
|
290
|
+
repl = REPL(debug_tokens=tokens, show_ast=ast)
|
291
|
+
exit_code = repl.run()
|
292
|
+
sys.exit(exit_code)
|
293
|
+
|
294
|
+
|
295
|
+
@cli.command()
|
296
|
+
@click.argument("bytecode_file", type=click.Path(exists=True))
|
297
|
+
def disasm(bytecode_file: str) -> None:
|
298
|
+
"""Disassemble a compiled bytecode file."""
|
299
|
+
# TODO: Implement disassembly for new register-based bytecode
|
300
|
+
# The code below will be re-enabled once bytecode generation is implemented
|
301
|
+
# try:
|
302
|
+
# with open(bytecode_file, "rb") as f:
|
303
|
+
# module = deserialize_module(f)
|
304
|
+
# except FileNotFoundError:
|
305
|
+
# click.echo(f"Error: File '{bytecode_file}' not found", err=True)
|
306
|
+
# sys.exit(1)
|
307
|
+
# except InvalidMagicError as e:
|
308
|
+
# click.echo(f"Error: {e}", err=True)
|
309
|
+
# sys.exit(1)
|
310
|
+
# except SerializationError as e:
|
311
|
+
# click.echo(f"Error loading file: {e}", err=True)
|
312
|
+
# sys.exit(1)
|
313
|
+
# except Exception as e:
|
314
|
+
# click.echo(f"Error loading file: {e}", err=True)
|
315
|
+
# sys.exit(1)
|
316
|
+
|
317
|
+
click.echo("Disassembly not yet implemented for register-based bytecode", err=True)
|
318
|
+
sys.exit(1)
|
319
|
+
|
320
|
+
|
321
|
+
@cli.command()
|
322
|
+
@click.argument("task", nargs=-1, required=True)
|
323
|
+
@click.option(
|
324
|
+
"--iterations",
|
325
|
+
type=int,
|
326
|
+
default=5,
|
327
|
+
help="Maximum iterations to attempt (default: 5)",
|
328
|
+
)
|
329
|
+
@click.option(
|
330
|
+
"--api-key",
|
331
|
+
help="AI API key (or set in .mdconfig file or MD_AI_API_KEY env var)",
|
332
|
+
)
|
333
|
+
@click.option(
|
334
|
+
"--model",
|
335
|
+
help="AI model to use (e.g., gpt-5). If not specified, uses .mdconfig or env vars",
|
336
|
+
)
|
337
|
+
@click.option(
|
338
|
+
"-o",
|
339
|
+
"--output",
|
340
|
+
type=click.Path(),
|
341
|
+
help="Save final code to file",
|
342
|
+
)
|
343
|
+
@click.option(
|
344
|
+
"--save-history",
|
345
|
+
type=click.Path(),
|
346
|
+
help="Save iteration history to JSON file",
|
347
|
+
)
|
348
|
+
@click.option(
|
349
|
+
"-q",
|
350
|
+
"--quiet",
|
351
|
+
is_flag=True,
|
352
|
+
help="Minimal output",
|
353
|
+
)
|
354
|
+
def agent(
|
355
|
+
task: tuple[str, ...],
|
356
|
+
iterations: int,
|
357
|
+
api_key: str | None,
|
358
|
+
model: str | None,
|
359
|
+
output: str | None,
|
360
|
+
save_history: str | None,
|
361
|
+
quiet: bool,
|
362
|
+
) -> None:
|
363
|
+
"""Iteratively develop Machine Dialect™ code with AI assistance.
|
364
|
+
|
365
|
+
The agent will automatically generate, compile, execute, and fix code
|
366
|
+
until it successfully completes the task or reaches the iteration limit.
|
367
|
+
|
368
|
+
Examples:
|
369
|
+
machine-dialect agent "calculate the factorial of 5"
|
370
|
+
machine-dialect agent "sort a list of numbers" --iterations 3
|
371
|
+
machine-dialect agent "check if a number is prime" --output prime.md
|
372
|
+
"""
|
373
|
+
# Join task arguments
|
374
|
+
task_description = " ".join(task)
|
375
|
+
|
376
|
+
if not task_description.strip():
|
377
|
+
click.echo("Error: Task description cannot be empty", err=True)
|
378
|
+
sys.exit(1)
|
379
|
+
|
380
|
+
# Load configuration
|
381
|
+
from machine_dialect.cfg.config import ConfigLoader
|
382
|
+
|
383
|
+
loader = ConfigLoader()
|
384
|
+
config = loader.load()
|
385
|
+
|
386
|
+
# Override with command-line arguments if provided
|
387
|
+
if api_key:
|
388
|
+
config.key = api_key
|
389
|
+
if model:
|
390
|
+
config.model = model
|
391
|
+
|
392
|
+
# Check configuration
|
393
|
+
if not config.key:
|
394
|
+
click.echo("Error: No API key configured", err=True)
|
395
|
+
click.echo(loader.get_error_message(), err=True)
|
396
|
+
sys.exit(1)
|
397
|
+
|
398
|
+
if not config.model:
|
399
|
+
click.echo("Error: No AI model configured", err=True)
|
400
|
+
click.echo(loader.get_error_message(), err=True)
|
401
|
+
sys.exit(1)
|
402
|
+
|
403
|
+
try:
|
404
|
+
# Import OpenAI
|
405
|
+
try:
|
406
|
+
from openai import OpenAI
|
407
|
+
except ImportError:
|
408
|
+
click.echo("Error: OpenAI library not installed", err=True)
|
409
|
+
click.echo("Install with: pip install openai", err=True)
|
410
|
+
sys.exit(1)
|
411
|
+
|
412
|
+
# Initialize client
|
413
|
+
client = OpenAI(api_key=config.key)
|
414
|
+
|
415
|
+
# Create agent
|
416
|
+
from machine_dialect.agent import Agent
|
417
|
+
|
418
|
+
agent = Agent(client, config.model, verbose=not quiet)
|
419
|
+
|
420
|
+
# Solve the task
|
421
|
+
result = agent.solve(task_description, max_iterations=iterations)
|
422
|
+
|
423
|
+
# Display results
|
424
|
+
if not quiet:
|
425
|
+
click.echo("\n" + "=" * 60)
|
426
|
+
|
427
|
+
if result.success:
|
428
|
+
click.echo(f"✨ SUCCESS in {result.iterations} iteration(s)!")
|
429
|
+
if result.output:
|
430
|
+
click.echo(f"Output: {result.output}")
|
431
|
+
|
432
|
+
# Save code if requested
|
433
|
+
if output and result.code:
|
434
|
+
with open(output, "w") as f:
|
435
|
+
f.write(result.code)
|
436
|
+
click.echo(f"\nCode saved to: {output}")
|
437
|
+
elif not quiet:
|
438
|
+
click.echo("\nFinal code:")
|
439
|
+
click.echo("-" * 40)
|
440
|
+
click.echo(result.code)
|
441
|
+
click.echo("-" * 40)
|
442
|
+
|
443
|
+
else:
|
444
|
+
click.echo(f"❌ Failed after {result.iterations} iterations", err=True)
|
445
|
+
if not quiet:
|
446
|
+
click.echo("Try with more iterations or a clearer task description.", err=True)
|
447
|
+
|
448
|
+
# Save history if requested
|
449
|
+
if save_history and result.history:
|
450
|
+
import json
|
451
|
+
|
452
|
+
with open(save_history, "w") as f:
|
453
|
+
json.dump(result.history, f, indent=2)
|
454
|
+
click.echo(f"History saved to: {save_history}")
|
455
|
+
|
456
|
+
# Exit with appropriate code
|
457
|
+
sys.exit(0 if result.success else 1)
|
458
|
+
|
459
|
+
except Exception as e:
|
460
|
+
click.echo(f"Error: {e}", err=True)
|
461
|
+
if not quiet:
|
462
|
+
import traceback
|
463
|
+
|
464
|
+
traceback.print_exc()
|
465
|
+
sys.exit(1)
|
466
|
+
|
467
|
+
|
468
|
+
@cli.command()
|
469
|
+
@click.argument("task", nargs=-1, required=True)
|
470
|
+
@click.option(
|
471
|
+
"--api-key",
|
472
|
+
help="AI API key (or set in .mdconfig file or MD_AI_API_KEY env var)",
|
473
|
+
)
|
474
|
+
@click.option(
|
475
|
+
"--model",
|
476
|
+
help="AI model to use (e.g., gpt-5, gpt-5-mini). If not specified, uses .mdconfig or env vars",
|
477
|
+
)
|
478
|
+
@click.option(
|
479
|
+
"-t",
|
480
|
+
"--temperature",
|
481
|
+
type=float,
|
482
|
+
default=0.7,
|
483
|
+
help="Sampling temperature 0-2 (default: 0.7)",
|
484
|
+
)
|
485
|
+
@click.option(
|
486
|
+
"-m",
|
487
|
+
"--max-tokens",
|
488
|
+
type=int,
|
489
|
+
default=400,
|
490
|
+
help="Maximum tokens to generate (default: 400)",
|
491
|
+
)
|
492
|
+
@click.option(
|
493
|
+
"-o",
|
494
|
+
"--output",
|
495
|
+
type=click.Path(),
|
496
|
+
help="Save generated code to file",
|
497
|
+
)
|
498
|
+
@click.option(
|
499
|
+
"--validate/--no-validate",
|
500
|
+
default=True,
|
501
|
+
help="Validate generated code syntax (default: validate)",
|
502
|
+
)
|
503
|
+
@click.option(
|
504
|
+
"-v",
|
505
|
+
"--verbose",
|
506
|
+
is_flag=True,
|
507
|
+
help="Show detailed generation information",
|
508
|
+
)
|
509
|
+
def llm(
|
510
|
+
task: tuple[str, ...],
|
511
|
+
api_key: str | None,
|
512
|
+
model: str | None,
|
513
|
+
temperature: float,
|
514
|
+
max_tokens: int,
|
515
|
+
output: str | None,
|
516
|
+
validate: bool,
|
517
|
+
verbose: bool,
|
518
|
+
) -> None:
|
519
|
+
"""Generate Machine Dialect™ code using AI models.
|
520
|
+
|
521
|
+
Examples:
|
522
|
+
machine-dialect llm "calculate the factorial of 5"
|
523
|
+
machine-dialect llm "check if age 18 can vote" --output vote_check.md
|
524
|
+
machine-dialect llm "sum numbers from 1 to 10" -t 0.1 -m 500
|
525
|
+
|
526
|
+
Note: Escape backticks in your task description or use single quotes:
|
527
|
+
machine-dialect llm 'Sum `r` and `s`, being r=11 and s=45'
|
528
|
+
"""
|
529
|
+
# Join task arguments into a single string
|
530
|
+
task_description = " ".join(task)
|
531
|
+
|
532
|
+
if not task_description.strip():
|
533
|
+
click.echo("Error: Task description cannot be empty", err=True)
|
534
|
+
sys.exit(1)
|
535
|
+
|
536
|
+
# Load configuration
|
537
|
+
from machine_dialect.cfg.config import ConfigLoader
|
538
|
+
|
539
|
+
loader = ConfigLoader()
|
540
|
+
config = loader.load()
|
541
|
+
|
542
|
+
# Override with command-line arguments if provided
|
543
|
+
if api_key:
|
544
|
+
config.key = api_key
|
545
|
+
if model:
|
546
|
+
config.model = model
|
547
|
+
|
548
|
+
# Check if configuration is valid
|
549
|
+
if not config.key:
|
550
|
+
click.echo("Error: No API key configured", err=True)
|
551
|
+
click.echo(loader.get_error_message(), err=True)
|
552
|
+
sys.exit(1)
|
553
|
+
|
554
|
+
if not config.model:
|
555
|
+
click.echo("Error: No AI model configured", err=True)
|
556
|
+
click.echo(loader.get_error_message(), err=True)
|
557
|
+
sys.exit(1)
|
558
|
+
|
559
|
+
try:
|
560
|
+
# Import OpenAI and our generator
|
561
|
+
try:
|
562
|
+
from openai import OpenAI
|
563
|
+
except ImportError:
|
564
|
+
click.echo("Error: OpenAI library not installed", err=True)
|
565
|
+
click.echo("Install with: pip install openai", err=True)
|
566
|
+
sys.exit(1)
|
567
|
+
|
568
|
+
from machine_dialect.cfg import CFGParser
|
569
|
+
from machine_dialect.cfg.openai_generation import generate_with_openai
|
570
|
+
|
571
|
+
if verbose:
|
572
|
+
click.echo(f"Model: {config.model}")
|
573
|
+
click.echo(f"Task: {task_description}")
|
574
|
+
click.echo(f"Temperature: {temperature}")
|
575
|
+
click.echo(f"Max tokens: {max_tokens}")
|
576
|
+
click.echo("-" * 50)
|
577
|
+
|
578
|
+
# Initialize OpenAI client
|
579
|
+
client = OpenAI(api_key=config.key)
|
580
|
+
|
581
|
+
# Generate code
|
582
|
+
click.echo(f"Generating Machine Dialect™ code with {config.model}...")
|
583
|
+
|
584
|
+
try:
|
585
|
+
generated_code, _token_info = generate_with_openai(
|
586
|
+
client=client,
|
587
|
+
model=config.model,
|
588
|
+
task_description=task_description,
|
589
|
+
temperature=temperature,
|
590
|
+
max_tokens=max_tokens,
|
591
|
+
)
|
592
|
+
|
593
|
+
# Check what we got
|
594
|
+
if not generated_code:
|
595
|
+
click.echo("Error: Empty response from API", err=True)
|
596
|
+
sys.exit(1)
|
597
|
+
|
598
|
+
except Exception as e:
|
599
|
+
click.echo(f"Error: API call failed - {e}", err=True)
|
600
|
+
sys.exit(1)
|
601
|
+
|
602
|
+
# Only show output if we actually got code
|
603
|
+
click.echo("\n" + "=" * 50)
|
604
|
+
click.echo("Generated Code:")
|
605
|
+
click.echo("=" * 50)
|
606
|
+
click.echo(generated_code)
|
607
|
+
click.echo("=" * 50)
|
608
|
+
|
609
|
+
# Validate if requested
|
610
|
+
if validate:
|
611
|
+
click.echo("\nValidating syntax...")
|
612
|
+
parser = CFGParser()
|
613
|
+
|
614
|
+
try:
|
615
|
+
is_valid = parser.validate(generated_code)
|
616
|
+
if is_valid:
|
617
|
+
click.echo("✓ Generated code is syntactically valid!")
|
618
|
+
|
619
|
+
if verbose:
|
620
|
+
tree = parser.parse(generated_code)
|
621
|
+
click.echo("\nAST Preview:")
|
622
|
+
ast_str = parser.pretty_print(tree)
|
623
|
+
# Show first few lines
|
624
|
+
for line in ast_str.split("\n")[:8]:
|
625
|
+
click.echo(f" {line}")
|
626
|
+
if len(ast_str.split("\n")) > 8:
|
627
|
+
click.echo(" ...")
|
628
|
+
else:
|
629
|
+
click.echo("✗ Generated code has syntax errors", err=True)
|
630
|
+
except Exception as e:
|
631
|
+
click.echo(f"✗ Validation error: {e}", err=True)
|
632
|
+
|
633
|
+
# Save to file if requested
|
634
|
+
if output:
|
635
|
+
try:
|
636
|
+
with open(output, "w") as f:
|
637
|
+
f.write(generated_code)
|
638
|
+
click.echo(f"\n✓ Code saved to: {output}")
|
639
|
+
except Exception as e:
|
640
|
+
click.echo(f"Error saving file: {e}", err=True)
|
641
|
+
sys.exit(1)
|
642
|
+
|
643
|
+
if verbose:
|
644
|
+
click.echo("\n" + "-" * 50)
|
645
|
+
click.echo("Generation complete!")
|
646
|
+
click.echo("CFG ensures 100% syntactic correctness")
|
647
|
+
|
648
|
+
except ImportError as e:
|
649
|
+
click.echo(f"Error: Missing required module: {e}", err=True)
|
650
|
+
click.echo("Make sure Lark is installed: pip install lark", err=True)
|
651
|
+
sys.exit(1)
|
652
|
+
except Exception as e:
|
653
|
+
click.echo(f"Error: {e}", err=True)
|
654
|
+
if verbose:
|
655
|
+
import traceback
|
656
|
+
|
657
|
+
traceback.print_exc()
|
658
|
+
sys.exit(1)
|
659
|
+
|
660
|
+
|
661
|
+
def main() -> None:
|
662
|
+
"""Entry point for the CLI."""
|
663
|
+
cli()
|
664
|
+
|
665
|
+
|
666
|
+
if __name__ == "__main__":
|
667
|
+
main()
|