dirac-browser 0.1.1
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.
- package/README.md +88 -0
- package/dist/chatbox.d.ts +1 -0
- package/dist/chatbox.js +81 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +39 -0
- package/dist/runtime/braket-parser.d.ts +44 -0
- package/dist/runtime/braket-parser.js +208 -0
- package/dist/runtime/interpreter.d.ts +6 -0
- package/dist/runtime/interpreter.js +110 -0
- package/dist/runtime/parser.d.ts +11 -0
- package/dist/runtime/parser.js +135 -0
- package/dist/runtime/session.d.ts +43 -0
- package/dist/runtime/session.js +171 -0
- package/dist/tags/assign.d.ts +6 -0
- package/dist/tags/assign.js +32 -0
- package/dist/tags/call.d.ts +6 -0
- package/dist/tags/call.js +130 -0
- package/dist/tags/defvar.d.ts +6 -0
- package/dist/tags/defvar.js +27 -0
- package/dist/tags/eval.d.ts +6 -0
- package/dist/tags/eval.js +55 -0
- package/dist/tags/execute.d.ts +6 -0
- package/dist/tags/execute.js +45 -0
- package/dist/tags/expr.d.ts +8 -0
- package/dist/tags/expr.js +107 -0
- package/dist/tags/if.d.ts +6 -0
- package/dist/tags/if.js +46 -0
- package/dist/tags/llm.d.ts +6 -0
- package/dist/tags/llm.js +73 -0
- package/dist/tags/loop.d.ts +6 -0
- package/dist/tags/loop.js +31 -0
- package/dist/tags/output.d.ts +6 -0
- package/dist/tags/output.js +19 -0
- package/dist/tags/parameters.d.ts +6 -0
- package/dist/tags/parameters.js +63 -0
- package/dist/tags/subroutine.d.ts +6 -0
- package/dist/tags/subroutine.js +41 -0
- package/dist/tags/variable.d.ts +6 -0
- package/dist/tags/variable.js +20 -0
- package/dist/types/index.d.ts +76 -0
- package/dist/types/index.js +5 -0
- package/dist/utils/llm-adapter.d.ts +22 -0
- package/dist/utils/llm-adapter.js +91 -0
- package/examples/llm-reflection-test.di +18 -0
- package/package.json +21 -0
- package/src/chatbox.ts +89 -0
- package/src/index.ts +47 -0
- package/src/runtime/braket-parser.ts +234 -0
- package/src/runtime/interpreter.ts +129 -0
- package/src/runtime/parser.ts +151 -0
- package/src/runtime/session.ts +209 -0
- package/src/tags/assign.ts +37 -0
- package/src/tags/call.ts +166 -0
- package/src/tags/defvar.ts +32 -0
- package/src/tags/eval.ts +65 -0
- package/src/tags/execute.ts +52 -0
- package/src/tags/expr.ts +128 -0
- package/src/tags/if.ts +58 -0
- package/src/tags/llm.ts +88 -0
- package/src/tags/loop.ts +43 -0
- package/src/tags/output.ts +23 -0
- package/src/tags/parameters.ts +77 -0
- package/src/tags/subroutine.ts +52 -0
- package/src/tags/variable.ts +27 -0
- package/src/types/index.ts +103 -0
- package/src/utils/llm-adapter.ts +118 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<dirac>
|
|
2
|
+
<subroutine name="inventory" description="Shows the current inventory.">
|
|
3
|
+
<eval>
|
|
4
|
+
emit(session, 'Inventory: apples=5, oranges=3');
|
|
5
|
+
</eval>
|
|
6
|
+
</subroutine>
|
|
7
|
+
|
|
8
|
+
<subroutine name="order" description="Places an order for an item." param-item="string:required:Item name" param-quantity="number:required:Quantity">
|
|
9
|
+
<parameters select="@item"/>
|
|
10
|
+
<parameters select="@quantity"/>
|
|
11
|
+
<eval>
|
|
12
|
+
emit(session, `Order placed: ${item} x ${quantity}`);
|
|
13
|
+
</eval>
|
|
14
|
+
</subroutine>
|
|
15
|
+
|
|
16
|
+
<llm>Show me my inventory</llm>
|
|
17
|
+
<llm>Order 2 apples</llm>
|
|
18
|
+
</dirac>
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dirac-browser",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Browser-compatible Dirac language interpreter (no Node.js dependencies)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"watch": "tsc --watch"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["dirac", "interpreter", "browser", "frontend"],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.3.3"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"fast-xml-parser": "^4.3.4"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/chatbox.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dirac Chatbox Web Component
|
|
3
|
+
* Usage: <dirac-chatbox resource="/assets/ui-services.dirac.xml"></dirac-chatbox>
|
|
4
|
+
*/
|
|
5
|
+
import { execute, createSession } from './index.js';
|
|
6
|
+
|
|
7
|
+
class DiracChatbox extends HTMLElement {
|
|
8
|
+
private resourceUrl: string = '';
|
|
9
|
+
private session: any;
|
|
10
|
+
private shadow: ShadowRoot;
|
|
11
|
+
private resourceLoaded: boolean = false;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.shadow = this.attachShadow({ mode: 'open' });
|
|
16
|
+
this.render();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static get observedAttributes() {
|
|
20
|
+
return ['resource'];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
|
24
|
+
if (name === 'resource' && newValue) {
|
|
25
|
+
this.resourceUrl = newValue;
|
|
26
|
+
this.loadResource();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
connectedCallback() {
|
|
31
|
+
if (this.getAttribute('resource')) {
|
|
32
|
+
this.resourceUrl = this.getAttribute('resource')!;
|
|
33
|
+
this.loadResource();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async loadResource() {
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(this.resourceUrl);
|
|
40
|
+
const xml = await res.text();
|
|
41
|
+
this.session = createSession({});
|
|
42
|
+
await execute(xml, this.session.config); // Register subroutines
|
|
43
|
+
this.resourceLoaded = true;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
this.shadow.innerHTML = `<div style="color:red">Failed to load resource: ${err}</div>`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
render() {
|
|
50
|
+
this.shadow.innerHTML = `
|
|
51
|
+
<style>
|
|
52
|
+
.chatbox { border: 1px solid #ccc; padding: 1em; border-radius: 8px; width: 100%; max-width: 400px; font-family: sans-serif; }
|
|
53
|
+
.messages { min-height: 100px; margin-bottom: 1em; }
|
|
54
|
+
.input-row { display: flex; gap: 0.5em; }
|
|
55
|
+
input[type=text] { flex: 1; padding: 0.5em; border-radius: 4px; border: 1px solid #aaa; }
|
|
56
|
+
button { padding: 0.5em 1em; border-radius: 4px; border: none; background: #007bff; color: white; cursor: pointer; }
|
|
57
|
+
button:disabled { background: #aaa; }
|
|
58
|
+
</style>
|
|
59
|
+
<div class="chatbox">
|
|
60
|
+
<div class="messages"></div>
|
|
61
|
+
<form class="input-row">
|
|
62
|
+
<input type="text" placeholder="Type your message..." />
|
|
63
|
+
<button type="submit">Send</button>
|
|
64
|
+
</form>
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
this.shadow.querySelector('form')!.addEventListener('submit', e => this.onSend(e));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async onSend(e: Event) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
const input = this.shadow.querySelector('input[type=text]') as HTMLInputElement;
|
|
73
|
+
const messages = this.shadow.querySelector('.messages')!;
|
|
74
|
+
const userMsg = input.value.trim();
|
|
75
|
+
if (!userMsg || !this.resourceLoaded) return;
|
|
76
|
+
messages.innerHTML += `<div><b>You:</b> ${userMsg}</div>`;
|
|
77
|
+
input.value = '';
|
|
78
|
+
// Execute Dirac with <llm>userMsg</llm>
|
|
79
|
+
const script = `<llm>${userMsg}</llm>`;
|
|
80
|
+
try {
|
|
81
|
+
const result = await execute(script, this.session.config);
|
|
82
|
+
messages.innerHTML += `<div><b>Dirac:</b> ${result}</div>`;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
messages.innerHTML += `<div style="color:red"><b>Error:</b> ${err}</div>`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
customElements.define('dirac-chatbox', DiracChatbox);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dirac Language Browser Edition
|
|
3
|
+
* Browser-compatible interpreter without Node.js dependencies
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { DiracParser } from './runtime/parser.js';
|
|
7
|
+
export { createSession, getOutput, getAvailableSubroutines } from './runtime/session.js';
|
|
8
|
+
export { integrate } from './runtime/interpreter.js';
|
|
9
|
+
export { createLLMAdapter, executeUserCommand } from './utils/llm-adapter.js';
|
|
10
|
+
export type { DiracSession, DiracConfig, DiracElement, ParameterMetadata } from './types/index.js';
|
|
11
|
+
export type { LLMPromptGenerator } from './utils/llm-adapter.js';
|
|
12
|
+
|
|
13
|
+
import { DiracParser } from './runtime/parser.js';
|
|
14
|
+
import { createSession, getOutput } from './runtime/session.js';
|
|
15
|
+
import { integrate } from './runtime/interpreter.js';
|
|
16
|
+
import type { DiracConfig } from './types/index.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute Dirac source code in browser
|
|
20
|
+
*
|
|
21
|
+
* @param source - Dirac XML/HTML source code
|
|
22
|
+
* @param config - Configuration including customContext for injecting services
|
|
23
|
+
* @returns Promise resolving to output string
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const script = `
|
|
28
|
+
* <sequence>
|
|
29
|
+
* <defvar name="x"><eval>42</eval></defvar>
|
|
30
|
+
* <output><variable name="x"/></output>
|
|
31
|
+
* </sequence>
|
|
32
|
+
* `;
|
|
33
|
+
*
|
|
34
|
+
* const result = await execute(script, {
|
|
35
|
+
* customContext: { myService: serviceInstance }
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export async function execute(source: string, config: DiracConfig = {}): Promise<string> {
|
|
40
|
+
const parser = new DiracParser();
|
|
41
|
+
const session = createSession(config);
|
|
42
|
+
|
|
43
|
+
const ast = parser.parse(source);
|
|
44
|
+
await integrate(session, ast);
|
|
45
|
+
|
|
46
|
+
return getOutput(session);
|
|
47
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bra-Ket Parser - Converts bra-ket notation to XML
|
|
3
|
+
*
|
|
4
|
+
* Syntax:
|
|
5
|
+
* - Bra (subroutine): <name| ... defines a subroutine
|
|
6
|
+
* - Ket (everything else): |tag attrs> ... can have children/content
|
|
7
|
+
* - Indentation: Defines scope (like Python/YAML)
|
|
8
|
+
*
|
|
9
|
+
* Examples:
|
|
10
|
+
* |output>Hello World → <output>Hello World</output>
|
|
11
|
+
* |variable name=x> → <variable name="x"/>
|
|
12
|
+
* <add| → <subroutine name="add">
|
|
13
|
+
* |output>test → <output>test</output>
|
|
14
|
+
* → </subroutine>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface BraKetLine {
|
|
18
|
+
indent: number;
|
|
19
|
+
type: 'bra' | 'ket' | 'text' | 'empty';
|
|
20
|
+
tag?: string;
|
|
21
|
+
attrs?: string;
|
|
22
|
+
text?: string;
|
|
23
|
+
raw: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class BraKetParser {
|
|
27
|
+
private lines: string[] = [];
|
|
28
|
+
private currentLine = 0;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse bra-ket notation and compile to XML
|
|
32
|
+
*/
|
|
33
|
+
parse(source: string): string {
|
|
34
|
+
this.lines = source.split('\n');
|
|
35
|
+
this.currentLine = 0;
|
|
36
|
+
|
|
37
|
+
const xml: string[] = ['<dirac>'];
|
|
38
|
+
this.parseBlock(xml, -1);
|
|
39
|
+
xml.push('</dirac>');
|
|
40
|
+
|
|
41
|
+
return xml.join('\n');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse a block of lines at a given indentation level
|
|
46
|
+
*/
|
|
47
|
+
private parseBlock(output: string[], parentIndent: number): void {
|
|
48
|
+
while (this.currentLine < this.lines.length) {
|
|
49
|
+
const line = this.parseLine(this.lines[this.currentLine]);
|
|
50
|
+
|
|
51
|
+
// Empty lines are preserved as-is
|
|
52
|
+
if (line.type === 'empty') {
|
|
53
|
+
this.currentLine++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If indent is less than or equal to parent, we're done with this block
|
|
58
|
+
if (line.indent <= parentIndent) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Bra: <name| defines a subroutine
|
|
63
|
+
if (line.type === 'bra') {
|
|
64
|
+
const attrs = line.attrs ? ` ${this.convertAttributes(line.attrs)}` : '';
|
|
65
|
+
output.push(`${' '.repeat(line.indent)}<subroutine name="${line.tag}"${attrs}>`);
|
|
66
|
+
this.currentLine++;
|
|
67
|
+
this.parseBlock(output, line.indent);
|
|
68
|
+
output.push(`${' '.repeat(line.indent)}</subroutine>`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Ket: |tag attrs>
|
|
73
|
+
if (line.type === 'ket') {
|
|
74
|
+
const indent = ' '.repeat(line.indent);
|
|
75
|
+
const attrs = line.attrs ? ` ${this.convertAttributes(line.attrs)}` : '';
|
|
76
|
+
|
|
77
|
+
// Check if next line is more indented (has children/content)
|
|
78
|
+
const nextLine = this.currentLine + 1 < this.lines.length
|
|
79
|
+
? this.parseLine(this.lines[this.currentLine + 1])
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
if (nextLine && nextLine.indent > line.indent && nextLine.type !== 'empty') {
|
|
83
|
+
// Has children - opening tag
|
|
84
|
+
output.push(`${indent}<${line.tag}${attrs}>`);
|
|
85
|
+
this.currentLine++;
|
|
86
|
+
this.parseBlock(output, line.indent);
|
|
87
|
+
output.push(`${indent}</${line.tag}>`);
|
|
88
|
+
} else {
|
|
89
|
+
// Self-closing or inline text
|
|
90
|
+
if (line.text) {
|
|
91
|
+
// Inline text with embedded kets
|
|
92
|
+
const content = this.convertInlineKets(line.text);
|
|
93
|
+
output.push(`${indent}<${line.tag}${attrs}>${content}</${line.tag}>`);
|
|
94
|
+
} else {
|
|
95
|
+
// Self-closing
|
|
96
|
+
output.push(`${indent}<${line.tag}${attrs}/>`);
|
|
97
|
+
}
|
|
98
|
+
this.currentLine++;
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Plain text content
|
|
104
|
+
if (line.type === 'text') {
|
|
105
|
+
const indent = ' '.repeat(line.indent);
|
|
106
|
+
const content = this.convertInlineKets(line.text || '');
|
|
107
|
+
output.push(`${indent}${content}`);
|
|
108
|
+
this.currentLine++;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parse a single line into structured form
|
|
116
|
+
*/
|
|
117
|
+
private parseLine(raw: string): BraKetLine {
|
|
118
|
+
// Count leading spaces for indentation
|
|
119
|
+
const match = raw.match(/^(\s*)(.*)/);
|
|
120
|
+
const indent = match ? Math.floor(match[1].length / 2) : 0;
|
|
121
|
+
const content = match ? match[2] : '';
|
|
122
|
+
|
|
123
|
+
// Empty line
|
|
124
|
+
if (!content.trim()) {
|
|
125
|
+
return { indent, type: 'empty', raw };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Bra: <name| or <name attrs|
|
|
129
|
+
const braMatch = content.match(/^<([a-zA-Z_][a-zA-Z0-9_-]*)\s*([^|]*)\|$/);
|
|
130
|
+
if (braMatch) {
|
|
131
|
+
return {
|
|
132
|
+
indent,
|
|
133
|
+
type: 'bra',
|
|
134
|
+
tag: braMatch[1],
|
|
135
|
+
attrs: braMatch[2].trim() || undefined,
|
|
136
|
+
raw
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Ket: |tag> or |tag attrs> or |tag>text
|
|
141
|
+
const ketMatch = content.match(/^\|([a-zA-Z_][a-zA-Z0-9_-]*)\s*([^>]*?)>\s*(.*)/);
|
|
142
|
+
if (ketMatch) {
|
|
143
|
+
return {
|
|
144
|
+
indent,
|
|
145
|
+
type: 'ket',
|
|
146
|
+
tag: ketMatch[1],
|
|
147
|
+
attrs: ketMatch[2].trim() || undefined,
|
|
148
|
+
text: ketMatch[3] || undefined,
|
|
149
|
+
raw
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Plain text
|
|
154
|
+
return {
|
|
155
|
+
indent,
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: content,
|
|
158
|
+
raw
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Convert bra-ket attribute syntax to XML
|
|
164
|
+
* Examples:
|
|
165
|
+
* name=value → name="value"
|
|
166
|
+
* x=5 y=10 → x="5" y="10"
|
|
167
|
+
* select=@* → select="@*"
|
|
168
|
+
*/
|
|
169
|
+
private convertAttributes(attrs: string): string {
|
|
170
|
+
if (!attrs) return '';
|
|
171
|
+
|
|
172
|
+
// Split by spaces but respect quoted strings
|
|
173
|
+
const parts: string[] = [];
|
|
174
|
+
let current = '';
|
|
175
|
+
let inQuotes = false;
|
|
176
|
+
let quoteChar = '';
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
179
|
+
const char = attrs[i];
|
|
180
|
+
|
|
181
|
+
if ((char === '"' || char === "'") && (i === 0 || attrs[i - 1] !== '\\')) {
|
|
182
|
+
if (!inQuotes) {
|
|
183
|
+
inQuotes = true;
|
|
184
|
+
quoteChar = char;
|
|
185
|
+
current += char;
|
|
186
|
+
} else if (char === quoteChar) {
|
|
187
|
+
inQuotes = false;
|
|
188
|
+
current += char;
|
|
189
|
+
} else {
|
|
190
|
+
current += char;
|
|
191
|
+
}
|
|
192
|
+
} else if (char === ' ' && !inQuotes) {
|
|
193
|
+
if (current.trim()) {
|
|
194
|
+
parts.push(current.trim());
|
|
195
|
+
current = '';
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
current += char;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (current.trim()) {
|
|
203
|
+
parts.push(current.trim());
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Convert each attribute
|
|
207
|
+
return parts.map(part => {
|
|
208
|
+
const match = part.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)=(.+)$/);
|
|
209
|
+
if (!match) return part;
|
|
210
|
+
|
|
211
|
+
const [, name, value] = match;
|
|
212
|
+
|
|
213
|
+
// Already quoted
|
|
214
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
215
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
216
|
+
return `${name}=${value}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Quote it
|
|
220
|
+
return `${name}="${value}"`;
|
|
221
|
+
}).join(' ');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Convert inline kets within text content
|
|
226
|
+
* Example: "Hello |variable name=x> world" → "Hello <variable name="x"/> world"
|
|
227
|
+
*/
|
|
228
|
+
private convertInlineKets(text: string): string {
|
|
229
|
+
return text.replace(/\|([a-zA-Z_][a-zA-Z0-9_-]*)\s*([^>]*?)>/g, (match, tag, attrs) => {
|
|
230
|
+
const attrStr = attrs.trim() ? ` ${this.convertAttributes(attrs.trim())}` : '';
|
|
231
|
+
return `<${tag}${attrStr}/>`;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core interpreter - Browser edition (no Node.js dependencies)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DiracElement, DiracSession } from '../types/index.js';
|
|
6
|
+
import { substituteVariables, emit, getSubroutine } from './session.js';
|
|
7
|
+
import { executeDefvar } from '../tags/defvar.js';
|
|
8
|
+
import { executeVariable } from '../tags/variable.js';
|
|
9
|
+
import { executeAssign } from '../tags/assign.js';
|
|
10
|
+
import { executeOutput } from '../tags/output.js';
|
|
11
|
+
import { executeSubroutine } from '../tags/subroutine.js';
|
|
12
|
+
import { executeCall } from '../tags/call.js';
|
|
13
|
+
import { executeLoop } from '../tags/loop.js';
|
|
14
|
+
import { executeIf } from '../tags/if.js';
|
|
15
|
+
import { executeEval } from '../tags/eval.js';
|
|
16
|
+
import { executeExecute } from '../tags/execute.js';
|
|
17
|
+
import { executeParameters } from '../tags/parameters.js';
|
|
18
|
+
import { executeExpr } from '../tags/expr.js';
|
|
19
|
+
import { executeLLM } from '../tags/llm.js';
|
|
20
|
+
|
|
21
|
+
export async function integrate(session: DiracSession, element: DiracElement): Promise<void> {
|
|
22
|
+
// Check execution limits
|
|
23
|
+
if (session.limits.currentDepth >= session.limits.maxDepth) {
|
|
24
|
+
throw new Error('Maximum execution depth exceeded');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
session.limits.currentDepth++;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Handle text nodes
|
|
31
|
+
if (element.text && !element.tag) {
|
|
32
|
+
const substituted = substituteVariables(session, element.text);
|
|
33
|
+
emit(session, substituted);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check control flow
|
|
38
|
+
if (session.isReturn || session.isBreak) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Dispatch to tag handlers
|
|
43
|
+
switch (element.tag.toLowerCase()) {
|
|
44
|
+
case 'defvar':
|
|
45
|
+
executeDefvar(session, element);
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case 'variable':
|
|
49
|
+
executeVariable(session, element);
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case 'assign':
|
|
53
|
+
executeAssign(session, element);
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case 'output':
|
|
57
|
+
await executeOutput(session, element);
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case 'subroutine':
|
|
61
|
+
executeSubroutine(session, element);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'call':
|
|
65
|
+
await executeCall(session, element);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'loop':
|
|
69
|
+
await executeLoop(session, element);
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case 'if':
|
|
73
|
+
await executeIf(session, element);
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case 'eval':
|
|
77
|
+
await executeEval(session, element);
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
case 'execute':
|
|
81
|
+
await executeExecute(session, element);
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
case 'parameters':
|
|
85
|
+
await executeParameters(session, element);
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 'expr':
|
|
89
|
+
await executeExpr(session, element);
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
// Browser-incompatible tags
|
|
93
|
+
case 'import':
|
|
94
|
+
console.warn('[dirac-browser] <import> tag not supported in browser (requires fs)');
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case 'system':
|
|
98
|
+
console.warn('[dirac-browser] <system> tag not supported in browser (requires child_process)');
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case 'llm':
|
|
102
|
+
await executeLLM(session, element);
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
// Unknown tag - check if it's a subroutine name
|
|
107
|
+
const subroutine = getSubroutine(session, element.tag);
|
|
108
|
+
if (subroutine) {
|
|
109
|
+
// Treat unknown tag as subroutine call
|
|
110
|
+
await executeCall(session, element);
|
|
111
|
+
} else {
|
|
112
|
+
// Really unknown - just process children
|
|
113
|
+
for (const child of element.children) {
|
|
114
|
+
await integrate(session, child);
|
|
115
|
+
if (session.isReturn || session.isBreak) break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} finally {
|
|
120
|
+
session.limits.currentDepth--;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function integrateChildren(session: DiracSession, element: DiracElement): Promise<void> {
|
|
125
|
+
for (const child of element.children) {
|
|
126
|
+
await integrate(session, child);
|
|
127
|
+
if (session.isReturn || session.isBreak) break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML Parser for Dirac (.di files)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
6
|
+
import type { DiracElement } from '../types/index.js';
|
|
7
|
+
|
|
8
|
+
export class DiracParser {
|
|
9
|
+
private parser: XMLParser;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.parser = new XMLParser({
|
|
13
|
+
ignoreAttributes: false,
|
|
14
|
+
attributeNamePrefix: '@_',
|
|
15
|
+
trimValues: true,
|
|
16
|
+
parseAttributeValue: false,
|
|
17
|
+
parseTagValue: false,
|
|
18
|
+
textNodeName: '#text',
|
|
19
|
+
cdataPropName: '#cdata',
|
|
20
|
+
preserveOrder: true, // Preserve element order!
|
|
21
|
+
commentPropName: '#comment',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
parse(source: string): DiracElement {
|
|
26
|
+
// Strip shebang line if present
|
|
27
|
+
if (source.startsWith('#!')) {
|
|
28
|
+
source = source.replace(/^#!.*\n/, '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = this.parser.parse(source);
|
|
32
|
+
// With preserveOrder, result is an array
|
|
33
|
+
if (!Array.isArray(result) || result.length === 0) {
|
|
34
|
+
throw new Error('Empty or invalid XML');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Find the first non-comment element
|
|
38
|
+
for (const item of result) {
|
|
39
|
+
if (!item['#comment']) {
|
|
40
|
+
return this.convertOrderedToElement(item);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new Error('No root element found');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private convertOrderedToElement(obj: any): DiracElement {
|
|
48
|
+
// obj is like { "tagname": [...children], ":@": {...attributes} }
|
|
49
|
+
const tagName = Object.keys(obj).find(k => k !== ':@' && k !== '#comment');
|
|
50
|
+
|
|
51
|
+
if (!tagName) {
|
|
52
|
+
throw new Error('Invalid element structure');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const element: DiracElement = {
|
|
56
|
+
tag: tagName,
|
|
57
|
+
attributes: {},
|
|
58
|
+
children: [],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Extract attributes
|
|
62
|
+
if (obj[':@']) {
|
|
63
|
+
for (const [key, value] of Object.entries(obj[':@'])) {
|
|
64
|
+
if (key.startsWith('@_')) {
|
|
65
|
+
element.attributes[key.slice(2)] = value as string;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Extract children
|
|
71
|
+
const children = obj[tagName];
|
|
72
|
+
if (Array.isArray(children)) {
|
|
73
|
+
for (const child of children) {
|
|
74
|
+
if (child['#text']) {
|
|
75
|
+
// Text node - add as child AND to element.text for backward compat
|
|
76
|
+
element.children.push({
|
|
77
|
+
tag: '',
|
|
78
|
+
text: child['#text'],
|
|
79
|
+
attributes: {},
|
|
80
|
+
children: []
|
|
81
|
+
});
|
|
82
|
+
// Also set element.text if not set (for simple text-only elements)
|
|
83
|
+
if (!element.text) {
|
|
84
|
+
element.text = child['#text'];
|
|
85
|
+
} else {
|
|
86
|
+
element.text += child['#text'];
|
|
87
|
+
}
|
|
88
|
+
} else if (child['#comment']) {
|
|
89
|
+
// Skip comments
|
|
90
|
+
continue;
|
|
91
|
+
} else {
|
|
92
|
+
// Child element
|
|
93
|
+
element.children.push(this.convertOrderedToElement(child));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return element;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Old method - no longer used
|
|
102
|
+
private convertToElement(obj: any, tagName?: string): DiracElement {
|
|
103
|
+
if (!tagName) {
|
|
104
|
+
// Root level - find the actual tag
|
|
105
|
+
const keys = Object.keys(obj);
|
|
106
|
+
if (keys.length === 0) {
|
|
107
|
+
throw new Error('Empty XML');
|
|
108
|
+
}
|
|
109
|
+
tagName = keys[0];
|
|
110
|
+
obj = obj[tagName];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const element: DiracElement = {
|
|
114
|
+
tag: tagName,
|
|
115
|
+
attributes: {},
|
|
116
|
+
children: [],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (typeof obj === 'string') {
|
|
120
|
+
element.text = obj;
|
|
121
|
+
return element;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!obj) {
|
|
125
|
+
return element;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Extract attributes and children
|
|
129
|
+
for (const key of Object.keys(obj)) {
|
|
130
|
+
const value = obj[key];
|
|
131
|
+
|
|
132
|
+
if (key === '#text') {
|
|
133
|
+
element.text = value;
|
|
134
|
+
} else if (key.startsWith('@_')) {
|
|
135
|
+
// Attribute
|
|
136
|
+
element.attributes[key.slice(2)] = value;
|
|
137
|
+
} else {
|
|
138
|
+
// Child element
|
|
139
|
+
if (Array.isArray(value)) {
|
|
140
|
+
for (const item of value) {
|
|
141
|
+
element.children.push(this.convertToElement(item, key));
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
element.children.push(this.convertToElement(value, key));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return element;
|
|
150
|
+
}
|
|
151
|
+
}
|