namel3ss 0.1.0a0__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.
Files changed (157) hide show
  1. namel3ss/__init__.py +4 -0
  2. namel3ss/ast/__init__.py +5 -0
  3. namel3ss/ast/agents.py +13 -0
  4. namel3ss/ast/ai.py +23 -0
  5. namel3ss/ast/base.py +10 -0
  6. namel3ss/ast/expressions.py +55 -0
  7. namel3ss/ast/nodes.py +86 -0
  8. namel3ss/ast/pages.py +43 -0
  9. namel3ss/ast/program.py +22 -0
  10. namel3ss/ast/records.py +27 -0
  11. namel3ss/ast/statements.py +107 -0
  12. namel3ss/ast/tool.py +11 -0
  13. namel3ss/cli/__init__.py +2 -0
  14. namel3ss/cli/actions_mode.py +39 -0
  15. namel3ss/cli/app_loader.py +22 -0
  16. namel3ss/cli/commands/action.py +27 -0
  17. namel3ss/cli/commands/run.py +43 -0
  18. namel3ss/cli/commands/ui.py +26 -0
  19. namel3ss/cli/commands/validate.py +23 -0
  20. namel3ss/cli/format_mode.py +30 -0
  21. namel3ss/cli/io/json_io.py +19 -0
  22. namel3ss/cli/io/read_source.py +16 -0
  23. namel3ss/cli/json_io.py +21 -0
  24. namel3ss/cli/lint_mode.py +29 -0
  25. namel3ss/cli/main.py +135 -0
  26. namel3ss/cli/new_mode.py +146 -0
  27. namel3ss/cli/runner.py +28 -0
  28. namel3ss/cli/studio_mode.py +22 -0
  29. namel3ss/cli/ui_mode.py +14 -0
  30. namel3ss/config/__init__.py +4 -0
  31. namel3ss/config/dotenv.py +33 -0
  32. namel3ss/config/loader.py +83 -0
  33. namel3ss/config/model.py +49 -0
  34. namel3ss/errors/__init__.py +2 -0
  35. namel3ss/errors/base.py +34 -0
  36. namel3ss/errors/render.py +22 -0
  37. namel3ss/format/__init__.py +3 -0
  38. namel3ss/format/formatter.py +18 -0
  39. namel3ss/format/rules.py +97 -0
  40. namel3ss/ir/__init__.py +3 -0
  41. namel3ss/ir/lowering/__init__.py +4 -0
  42. namel3ss/ir/lowering/agents.py +42 -0
  43. namel3ss/ir/lowering/ai.py +45 -0
  44. namel3ss/ir/lowering/expressions.py +49 -0
  45. namel3ss/ir/lowering/flow.py +21 -0
  46. namel3ss/ir/lowering/pages.py +48 -0
  47. namel3ss/ir/lowering/program.py +34 -0
  48. namel3ss/ir/lowering/records.py +25 -0
  49. namel3ss/ir/lowering/statements.py +122 -0
  50. namel3ss/ir/lowering/tools.py +16 -0
  51. namel3ss/ir/model/__init__.py +50 -0
  52. namel3ss/ir/model/agents.py +33 -0
  53. namel3ss/ir/model/ai.py +31 -0
  54. namel3ss/ir/model/base.py +20 -0
  55. namel3ss/ir/model/expressions.py +50 -0
  56. namel3ss/ir/model/pages.py +43 -0
  57. namel3ss/ir/model/program.py +28 -0
  58. namel3ss/ir/model/statements.py +76 -0
  59. namel3ss/ir/model/tools.py +11 -0
  60. namel3ss/ir/nodes.py +88 -0
  61. namel3ss/lexer/__init__.py +2 -0
  62. namel3ss/lexer/lexer.py +152 -0
  63. namel3ss/lexer/tokens.py +98 -0
  64. namel3ss/lint/__init__.py +4 -0
  65. namel3ss/lint/engine.py +125 -0
  66. namel3ss/lint/semantic.py +45 -0
  67. namel3ss/lint/text_scan.py +70 -0
  68. namel3ss/lint/types.py +22 -0
  69. namel3ss/parser/__init__.py +3 -0
  70. namel3ss/parser/agent.py +78 -0
  71. namel3ss/parser/ai.py +113 -0
  72. namel3ss/parser/constraints.py +37 -0
  73. namel3ss/parser/core.py +166 -0
  74. namel3ss/parser/expressions.py +105 -0
  75. namel3ss/parser/flow.py +37 -0
  76. namel3ss/parser/pages.py +76 -0
  77. namel3ss/parser/program.py +45 -0
  78. namel3ss/parser/records.py +66 -0
  79. namel3ss/parser/statements/__init__.py +27 -0
  80. namel3ss/parser/statements/control_flow.py +116 -0
  81. namel3ss/parser/statements/core.py +66 -0
  82. namel3ss/parser/statements/data.py +17 -0
  83. namel3ss/parser/statements/letset.py +22 -0
  84. namel3ss/parser/statements.py +1 -0
  85. namel3ss/parser/tokens.py +35 -0
  86. namel3ss/parser/tool.py +29 -0
  87. namel3ss/runtime/__init__.py +3 -0
  88. namel3ss/runtime/ai/http/client.py +24 -0
  89. namel3ss/runtime/ai/mock_provider.py +5 -0
  90. namel3ss/runtime/ai/provider.py +29 -0
  91. namel3ss/runtime/ai/providers/__init__.py +18 -0
  92. namel3ss/runtime/ai/providers/_shared/errors.py +20 -0
  93. namel3ss/runtime/ai/providers/_shared/parse.py +18 -0
  94. namel3ss/runtime/ai/providers/anthropic.py +55 -0
  95. namel3ss/runtime/ai/providers/gemini.py +50 -0
  96. namel3ss/runtime/ai/providers/mistral.py +51 -0
  97. namel3ss/runtime/ai/providers/mock.py +23 -0
  98. namel3ss/runtime/ai/providers/ollama.py +39 -0
  99. namel3ss/runtime/ai/providers/openai.py +55 -0
  100. namel3ss/runtime/ai/providers/registry.py +38 -0
  101. namel3ss/runtime/ai/trace.py +18 -0
  102. namel3ss/runtime/executor/__init__.py +3 -0
  103. namel3ss/runtime/executor/agents.py +91 -0
  104. namel3ss/runtime/executor/ai_runner.py +90 -0
  105. namel3ss/runtime/executor/api.py +54 -0
  106. namel3ss/runtime/executor/assign.py +40 -0
  107. namel3ss/runtime/executor/context.py +31 -0
  108. namel3ss/runtime/executor/executor.py +77 -0
  109. namel3ss/runtime/executor/expr_eval.py +110 -0
  110. namel3ss/runtime/executor/records_ops.py +64 -0
  111. namel3ss/runtime/executor/result.py +13 -0
  112. namel3ss/runtime/executor/signals.py +6 -0
  113. namel3ss/runtime/executor/statements.py +99 -0
  114. namel3ss/runtime/memory/manager.py +52 -0
  115. namel3ss/runtime/memory/profile.py +17 -0
  116. namel3ss/runtime/memory/semantic.py +20 -0
  117. namel3ss/runtime/memory/short_term.py +18 -0
  118. namel3ss/runtime/records/service.py +105 -0
  119. namel3ss/runtime/store/__init__.py +2 -0
  120. namel3ss/runtime/store/memory_store.py +62 -0
  121. namel3ss/runtime/tools/registry.py +13 -0
  122. namel3ss/runtime/ui/__init__.py +2 -0
  123. namel3ss/runtime/ui/actions.py +124 -0
  124. namel3ss/runtime/validators/__init__.py +2 -0
  125. namel3ss/runtime/validators/constraints.py +126 -0
  126. namel3ss/schema/__init__.py +2 -0
  127. namel3ss/schema/records.py +52 -0
  128. namel3ss/studio/__init__.py +4 -0
  129. namel3ss/studio/api.py +115 -0
  130. namel3ss/studio/edit/__init__.py +3 -0
  131. namel3ss/studio/edit/ops.py +80 -0
  132. namel3ss/studio/edit/selectors.py +74 -0
  133. namel3ss/studio/edit/transform.py +39 -0
  134. namel3ss/studio/server.py +175 -0
  135. namel3ss/studio/session.py +11 -0
  136. namel3ss/studio/web/app.js +248 -0
  137. namel3ss/studio/web/index.html +44 -0
  138. namel3ss/studio/web/styles.css +42 -0
  139. namel3ss/templates/__init__.py +3 -0
  140. namel3ss/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  141. namel3ss/templates/ai_assistant/.gitignore +1 -0
  142. namel3ss/templates/ai_assistant/README.md +10 -0
  143. namel3ss/templates/ai_assistant/app.ai +30 -0
  144. namel3ss/templates/crud/.gitignore +1 -0
  145. namel3ss/templates/crud/README.md +10 -0
  146. namel3ss/templates/crud/app.ai +26 -0
  147. namel3ss/templates/multi_agent/.gitignore +1 -0
  148. namel3ss/templates/multi_agent/README.md +10 -0
  149. namel3ss/templates/multi_agent/app.ai +43 -0
  150. namel3ss/ui/__init__.py +2 -0
  151. namel3ss/ui/manifest.py +220 -0
  152. namel3ss/utils/__init__.py +2 -0
  153. namel3ss-0.1.0a0.dist-info/METADATA +123 -0
  154. namel3ss-0.1.0a0.dist-info/RECORD +157 -0
  155. namel3ss-0.1.0a0.dist-info/WHEEL +5 -0
  156. namel3ss-0.1.0a0.dist-info/entry_points.txt +2 -0
  157. namel3ss-0.1.0a0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import List
