@holoscript/robotics-plugin 1.0.0 → 2.0.2
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/CHANGELOG.md +14 -0
- package/examples/isaac-lab-sim-to-real-bridge.holo +63 -0
- package/examples/isaac-lab-sim-to-real.holo +127 -0
- package/package.json +7 -42
- package/python/isaac_lab_bridge.py +157 -0
- package/src/__tests__/isaac-lab-interop.test.ts +210 -0
- package/src/ast.ts +80 -0
- package/src/index.ts +563 -0
- package/src/lexer.ts +307 -0
- package/src/parser.ts +473 -0
- package/src/traits/ROS2HardwareLoopTrait.ts +84 -0
- package/src/traits/types.ts +4 -0
- package/src/usd-codegen.ts +651 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
- package/README.md +0 -3
- package/dist/ast.d.ts +0 -21
- package/dist/ast.js +0 -5
- package/dist/index.d.ts +0 -58
- package/dist/index.js +0 -44
- package/dist/lexer.d.ts +0 -52
- package/dist/lexer.js +0 -275
- package/dist/parser.d.ts +0 -22
- package/dist/parser.js +0 -202
- package/dist/usd-codegen.d.ts +0 -18
- package/dist/usd-codegen.js +0 -361
- package/examples/robot-arm-complete.holo +0 -199
- package/python/ros2_bridge.py +0 -77
package/src/lexer.ts
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HoloScript Lexer (Tokenizer)
|
|
3
|
+
*
|
|
4
|
+
* Converts HoloScript source code into a stream of tokens for parsing.
|
|
5
|
+
* Supports: keywords, identifiers, traits (@trait), strings, numbers, symbols
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export enum TokenType {
|
|
9
|
+
// Keywords
|
|
10
|
+
KEYWORD = 'KEYWORD', // composition, object, using, template, group
|
|
11
|
+
|
|
12
|
+
// Identifiers & Traits
|
|
13
|
+
IDENTIFIER = 'IDENTIFIER', // variable_name, link1, base
|
|
14
|
+
TRAIT = 'TRAIT', // @joint_revolute, @force_sensor
|
|
15
|
+
STRING = 'STRING', // "Base", "metal", "cylinder"
|
|
16
|
+
NUMBER = 'NUMBER', // 0.1, 3.14, 100, -2.0
|
|
17
|
+
|
|
18
|
+
// Symbols
|
|
19
|
+
LBRACE = 'LBRACE', // {
|
|
20
|
+
RBRACE = 'RBRACE', // }
|
|
21
|
+
LBRACKET = 'LBRACKET', // [
|
|
22
|
+
RBRACKET = 'RBRACKET', // ]
|
|
23
|
+
COLON = 'COLON', // :
|
|
24
|
+
COMMA = 'COMMA', // ,
|
|
25
|
+
|
|
26
|
+
// Special
|
|
27
|
+
COMMENT = 'COMMENT', // // comment
|
|
28
|
+
EOF = 'EOF', // end of file
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Token {
|
|
32
|
+
type: TokenType;
|
|
33
|
+
value: string;
|
|
34
|
+
line: number;
|
|
35
|
+
column: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class Lexer {
|
|
39
|
+
private source: string;
|
|
40
|
+
private position: number = 0;
|
|
41
|
+
private line: number = 1;
|
|
42
|
+
private column: number = 1;
|
|
43
|
+
private tokens: Token[] = [];
|
|
44
|
+
|
|
45
|
+
private readonly keywords = new Set([
|
|
46
|
+
'composition',
|
|
47
|
+
'object',
|
|
48
|
+
'using',
|
|
49
|
+
'template',
|
|
50
|
+
'group',
|
|
51
|
+
'scene',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
constructor(source: string) {
|
|
55
|
+
this.source = source;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
tokenize(): Token[] {
|
|
59
|
+
while (this.position < this.source.length) {
|
|
60
|
+
this.scanToken();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.tokens.push({
|
|
64
|
+
type: TokenType.EOF,
|
|
65
|
+
value: '',
|
|
66
|
+
line: this.line,
|
|
67
|
+
column: this.column,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return this.tokens;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private scanToken(): void {
|
|
74
|
+
const char = this.currentChar();
|
|
75
|
+
|
|
76
|
+
// Skip whitespace
|
|
77
|
+
if (this.isWhitespace(char)) {
|
|
78
|
+
this.skipWhitespace();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Comments
|
|
83
|
+
if (char === '/' && this.peek() === '/') {
|
|
84
|
+
this.skipComment();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Traits (@identifier)
|
|
89
|
+
if (char === '@') {
|
|
90
|
+
this.scanTrait();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// String literals ("...")
|
|
95
|
+
if (char === '"') {
|
|
96
|
+
this.scanString();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Numbers (0.1, 3.14, -2.0)
|
|
101
|
+
if (this.isDigit(char) || (char === '-' && this.isDigit(this.peek()))) {
|
|
102
|
+
this.scanNumber();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Keywords & Identifiers
|
|
107
|
+
if (this.isAlpha(char)) {
|
|
108
|
+
this.scanIdentifierOrKeyword();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Single-character tokens
|
|
113
|
+
this.scanSymbol(char);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private scanTrait(): void {
|
|
117
|
+
const startLine = this.line;
|
|
118
|
+
const startColumn = this.column;
|
|
119
|
+
|
|
120
|
+
this.advance(); // Skip '@'
|
|
121
|
+
|
|
122
|
+
let trait = '';
|
|
123
|
+
while (
|
|
124
|
+
this.position < this.source.length &&
|
|
125
|
+
this.isAlphaNumericOrUnderscore(this.currentChar())
|
|
126
|
+
) {
|
|
127
|
+
trait += this.currentChar();
|
|
128
|
+
this.advance();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.tokens.push({
|
|
132
|
+
type: TokenType.TRAIT,
|
|
133
|
+
value: trait,
|
|
134
|
+
line: startLine,
|
|
135
|
+
column: startColumn,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private scanString(): void {
|
|
140
|
+
const startLine = this.line;
|
|
141
|
+
const startColumn = this.column;
|
|
142
|
+
|
|
143
|
+
this.advance(); // Skip opening "
|
|
144
|
+
|
|
145
|
+
let str = '';
|
|
146
|
+
while (this.position < this.source.length && this.currentChar() !== '"') {
|
|
147
|
+
if (this.currentChar() === '\\') {
|
|
148
|
+
// Handle escape sequences
|
|
149
|
+
this.advance();
|
|
150
|
+
if (this.position < this.source.length) {
|
|
151
|
+
str += this.currentChar();
|
|
152
|
+
this.advance();
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
str += this.currentChar();
|
|
156
|
+
this.advance();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.currentChar() === '"') {
|
|
161
|
+
this.advance(); // Skip closing "
|
|
162
|
+
} else {
|
|
163
|
+
throw new Error(`Unterminated string at line ${startLine}, column ${startColumn}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.tokens.push({
|
|
167
|
+
type: TokenType.STRING,
|
|
168
|
+
value: str,
|
|
169
|
+
line: startLine,
|
|
170
|
+
column: startColumn,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private scanNumber(): void {
|
|
175
|
+
const startLine = this.line;
|
|
176
|
+
const startColumn = this.column;
|
|
177
|
+
|
|
178
|
+
let num = '';
|
|
179
|
+
|
|
180
|
+
// Handle negative sign
|
|
181
|
+
if (this.currentChar() === '-') {
|
|
182
|
+
num += this.currentChar();
|
|
183
|
+
this.advance();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Integer part
|
|
187
|
+
while (this.position < this.source.length && this.isDigit(this.currentChar())) {
|
|
188
|
+
num += this.currentChar();
|
|
189
|
+
this.advance();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Decimal part
|
|
193
|
+
if (this.currentChar() === '.' && this.isDigit(this.peek())) {
|
|
194
|
+
num += this.currentChar();
|
|
195
|
+
this.advance();
|
|
196
|
+
|
|
197
|
+
while (this.position < this.source.length && this.isDigit(this.currentChar())) {
|
|
198
|
+
num += this.currentChar();
|
|
199
|
+
this.advance();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.tokens.push({
|
|
204
|
+
type: TokenType.NUMBER,
|
|
205
|
+
value: num,
|
|
206
|
+
line: startLine,
|
|
207
|
+
column: startColumn,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private scanIdentifierOrKeyword(): void {
|
|
212
|
+
const startLine = this.line;
|
|
213
|
+
const startColumn = this.column;
|
|
214
|
+
|
|
215
|
+
let ident = '';
|
|
216
|
+
while (
|
|
217
|
+
this.position < this.source.length &&
|
|
218
|
+
this.isAlphaNumericOrUnderscore(this.currentChar())
|
|
219
|
+
) {
|
|
220
|
+
ident += this.currentChar();
|
|
221
|
+
this.advance();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const type = this.keywords.has(ident) ? TokenType.KEYWORD : TokenType.IDENTIFIER;
|
|
225
|
+
|
|
226
|
+
this.tokens.push({
|
|
227
|
+
type,
|
|
228
|
+
value: ident,
|
|
229
|
+
line: startLine,
|
|
230
|
+
column: startColumn,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private scanSymbol(char: string): void {
|
|
235
|
+
const symbolMap: Record<string, TokenType> = {
|
|
236
|
+
'{': TokenType.LBRACE,
|
|
237
|
+
'}': TokenType.RBRACE,
|
|
238
|
+
'[': TokenType.LBRACKET,
|
|
239
|
+
']': TokenType.RBRACKET,
|
|
240
|
+
':': TokenType.COLON,
|
|
241
|
+
',': TokenType.COMMA,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const tokenType = symbolMap[char];
|
|
245
|
+
if (tokenType) {
|
|
246
|
+
this.tokens.push({
|
|
247
|
+
type: tokenType,
|
|
248
|
+
value: char,
|
|
249
|
+
line: this.line,
|
|
250
|
+
column: this.column,
|
|
251
|
+
});
|
|
252
|
+
this.advance();
|
|
253
|
+
} else {
|
|
254
|
+
throw new Error(`Unexpected character '${char}' at line ${this.line}, column ${this.column}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private skipWhitespace(): void {
|
|
259
|
+
while (this.position < this.source.length && this.isWhitespace(this.currentChar())) {
|
|
260
|
+
if (this.currentChar() === '\n') {
|
|
261
|
+
this.line++;
|
|
262
|
+
this.column = 1;
|
|
263
|
+
} else {
|
|
264
|
+
this.column++;
|
|
265
|
+
}
|
|
266
|
+
this.position++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private skipComment(): void {
|
|
271
|
+
// Skip until end of line
|
|
272
|
+
while (this.position < this.source.length && this.currentChar() !== '\n') {
|
|
273
|
+
this.position++;
|
|
274
|
+
this.column++;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private currentChar(): string {
|
|
279
|
+
return this.source[this.position] || '';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private peek(offset: number = 1): string {
|
|
283
|
+
const peekPos = this.position + offset;
|
|
284
|
+
return this.source[peekPos] || '';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private advance(): void {
|
|
288
|
+
this.position++;
|
|
289
|
+
this.column++;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private isWhitespace(char: string): boolean {
|
|
293
|
+
return /\s/.test(char);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private isDigit(char: string): boolean {
|
|
297
|
+
return /[0-9]/.test(char);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private isAlpha(char: string): boolean {
|
|
301
|
+
return /[a-z_]/i.test(char);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private isAlphaNumericOrUnderscore(char: string): boolean {
|
|
305
|
+
return /[a-z0-9_]/i.test(char);
|
|
306
|
+
}
|
|
307
|
+
}
|