@holoscript/robotics-plugin 2.0.1 → 2.0.3

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/src/parser.ts DELETED
@@ -1,461 +0,0 @@
1
- /**
2
- * HoloScript Parser
3
- *
4
- * Converts token stream into Abstract Syntax Tree (AST).
5
- * Uses recursive descent parsing.
6
- */
7
-
8
- import { Token, TokenType } from './lexer';
9
- import { CompositionNode, ObjectNode, PropertyValue, DomainRandomizationConfig, ActuatorGroupConfig } from './ast';
10
-
11
- export class Parser {
12
- private tokens: Token[];
13
- private position: number = 0;
14
-
15
- constructor(tokens: Token[]) {
16
- this.tokens = tokens;
17
- }
18
-
19
- parse(): CompositionNode {
20
- return this.parseComposition();
21
- }
22
-
23
- private currentToken(): Token {
24
- const token = this.tokens[this.position];
25
- if (!token) {
26
- throw new Error('Unexpected end of input');
27
- }
28
- return token;
29
- }
30
-
31
- private peek(offset: number = 1): Token {
32
- const pos = this.position + offset;
33
- const token = pos < this.tokens.length ? this.tokens[pos] : this.tokens[this.tokens.length - 1];
34
- if (!token) {
35
- throw new Error('Unexpected end of input');
36
- }
37
- return token;
38
- }
39
-
40
- private advance(): Token {
41
- const token = this.currentToken();
42
- this.position++;
43
- return token;
44
- }
45
-
46
- private expect(type: TokenType, value?: string): Token {
47
- const token = this.currentToken();
48
-
49
- if (token.type !== type) {
50
- throw new Error(
51
- `Expected ${type}${value ? ` "${value}"` : ''} but got ${token.type} "${token.value}" at line ${token.line}, column ${token.column}`
52
- );
53
- }
54
-
55
- if (value !== undefined && token.value !== value) {
56
- throw new Error(
57
- `Expected "${value}" but got "${token.value}" at line ${token.line}, column ${token.column}`
58
- );
59
- }
60
-
61
- return this.advance();
62
- }
63
-
64
- private parseComposition(): CompositionNode {
65
- const startToken = this.expect(TokenType.KEYWORD, 'composition');
66
- const name = this.expect(TokenType.STRING).value;
67
- this.expect(TokenType.LBRACE);
68
-
69
- const objects: ObjectNode[] = [];
70
- let domainRandomization: DomainRandomizationConfig | undefined;
71
-
72
- while (
73
- this.currentToken().type !== TokenType.RBRACE &&
74
- this.currentToken().type !== TokenType.EOF
75
- ) {
76
- // Check for domain_randomization block at composition level
77
- if (this.currentToken().type === TokenType.IDENTIFIER &&
78
- this.currentToken().value === 'domain_randomization') {
79
- this.advance();
80
- this.expect(TokenType.COLON);
81
- domainRandomization = this.parseDomainRandomizationBlock();
82
- } else {
83
- objects.push(this.parseObject());
84
- }
85
- }
86
-
87
- this.expect(TokenType.RBRACE);
88
-
89
- return {
90
- type: 'composition',
91
- name,
92
- objects,
93
- domainRandomization,
94
- line: startToken.line,
95
- column: startToken.column,
96
- };
97
- }
98
-
99
- private parseObject(): ObjectNode {
100
- const startToken = this.expect(TokenType.KEYWORD, 'object');
101
- const name = this.expect(TokenType.STRING).value;
102
-
103
- // Parse traits (@trait1 @trait2 ...)
104
- const traits: string[] = [];
105
- while (this.currentToken().type === TokenType.TRAIT) {
106
- traits.push(this.advance().value);
107
- }
108
-
109
- this.expect(TokenType.LBRACE);
110
-
111
- // Parse properties (key: value)
112
- const properties: Record<string, PropertyValue> = {};
113
- let domainRandomization: DomainRandomizationConfig | undefined;
114
- let actuatorGroups: ActuatorGroupConfig[] | undefined;
115
-
116
- while (
117
- this.currentToken().type !== TokenType.RBRACE &&
118
- this.currentToken().type !== TokenType.EOF
119
- ) {
120
- // Support both object "link" @trait { ... } and object "link" { @trait ... }.
121
- if (this.currentToken().type === TokenType.TRAIT) {
122
- const trait = this.advance().value;
123
- if (!traits.includes(trait)) {
124
- traits.push(trait);
125
- }
126
- }
127
- // Check for domain_randomization block
128
- else if (this.currentToken().type === TokenType.IDENTIFIER &&
129
- this.currentToken().value === 'domain_randomization') {
130
- this.advance();
131
- this.expect(TokenType.COLON);
132
- domainRandomization = this.parseDomainRandomizationBlock();
133
- }
134
- // Check for actuator_group block
135
- else if (this.currentToken().type === TokenType.IDENTIFIER &&
136
- this.currentToken().value === 'actuator_group') {
137
- this.advance();
138
- this.expect(TokenType.COLON);
139
- const group = this.parseActuatorGroupBlock();
140
- if (!actuatorGroups) actuatorGroups = [];
141
- actuatorGroups.push(group);
142
- }
143
- // Regular property
144
- else {
145
- const keyName = this.expect(TokenType.IDENTIFIER).value;
146
- this.expect(TokenType.COLON);
147
- const value = this.parseValue();
148
- properties[keyName] = value;
149
- }
150
- }
151
-
152
- this.expect(TokenType.RBRACE);
153
-
154
- return {
155
- type: 'object',
156
- name,
157
- traits,
158
- properties,
159
- domainRandomization,
160
- actuatorGroups,
161
- line: startToken.line,
162
- column: startToken.column,
163
- };
164
- }
165
-
166
- private parseValue(): PropertyValue {
167
- const token = this.currentToken();
168
-
169
- // String value
170
- if (token.type === TokenType.STRING) {
171
- return this.advance().value;
172
- }
173
-
174
- // Number value
175
- if (token.type === TokenType.NUMBER) {
176
- return parseFloat(this.advance().value);
177
- }
178
-
179
- // Array value [1, 2, 3]
180
- if (token.type === TokenType.LBRACKET) {
181
- return this.parseArray();
182
- }
183
-
184
- // Function call (for future: diagonal([1, 2, 3]))
185
- if (token.type === TokenType.IDENTIFIER && this.peek().type === TokenType.LBRACKET) {
186
- this.advance(); // Skip function name for now
187
- const args = this.parseArray();
188
-
189
- // For now, treat function calls as arrays (e.g., diagonal([1,2,3]) -> [1,2,3])
190
- // In future, could preserve function name metadata
191
- return args;
192
- }
193
-
194
- throw new Error(
195
- `Unexpected value type ${token.type} "${token.value}" at line ${token.line}, column ${token.column}`
196
- );
197
- }
198
-
199
- private parseArray(): PropertyValue[] {
200
- this.expect(TokenType.LBRACKET);
201
-
202
- const values: PropertyValue[] = [];
203
-
204
- while (
205
- this.currentToken().type !== TokenType.RBRACKET &&
206
- this.currentToken().type !== TokenType.EOF
207
- ) {
208
- if (this.currentToken().type === TokenType.NUMBER) {
209
- values.push(parseFloat(this.advance().value));
210
- } else if (this.currentToken().type === TokenType.STRING) {
211
- values.push(this.advance().value);
212
- } else if (this.currentToken().type === TokenType.LBRACKET) {
213
- // Nested array
214
- values.push(this.parseArray());
215
- } else {
216
- throw new Error(
217
- `Unexpected token in array: ${this.currentToken().type} at line ${this.currentToken().line}`
218
- );
219
- }
220
-
221
- // Optional comma
222
- if (this.currentToken().type === TokenType.COMMA) {
223
- this.advance();
224
- }
225
- }
226
-
227
- this.expect(TokenType.RBRACKET);
228
- return values;
229
- }
230
-
231
- private parseDomainRandomizationBlock(): DomainRandomizationConfig {
232
- this.expect(TokenType.LBRACE);
233
- const config: DomainRandomizationConfig = {};
234
-
235
- while (
236
- this.currentToken().type !== TokenType.RBRACE &&
237
- this.currentToken().type !== TokenType.EOF
238
- ) {
239
- const key = this.expect(TokenType.IDENTIFIER).value;
240
- this.expect(TokenType.COLON);
241
-
242
- switch (key) {
243
- case 'physics':
244
- config.physics = this.parsePhysicsRandomization();
245
- break;
246
- case 'actuator':
247
- config.actuator = this.parseActuatorRandomization();
248
- break;
249
- case 'observation':
250
- config.observation = this.parseObservationRandomization();
251
- break;
252
- case 'initialState':
253
- config.initialState = this.parseInitialStateRandomization();
254
- break;
255
- case 'disturbance':
256
- config.disturbance = this.parseDisturbanceRandomization();
257
- break;
258
- default:
259
- // Skip unknown keys
260
- this.parseValue();
261
- }
262
- }
263
-
264
- this.expect(TokenType.RBRACE);
265
- return config;
266
- }
267
-
268
- private parsePhysicsRandomization(): DomainRandomizationConfig['physics'] {
269
- this.expect(TokenType.LBRACE);
270
- const physics: DomainRandomizationConfig['physics'] = {};
271
-
272
- while (
273
- this.currentToken().type !== TokenType.RBRACE &&
274
- this.currentToken().type !== TokenType.EOF
275
- ) {
276
- const key = this.expect(TokenType.IDENTIFIER).value;
277
- this.expect(TokenType.COLON);
278
- const value = this.parseValue();
279
-
280
- if (key === 'massScale' && Array.isArray(value)) {
281
- physics.massScale = [value[0] as number, value[1] as number];
282
- } else if (key === 'frictionRange' && Array.isArray(value)) {
283
- physics.frictionRange = [value[0] as number, value[1] as number];
284
- } else if (key === 'dampingRange' && Array.isArray(value)) {
285
- physics.dampingRange = [value[0] as number, value[1] as number];
286
- } else if (key === 'armatureRange' && Array.isArray(value)) {
287
- physics.armatureRange = [value[0] as number, value[1] as number];
288
- }
289
- }
290
-
291
- this.expect(TokenType.RBRACE);
292
- return physics;
293
- }
294
-
295
- private parseActuatorRandomization(): DomainRandomizationConfig['actuator'] {
296
- this.expect(TokenType.LBRACE);
297
- const actuator: DomainRandomizationConfig['actuator'] = {};
298
-
299
- while (
300
- this.currentToken().type !== TokenType.RBRACE &&
301
- this.currentToken().type !== TokenType.EOF
302
- ) {
303
- const key = this.expect(TokenType.IDENTIFIER).value;
304
- this.expect(TokenType.COLON);
305
- const value = this.parseValue();
306
-
307
- if (key === 'kpNoise' && typeof value === 'number') {
308
- actuator.kpNoise = value;
309
- } else if (key === 'kdNoise' && typeof value === 'number') {
310
- actuator.kdNoise = value;
311
- } else if (key === 'latencyNoise' && typeof value === 'number') {
312
- actuator.latencyNoise = value;
313
- }
314
- }
315
-
316
- this.expect(TokenType.RBRACE);
317
- return actuator;
318
- }
319
-
320
- private parseObservationRandomization(): DomainRandomizationConfig['observation'] {
321
- this.expect(TokenType.LBRACE);
322
- const observation: DomainRandomizationConfig['observation'] = {};
323
-
324
- while (
325
- this.currentToken().type !== TokenType.RBRACE &&
326
- this.currentToken().type !== TokenType.EOF
327
- ) {
328
- const key = this.expect(TokenType.IDENTIFIER).value;
329
- this.expect(TokenType.COLON);
330
- const value = this.parseValue();
331
-
332
- if (key === 'jointPosNoise' && typeof value === 'number') {
333
- observation.jointPosNoise = value;
334
- } else if (key === 'jointVelNoise' && typeof value === 'number') {
335
- observation.jointVelNoise = value;
336
- } else if (key === 'imuNoise' && typeof value === 'number') {
337
- observation.imuNoise = value;
338
- }
339
- }
340
-
341
- this.expect(TokenType.RBRACE);
342
- return observation;
343
- }
344
-
345
- private parseInitialStateRandomization(): DomainRandomizationConfig['initialState'] {
346
- this.expect(TokenType.LBRACE);
347
- const initialState: DomainRandomizationConfig['initialState'] = {};
348
-
349
- while (
350
- this.currentToken().type !== TokenType.RBRACE &&
351
- this.currentToken().type !== TokenType.EOF
352
- ) {
353
- const key = this.expect(TokenType.IDENTIFIER).value;
354
- this.expect(TokenType.COLON);
355
-
356
- if (key === 'jointPosRange') {
357
- initialState.jointPosRange = this.parseNumberRangeMap();
358
- } else {
359
- const value = this.parseValue();
360
- if (key === 'rootPoseRange' && Array.isArray(value)) {
361
- initialState.rootPoseRange = value as [number, number, number, number, number, number];
362
- }
363
- }
364
- }
365
-
366
- this.expect(TokenType.RBRACE);
367
- return initialState;
368
- }
369
-
370
- private parseNumberRangeMap(): Record<string, [number, number]> {
371
- this.expect(TokenType.LBRACE);
372
- const ranges: Record<string, [number, number]> = {};
373
-
374
- while (
375
- this.currentToken().type !== TokenType.RBRACE &&
376
- this.currentToken().type !== TokenType.EOF
377
- ) {
378
- const key = this.expect(TokenType.IDENTIFIER).value;
379
- this.expect(TokenType.COLON);
380
- const value = this.parseValue();
381
-
382
- if (Array.isArray(value)) {
383
- ranges[key] = [value[0] as number, value[1] as number];
384
- }
385
- }
386
-
387
- this.expect(TokenType.RBRACE);
388
- return ranges;
389
- }
390
-
391
- private parseDisturbanceRandomization(): DomainRandomizationConfig['disturbance'] {
392
- this.expect(TokenType.LBRACE);
393
- const disturbance: DomainRandomizationConfig['disturbance'] = {};
394
-
395
- while (
396
- this.currentToken().type !== TokenType.RBRACE &&
397
- this.currentToken().type !== TokenType.EOF
398
- ) {
399
- const key = this.expect(TokenType.IDENTIFIER).value;
400
- this.expect(TokenType.COLON);
401
- const value = this.parseValue();
402
-
403
- if (key === 'forceRange' && Array.isArray(value)) {
404
- disturbance.forceRange = [value[0] as number, value[1] as number];
405
- } else if (key === 'intervalRange' && Array.isArray(value)) {
406
- disturbance.intervalRange = [value[0] as number, value[1] as number];
407
- }
408
- }
409
-
410
- this.expect(TokenType.RBRACE);
411
- return disturbance;
412
- }
413
-
414
- private parseActuatorGroupBlock(): ActuatorGroupConfig {
415
- // Parse actuator_group { name: "foo" type: "DelayedPDActuator" joints: ["joint1", "joint2"] ... }
416
- this.expect(TokenType.LBRACE);
417
- const group: ActuatorGroupConfig = {
418
- name: '',
419
- type: 'IdealPDActuator',
420
- jointNames: [],
421
- };
422
-
423
- while (
424
- this.currentToken().type !== TokenType.RBRACE &&
425
- this.currentToken().type !== TokenType.EOF
426
- ) {
427
- const key = this.expect(TokenType.IDENTIFIER).value;
428
- this.expect(TokenType.COLON);
429
- const value = this.parseValue();
430
-
431
- switch (key) {
432
- case 'name':
433
- group.name = value as string;
434
- break;
435
- case 'type':
436
- group.type = value as ActuatorGroupConfig['type'];
437
- break;
438
- case 'joints':
439
- if (Array.isArray(value)) {
440
- group.jointNames = value as string[];
441
- }
442
- break;
443
- case 'stiffness':
444
- group.stiffness = value as number;
445
- break;
446
- case 'damping':
447
- group.damping = value as number;
448
- break;
449
- case 'friction':
450
- group.friction = value as number;
451
- break;
452
- case 'latency':
453
- group.latency = value as number;
454
- break;
455
- }
456
- }
457
-
458
- this.expect(TokenType.RBRACE);
459
- return group;
460
- }
461
- }
@@ -1,69 +0,0 @@
1
- /** @ros2_hardware_loop Trait — Bidirectional hardware-in-the-loop bridge for physical robotics. @trait ros2_hardware_loop */
2
- export interface ROS2Config {
3
- nodeName: string;
4
- topicPrefix: string;
5
- updateFrequencyHz: number;
6
- bidirectional: boolean;
7
- }
8
-
9
- export interface ROS2State {
10
- connected: boolean;
11
- lastPingMs: number;
12
- activeTopics: string[];
13
- hardwareSyncDriftMs: number;
14
- }
15
-
16
- import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
17
-
18
- export function createROS2HardwareLoopHandler(): TraitHandler<ROS2Config> {
19
- return {
20
- name: 'ros2_hardware_loop',
21
- defaultConfig: { nodeName: 'holo_rig_01', topicPrefix: '/holo', updateFrequencyHz: 60, bidirectional: true },
22
- onAttach(n: unknown, c: ROS2Config, ctx: unknown) {
23
- (n as any).__ros2State = { connected: false, lastPingMs: 0, activeTopics: [], hardwareSyncDriftMs: 0 };
24
- (ctx as any).emit?.('ros2:init', { node: c.nodeName });
25
- },
26
- onDetach(n: unknown, _c: ROS2Config, ctx: unknown) {
27
- delete (n as any).__ros2State;
28
- (ctx as any).emit?.('ros2:disconnect');
29
- },
30
- onUpdate() {},
31
- onEvent(n: unknown, c: ROS2Config, ctx: unknown, e: unknown) {
32
- const s = (n as any).__ros2State as ROS2State;
33
- if (!s) return;
34
- const evt = e as any;
35
-
36
- switch (evt.type) {
37
- case 'ros2:connect':
38
- s.connected = true;
39
- // In real usage, initializes rclnodejs bridge for pub/sub physics
40
- (ctx as any).emit?.('ros2:connected', { latencyMs: 12 });
41
- break;
42
-
43
- case 'scene:transform_change':
44
- if (c.bidirectional && s.connected) {
45
- // Push virtual set transform to physical robot joint
46
- (ctx as any).emit?.('ros2:publish', { topic: `${c.topicPrefix}/joint_cmd`, payload: evt.payload });
47
- }
48
- break;
49
-
50
- case 'ros2:telemetry':
51
- // Receive physical robot telemetry to drive virtual set
52
- s.hardwareSyncDriftMs = Math.abs(Date.now() - (evt.payload.timestamp || Date.now()));
53
- (n as any).transform = evt.payload.transform;
54
- break;
55
-
56
- case 'ttu:manifested':
57
- // Dynamically bind to Text-To-Universe AST streams
58
- const crdtRoot = evt.payload?.crdtRoot;
59
- if (crdtRoot && s.connected) {
60
- const dynamicTopic = `${c.topicPrefix}/ttu_sync/${crdtRoot}`;
61
- s.activeTopics.push(dynamicTopic);
62
- (ctx as any).emit?.('ros2:subscribe', { topic: dynamicTopic });
63
- (ctx as any).emit?.('ros2:log', { message: `Bridged digital twin for TTU root: ${crdtRoot}` });
64
- }
65
- break;
66
- }
67
- }
68
- };
69
- }
@@ -1,4 +0,0 @@
1
- export type TraitContext = unknown;
2
- export type TraitEvent = unknown;
3
- export type HSPlusNode = unknown;
4
- export type TraitHandler<_T> = unknown;