5
+
6
+
7
+ def migrate_buttons(lines: List[str]) -> List[str]:
8
+ migrated: List[str] = []
9
+ pattern = re.compile(r'(\s*)button\s+"([^"]+)"\s+calls\s+flow\s+"([^"]+)"\s*$', re.IGNORECASE)
10
+ for line in lines:
11
+ m = pattern.match(line)
12
+ if m:
13
+ indent = m.group(1)
14
+ label = m.group(2)
15
+ flow = m.group(3)
16
+ migrated.append(f'{indent}button "{label}":')
17
+ migrated.append(f"{indent} calls flow \"{flow}\"")
18
+ continue
19
+ migrated.append(line)
20
+ return migrated
21
+
22
+
23
+ def normalize_spacing(line: str) -> str:
24
+ indent_len = len(line) - len(line.lstrip(" "))
25
+ indent = " " * indent_len
26
+ rest = line.strip()
27
+ if rest == "":
28
+ return ""
29
+
30
+ # headers with names
31
+ m = re.match(r'^(flow|page|record|ai|agent|tool)\s+"([^"]+)"\s*:?\s*$', rest)
32
+ if m:
33
+ return f'{indent}{m.group(1)} "{m.group(2)}":'
34
+
35
+ if rest.startswith("button "):
36
+ m = re.match(r'^button\s+"([^"]+)"\s*:$', rest)
37
+ if m:
38
+ return f'{indent}button "{m.group(1)}":'
39
+
40
+ # property with "is"
41
+ m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)\s+is\s+(.+)$', rest)
42
+ if m:
43
+ rest = f"{m.group(1)} is {m.group(2)}"
44
+
45
+ # ask ai pattern
46
+ m = re.match(
47
+ r'^ask\s+ai\s+"([^"]+)"\s+with\s+input\s*:?\s*(.+?)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$',
48
+ rest,
49
+ )
50
+ if m:
51
+ rest = f'ask ai "{m.group(1)}" with input: {m.group(2)} as {m.group(3)}'
52
+
53
+ # calls flow line
54
+ m = re.match(r'^calls\s+flow\s+"([^"]+)"\s*$', rest)
55
+ if m:
56
+ rest = f'calls flow "{m.group(1)}"'
57
+
58
+ rest = re.sub(r'\s+:', ":", rest)
59
+ return f"{indent}{rest}"
60
+
61
+
62
+ def normalize_indentation(lines: List[str]) -> List[str]:
63
+ result: List[str] = []
64
+ indent_stack = [0]
65
+ for line in lines:
66
+ if line.strip() == "":
67
+ result.append("")
68
+ continue
69
+ leading = len(line) - len(line.lstrip(" "))
70
+ if leading > indent_stack[-1]:
71
+ indent_stack.append(leading)
72
+ else:
73
+ while indent_stack and leading < indent_stack[-1]:
74
+ indent_stack.pop()
75
+ if leading != indent_stack[-1]:
76
+ indent_stack.append(leading)
77
+ depth = max(0, len(indent_stack) - 1)
78
+ content = line.lstrip(" ")
79
+ result.append(" " * depth + content)
80
+ return result
81
+
82
+
83
+ def collapse_blank_lines(lines: List[str]) -> List[str]:
84
+ cleaned: List[str] = []
85
+ for line in lines:
86
+ if line.strip() == "":
87
+ if cleaned and cleaned[-1] == "":
88
+ continue
89
+ cleaned.append("")
90
+ else:
91
+ cleaned.append(line)
92
+ # trim leading/trailing blanks
93
+ while cleaned and cleaned[0] == "":
94
+ cleaned.pop(0)
95
+ while cleaned and cleaned[-1] == "":
96
+ cleaned.pop()
97
+ return cleaned
@@ -0,0 +1,3 @@
1
+ """Intermediate representation for Namel3ss."""
2
+
3
+ from namel3ss.ir.nodes import * # noqa: F401,F403
@@ -0,0 +1,4 @@
1
+ from namel3ss.ir.lowering.program import lower_program
2
+ from namel3ss.ir.lowering.flow import lower_flow
3
+
4
+ __all__ = ["lower_program", "lower_flow"]
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+ from namel3ss.ast import nodes as ast
6
+ from namel3ss.errors.base import Namel3ssError
7
+ from namel3ss.ir.model.agents import AgentDecl, ParallelAgentEntry, RunAgentStmt, RunAgentsParallelStmt
8
+ from namel3ss.ir.model.ai import AIDecl
9
+
10
+
11
+ def _lower_agents(agents: List[ast.AgentDecl], ais: Dict[str, AIDecl]) -> Dict[str, AgentDecl]:
12
+ agent_map: Dict[str, AgentDecl] = {}
13
+ for agent in agents:
14
+ if agent.name in agent_map:
15
+ raise Namel3ssError(f"Duplicate agent declaration '{agent.name}'", line=agent.line, column=agent.column)
16
+ if agent.ai_name not in ais:
17
+ raise Namel3ssError(f"Agent '{agent.name}' references unknown AI '{agent.ai_name}'", line=agent.line, column=agent.column)
18
+ agent_map[agent.name] = AgentDecl(
19
+ name=agent.name,
20
+ ai_name=agent.ai_name,
21
+ system_prompt=agent.system_prompt,
22
+ line=agent.line,
23
+ column=agent.column,
24
+ )
25
+ return agent_map
26
+
27
+
28
+ def _validate_agent_reference(agent_name: str, agents: Dict[str, AgentDecl], line, column) -> None:
29
+ if agent_name not in agents:
30
+ raise Namel3ssError(f"Unknown agent '{agent_name}'", line=line, column=column)
31
+
32
+
33
+ def validate_agent_statement(stmt: RunAgentStmt | RunAgentsParallelStmt, agents: Dict[str, AgentDecl]) -> None:
34
+ if isinstance(stmt, RunAgentStmt):
35
+ _validate_agent_reference(stmt.agent_name, agents, stmt.line, stmt.column)
36
+ return
37
+ if isinstance(stmt, RunAgentsParallelStmt):
38
+ if not stmt.entries:
39
+ raise Namel3ssError("Parallel agent block requires at least one entry", line=stmt.line, column=stmt.column)
40
+ for entry in stmt.entries:
41
+ _validate_agent_reference(entry.agent_name, agents, entry.line, entry.column)
42
+ return
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+ from namel3ss.ast import nodes as ast
6
+ from namel3ss.errors.base import Namel3ssError
7
+ from namel3ss.ir.model.ai import AIDecl, AIMemory
8
+ from namel3ss.ir.model.tools import ToolDecl
9
+ from namel3ss.runtime.ai.providers.registry import is_supported_provider
10
+
11
+
12
+ def _lower_ai_decls(ais: List[ast.AIDecl], tools: Dict[str, ToolDecl]) -> Dict[str, AIDecl]:
13
+ ai_map: Dict[str, AIDecl] = {}
14
+ for ai in ais:
15
+ if ai.name in ai_map:
16
+ raise Namel3ssError(f"Duplicate AI declaration '{ai.name}'", line=ai.line, column=ai.column)
17
+ if not ai.model:
18
+ raise Namel3ssError(f"AI '{ai.name}' must specify a model", line=ai.line, column=ai.column)
19
+ provider = (ai.provider or "mock").lower()
20
+ if not is_supported_provider(provider):
21
+ raise Namel3ssError(f"Unknown AI provider '{provider}'", line=ai.line, column=ai.column)
22
+ exposed: List[str] = []
23
+ for tool in ai.exposed_tools:
24
+ if tool not in tools:
25
+ raise Namel3ssError(f"AI '{ai.name}' exposes unknown tool '{tool}'", line=ai.line, column=ai.column)
26
+ if tool in exposed:
27
+ raise Namel3ssError(f"Duplicate tool exposure '{tool}' in AI '{ai.name}'", line=ai.line, column=ai.column)
28
+ exposed.append(tool)
29
+ ai_map[ai.name] = AIDecl(
30
+ name=ai.name,
31
+ model=ai.model,
32
+ provider=provider,
33
+ system_prompt=ai.system_prompt,
34
+ exposed_tools=exposed,
35
+ memory=AIMemory(
36
+ short_term=ai.memory.short_term,
37
+ semantic=ai.memory.semantic,
38
+ profile=ai.memory.profile,
39
+ line=ai.memory.line,
40
+ column=ai.memory.column,
41
+ ),
42
+ line=ai.line,
43
+ column=ai.column,
44
+ )
45
+ return ai_map
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+ from namel3ss.ir.model.expressions import AttrAccess, BinaryOp, Comparison, Literal, StatePath, UnaryOp, VarReference, Assignable
5
+
6
+
7
+ def _lower_assignable(expr: ast.Assignable) -> Assignable:
8
+ if isinstance(expr, ast.VarReference):
9
+ return VarReference(name=expr.name, line=expr.line, column=expr.column)
10
+ if isinstance(expr, ast.StatePath):
11
+ return StatePath(path=list(expr.path), line=expr.line, column=expr.column)
12
+ raise TypeError(f"Unhandled assignable type: {type(expr)}")
13
+
14
+
15
+ def _lower_expression(expr: ast.Expression | None):
16
+ if expr is None:
17
+ return None
18
+ if isinstance(expr, ast.Literal):
19
+ return Literal(value=expr.value, line=expr.line, column=expr.column)
20
+ if isinstance(expr, ast.VarReference):
21
+ return VarReference(name=expr.name, line=expr.line, column=expr.column)
22
+ if isinstance(expr, ast.AttrAccess):
23
+ return AttrAccess(base=expr.base, attrs=list(expr.attrs), line=expr.line, column=expr.column)
24
+ if isinstance(expr, ast.StatePath):
25
+ return StatePath(path=list(expr.path), line=expr.line, column=expr.column)
26
+ if isinstance(expr, ast.UnaryOp):
27
+ return UnaryOp(
28
+ op=expr.op,
29
+ operand=_lower_expression(expr.operand),
30
+ line=expr.line,
31
+ column=expr.column,
32
+ )
33
+ if isinstance(expr, ast.BinaryOp):
34
+ return BinaryOp(
35
+ op=expr.op,
36
+ left=_lower_expression(expr.left),
37
+ right=_lower_expression(expr.right),
38
+ line=expr.line,
39
+ column=expr.column,
40
+ )
41
+ if isinstance(expr, ast.Comparison):
42
+ return Comparison(
43
+ kind=expr.kind,
44
+ left=_lower_expression(expr.left),
45
+ right=_lower_expression(expr.right),
46
+ line=expr.line,
47
+ column=expr.column,
48
+ )
49
+ raise TypeError(f"Unhandled expression type: {type(expr)}")
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+ from namel3ss.ir.lowering.statements import _lower_statement
5
+ from namel3ss.ir.model.agents import AgentDecl
6
+ from namel3ss.ir.model.program import Flow
7
+ from namel3ss.ir.lowering.agents import validate_agent_statement
8
+
9
+
10
+ def lower_flow(flow: ast.Flow, agents: dict[str, AgentDecl]) -> Flow:
11
+ ir_body = []
12
+ for stmt in flow.body:
13
+ lowered = _lower_statement(stmt, agents)
14
+ validate_agent_statement(lowered, agents)
15
+ ir_body.append(lowered)
16
+ return Flow(
17
+ name=flow.name,
18
+ body=ir_body,
19
+ line=flow.line,
20
+ column=flow.column,
21
+ )
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+ from namel3ss.errors.base import Namel3ssError
5
+ from namel3ss.ir.model.pages import ButtonItem, FormItem, Page, PageItem, TableItem, TextItem, TitleItem
6
+ from namel3ss.schema import records as schema
7
+
8
+
9
+ def _lower_page(page: ast.PageDecl, record_map: dict[str, schema.RecordSchema], flow_names: set[str]) -> Page:
10
+ items = [_lower_page_item(item, record_map, flow_names, page.name) for item in page.items]
11
+ return Page(name=page.name, items=items, line=page.line, column=page.column)
12
+
13
+
14
+ def _lower_page_item(
15
+ item: ast.PageItem,
16
+ record_map: dict[str, schema.RecordSchema],
17
+ flow_names: set[str],
18
+ page_name: str,
19
+ ) -> PageItem:
20
+ if isinstance(item, ast.TitleItem):
21
+ return TitleItem(value=item.value, line=item.line, column=item.column)
22
+ if isinstance(item, ast.TextItem):
23
+ return TextItem(value=item.value, line=item.line, column=item.column)
24
+ if isinstance(item, ast.FormItem):
25
+ if item.record_name not in record_map:
26
+ raise Namel3ssError(
27
+ f"Page '{page_name}' references unknown record '{item.record_name}'",
28
+ line=item.line,
29
+ column=item.column,
30
+ )
31
+ return FormItem(record_name=item.record_name, line=item.line, column=item.column)
32
+ if isinstance(item, ast.TableItem):
33
+ if item.record_name not in record_map:
34
+ raise Namel3ssError(
35
+ f"Page '{page_name}' references unknown record '{item.record_name}'",
36
+ line=item.line,
37
+ column=item.column,
38
+ )
39
+ return TableItem(record_name=item.record_name, line=item.line, column=item.column)
40
+ if isinstance(item, ast.ButtonItem):
41
+ if item.flow_name not in flow_names:
42
+ raise Namel3ssError(
43
+ f"Page '{page_name}' references unknown flow '{item.flow_name}'",
44
+ line=item.line,
45
+ column=item.column,
46
+ )
47
+ return ButtonItem(label=item.label, flow_name=item.flow_name, line=item.line, column=item.column)
48
+ raise TypeError(f"Unhandled page item type: {type(item)}")
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+ from namel3ss.ast import nodes as ast
6
+ from namel3ss.ir.lowering.agents import _lower_agents
7
+ from namel3ss.ir.lowering.ai import _lower_ai_decls
8
+ from namel3ss.ir.lowering.flow import lower_flow
9
+ from namel3ss.ir.lowering.pages import _lower_page
10
+ from namel3ss.ir.lowering.records import _lower_record
11
+ from namel3ss.ir.lowering.tools import _lower_tools
12
+ from namel3ss.ir.model.program import Flow, Program
13
+ from namel3ss.schema import records as schema
14
+
15
+
16
+ def lower_program(program: ast.Program) -> Program:
17
+ record_schemas = [_lower_record(record) for record in program.records]
18
+ tool_map = _lower_tools(program.tools)
19
+ ai_map = _lower_ai_decls(program.ais, tool_map)
20
+ agent_map = _lower_agents(program.agents, ai_map)
21
+ flow_irs: List[Flow] = [lower_flow(flow, agent_map) for flow in program.flows]
22
+ record_map: Dict[str, schema.RecordSchema] = {rec.name: rec for rec in record_schemas}
23
+ flow_names = {flow.name for flow in flow_irs}
24
+ pages = [_lower_page(page, record_map, flow_names) for page in program.pages]
25
+ return Program(
26
+ records=record_schemas,
27
+ flows=flow_irs,
28
+ pages=pages,
29
+ ais=ai_map,
30
+ tools=tool_map,
31
+ agents=agent_map,
32
+ line=program.line,
33
+ column=program.column,
34
+ )
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+ from namel3ss.ir.lowering.expressions import _lower_expression
5
+ from namel3ss.schema import records as schema
6
+
7
+
8
+ def _lower_record(record: ast.RecordDecl) -> schema.RecordSchema:
9
+ fields = []
10
+ for field in record.fields:
11
+ constraint = None
12
+ if field.constraint:
13
+ constraint = schema.FieldConstraint(
14
+ kind=field.constraint.kind,
15
+ expression=_lower_expression(field.constraint.expression) if field.constraint.expression else None,
16
+ pattern=field.constraint.pattern,
17
+ )
18
+ fields.append(
19
+ schema.FieldSchema(
20
+ name=field.name,
21
+ type_name=field.type_name,
22
+ constraint=constraint,
23
+ )
24
+ )
25
+ return schema.RecordSchema(name=record.name, fields=fields)
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+ from namel3ss.ir.lowering.expressions import _lower_assignable, _lower_expression
5
+ from namel3ss.ir.model.agents import ParallelAgentEntry, RunAgentStmt, RunAgentsParallelStmt
6
+ from namel3ss.ir.model.ai import AskAIStmt
7
+ from namel3ss.ir.model.statements import (
8
+ Find,
9
+ ForEach,
10
+ If,
11
+ Let,
12
+ Match,
13
+ MatchCase,
14
+ Repeat,
15
+ Return,
16
+ Save,
17
+ Set,
18
+ TryCatch,
19
+ )
20
+ from namel3ss.ir.model.statements import Statement as IRStatement
21
+
22
+
23
+ def _lower_statement(stmt: ast.Statement, agents) -> IRStatement:
24
+ if isinstance(stmt, ast.Let):
25
+ return Let(
26
+ name=stmt.name,
27
+ expression=_lower_expression(stmt.expression),
28
+ constant=stmt.constant,
29
+ line=stmt.line,
30
+ column=stmt.column,
31
+ )
32
+ if isinstance(stmt, ast.Set):
33
+ return Set(
34
+ target=_lower_assignable(stmt.target),
35
+ expression=_lower_expression(stmt.expression),
36
+ line=stmt.line,
37
+ column=stmt.column,
38
+ )
39
+ if isinstance(stmt, ast.If):
40
+ return If(
41
+ condition=_lower_expression(stmt.condition),
42
+ then_body=[_lower_statement(s, agents) for s in stmt.then_body],
43
+ else_body=[_lower_statement(s, agents) for s in stmt.else_body],
44
+ line=stmt.line,
45
+ column=stmt.column,
46
+ )
47
+ if isinstance(stmt, ast.Return):
48
+ return Return(
49
+ expression=_lower_expression(stmt.expression),
50
+ line=stmt.line,
51
+ column=stmt.column,
52
+ )
53
+ if isinstance(stmt, ast.Repeat):
54
+ return Repeat(
55
+ count=_lower_expression(stmt.count),
56
+ body=[_lower_statement(s, agents) for s in stmt.body],
57
+ line=stmt.line,
58
+ column=stmt.column,
59
+ )
60
+ if isinstance(stmt, ast.ForEach):
61
+ return ForEach(
62
+ name=stmt.name,
63
+ iterable=_lower_expression(stmt.iterable),
64
+ body=[_lower_statement(s, agents) for s in stmt.body],
65
+ line=stmt.line,
66
+ column=stmt.column,
67
+ )
68
+ if isinstance(stmt, ast.AskAIStmt):
69
+ return AskAIStmt(
70
+ ai_name=stmt.ai_name,
71
+ input_expr=_lower_expression(stmt.input_expr),
72
+ target=stmt.target,
73
+ line=stmt.line,
74
+ column=stmt.column,
75
+ )
76
+ if isinstance(stmt, ast.Match):
77
+ return Match(
78
+ expression=_lower_expression(stmt.expression),
79
+ cases=[
80
+ MatchCase(
81
+ pattern=_lower_expression(case.pattern),
82
+ body=[_lower_statement(s, agents) for s in case.body],
83
+ line=case.line,
84
+ column=case.column,
85
+ )
86
+ for case in stmt.cases
87
+ ],
88
+ otherwise=[_lower_statement(s, agents) for s in stmt.otherwise] if stmt.otherwise is not None else None,
89
+ line=stmt.line,
90
+ column=stmt.column,
91
+ )
92
+ if isinstance(stmt, ast.TryCatch):
93
+ return TryCatch(
94
+ try_body=[_lower_statement(s, agents) for s in stmt.try_body],
95
+ catch_var=stmt.catch_var,
96
+ catch_body=[_lower_statement(s, agents) for s in stmt.catch_body],
97
+ line=stmt.line,
98
+ column=stmt.column,
99
+ )
100
+ if isinstance(stmt, ast.Save):
101
+ return Save(record_name=stmt.record_name, line=stmt.line, column=stmt.column)
102
+ if isinstance(stmt, ast.Find):
103
+ return Find(record_name=stmt.record_name, predicate=_lower_expression(stmt.predicate), line=stmt.line, column=stmt.column)
104
+ if isinstance(stmt, ast.RunAgentStmt):
105
+ return RunAgentStmt(
106
+ agent_name=stmt.agent_name,
107
+ input_expr=_lower_expression(stmt.input_expr),
108
+ target=stmt.target,
109
+ line=stmt.line,
110
+ column=stmt.column,
111
+ )
112
+ if isinstance(stmt, ast.RunAgentsParallelStmt):
113
+ return RunAgentsParallelStmt(
114
+ entries=[
115
+ ParallelAgentEntry(agent_name=e.agent_name, input_expr=_lower_expression(e.input_expr), line=e.line, column=e.column)
116
+ for e in stmt.entries
117
+ ],
118
+ target=stmt.target,
119
+ line=stmt.line,
120
+ column=stmt.column,
121
+ )
122
+ raise TypeError(f"Unhandled statement type: {type(stmt)}")
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+ from namel3ss.ast import nodes as ast
6
+ from namel3ss.errors.base import Namel3ssError
7
+ from namel3ss.ir.model.tools import ToolDecl
8
+
9
+
10
+ def _lower_tools(tools: List[ast.ToolDecl]) -> Dict[str, ToolDecl]:
11
+ tool_map: Dict[str, ToolDecl] = {}
12
+ for tool in tools:
13
+ if tool.name in tool_map:
14
+ raise Namel3ssError(f"Duplicate tool declaration '{tool.name}'", line=tool.line, column=tool.column)
15
+ tool_map[tool.name] = ToolDecl(name=tool.name, kind=tool.kind, line=tool.line, column=tool.column)
16
+ return tool_map
@@ -0,0 +1,50 @@
1
+ from namel3ss.ir.model.base import Expression, Node, Statement
2
+ from namel3ss.ir.model.program import Flow, Program
3
+ from namel3ss.ir.model.statements import ForEach, If, Let, Match, MatchCase, Repeat, Return, Save, Set, TryCatch, Find
4
+ from namel3ss.ir.model.expressions import AttrAccess, BinaryOp, Comparison, Literal, StatePath, UnaryOp, VarReference, Assignable
5
+ from namel3ss.ir.model.ai import AIDecl, AIMemory, AskAIStmt
6
+ from namel3ss.ir.model.agents import AgentDecl, ParallelAgentEntry, RunAgentStmt, RunAgentsParallelStmt
7
+ from namel3ss.ir.model.pages import ButtonItem, FormItem, Page, PageItem, TableItem, TextItem, TitleItem
8
+ from namel3ss.ir.model.tools import ToolDecl
9
+
10
+ __all__ = [
11
+ "Expression",
12
+ "Node",
13
+ "Statement",
14
+ "Flow",
15
+ "Program",
16
+ "ForEach",
17
+ "If",
18
+ "Let",
19
+ "Match",
20
+ "MatchCase",
21
+ "Repeat",
22
+ "Return",
23
+ "Save",
24
+ "Set",
25
+ "TryCatch",
26
+ "Find",
27
+ "AttrAccess",
28
+ "BinaryOp",
29
+ "Comparison",
30
+ "Literal",
31
+ "StatePath",
32
+ "UnaryOp",
33
+ "VarReference",
34
+ "Assignable",
35
+ "AIDecl",
36
+ "AIMemory",
37
+ "AskAIStmt",
38
+ "AgentDecl",
39
+ "ParallelAgentEntry",
40
+ "RunAgentStmt",
41
+ "RunAgentsParallelStmt",
42
+ "ButtonItem",
43
+ "FormItem",
44
+ "Page",
45
+ "PageItem",
46
+ "TableItem",
47
+ "TextItem",
48
+ "TitleItem",
49
+ "ToolDecl",
50
+ ]
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import List, Optional
5
+
6
+ from namel3ss.ir.model.base import Node, Statement
7
+ from namel3ss.ir.model.expressions import Expression
8
+
9
+
10
+ @dataclass
11
+ class AgentDecl(Node):
12
+ name: str
13
+ ai_name: str
14
+ system_prompt: Optional[str]
15
+
16
+
17
+ @dataclass
18
+ class ParallelAgentEntry(Node):
19
+ agent_name: str
20
+ input_expr: Expression
21
+
22
+
23
+ @dataclass
24
+ class RunAgentStmt(Statement):
25
+ agent_name: str
26
+ input_expr: Expression
27
+ target: str
28
+
29
+
30
+ @dataclass
31
+ class RunAgentsParallelStmt(Statement):
32
+ entries: List[ParallelAgentEntry]
33
+ target: str
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import List, Optional
5
+
6
+ from namel3ss.ir.model.base import Node, Statement
7
+ from namel3ss.ir.model.expressions import Expression
8
+
9
+
10
+ @dataclass
11
+ class AIMemory(Node):
12
+ short_term: int = 0
13
+ semantic: bool = False
14
+ profile: bool = False
15
+
16
+
17
+ @dataclass
18
+ class AIDecl(Node):
19
+ name: str
20
+ model: str
21
+ provider: str
22
+ system_prompt: Optional[str]
23
+ exposed_tools: List[str]
24
+ memory: AIMemory
25
+
26
+
27
+ @dataclass
28
+ class AskAIStmt(Statement):
29
+ ai_name: str
30
+ input_expr: Expression
31
+ target: str
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+
7
+ @dataclass
8
+ class Node:
9
+ line: Optional[int]
10
+ column: Optional[int]
11
+
12
+
13
+ @dataclass
14
+ class Statement(Node):
15
+ pass
16
+
17
+
18
+ @dataclass
19
+ class Expression(Node):
20
+ pass