-
Notifications
You must be signed in to change notification settings - Fork 10
/
vrml.pegjs
367 lines (298 loc) · 9.68 KB
/
vrml.pegjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/*
* VRML Grammar
* ============
*
* VRML Grammar for pegjs, inspired by JSON grammar found here:
* https://github.com/pegjs/pegjs/blob/master/examples/json.pegjs
*
* @author Bart McLeod [email protected]
* @since 2016-05-04
*
* The primary goal of this grammar is to enable you to use a full VRML node tree with modern JavaScript
* 3D libraries, such as ThreeJS.If this grammar is used with Pegjs, it will produce a node tree that
* you can use with a converter, to produce valid ThreeJS code, or direct output in ThreeJS (or another 3D
* JavaScript library of your choice.
*
* This grammar is currently experimental. Is has been built by trial and error, based on an old
* VRML model that I used as a source for the ThreeJs VRML loader example. The ThreeJs example
* can be found here, but it currently uses the old line by line parsing method, which has very
* limited awareness of the node tree.
*
* When used with Pegjs (https://github.com/pegjs), it can be used to parse a node tree, so
* that full awareness of the node tree will exist. This will allow to do more with VRML in JavaScript,
* such as animations.
*
*
*/
{
var nodeDefinitions = {};
var routes = {};
}
vrml
= '#VRML V2.0 utf8' vrml:( OrientationInterpolator / nodeDefinition / node / comment / route)*
{
// before returning the root vrml object, enricht it with routes and nodeDefinitions
vrml.nodeDefinitions = nodeDefinitions;
vrml.routes = routes;
return vrml;
}
/* ----- Node ------ */
OrientationInterpolator
= name:(def ws name:identifier ws { return name; }) "OrientationInterpolator" begin_node properties:( KeyValueForOrientationInterpolator / property)+ end_node
{
var n = {name:name, node: "OrientationInterpolator", isDefinition: true}
for (var i=0; i < properties.length; i++) {
n[properties[i]['name']] = properties[i]['value'];
}
// store node for later re-use
nodeDefinitions[name] = n;
n.type = "OrientationInterpolator";
return n;
}
KeyValueForOrientationInterpolator
= ws? "keyValue" begin_array quaternionArray:( q:(q:quaternion value_separator comment? {return q;})* lq:quaternion? comment? {if(lq)q.push(lq);return q;} ) end_array
{
return {name: "keyValue", value: quaternionArray, type: "KeyValueForOrientationInterpolator"};
}
nodeDefinition
= ws def ws name:identifier ws n:node
{
n.name = name;
n.isDefinition = true;
// store node for later re-use
nodeDefinitions[name] = n;
n.type = "nodeDefinition"
return n;
}
node
= ws t:identifier begin_node pp:( nodeDefinition / route / property / node / comment )* end_node
{
var n = {node: t};
// node properties are in pp, if pp is not an Inline node, if pp is an inline node, it should be read from the url
for (var i=0; i < pp.length; i++) {
var p = pp[i];
// is p a node?
if (undefined !== p.node) {
//console.log(p.node + ' node found');
// are we processing a Switch node?
if ('Switch' === n.node) {
// if the switch does not already have choice, create choice here
if (undefined === n.choice) {
n.choice = [];
}
n.choice.push(p);
} else {
// not a Switch, some other node, which has children.
// if the node does not already have children, create children here
if (undefined === n.children) {
n.children = [];
}
// @todo for an Inline node, we could use the parser (named 'parser') and fs here, to fetch the inline file and parse it
// on the other hand, it could be left up to the renderers what to do with the inline node.
/*
@see http://pegjs.org/documentation#grammar-syntax-and-semantics
The code inside the predicate can also access the parser object using the parser variable and options passed to the parser using the options variable.
*/
n.children.push(p);
}
} else if (undefined !== p.name) {
// p is a property
n[p.name] = p.value;
if (undefined !== p.comment) {
if (undefined === n.comments) { n.comments = {}; }
if (undefined === n.comments[p.name]) { n.comments[p.name] = []; }
n.comments[p.name].push(p.comment);
}
} else if (undefined !== p.src) {
// p is a route
// move it to global scope
routes.push(p);
} else {
// p is a comment
if (undefined === n.nodeComments) {
n.nodeComments = [];
}
n.nodeComments.push(p);
}
}
return n;
}
property
= orientation / coordIndex / pointArray / generic_property
orientation
= ws? name:("orientation" / "rotation" / "scaleOrientation") q:quaternion
{ return {name: name, value: q} }
quaternion
= ws? x:number " "+ y:number " "+ z:number " "+ radians:number ws?
{ return {x: x, y: y, z: z, radians: radians} }
coordIndex
= "coordIndex" ws? begin_array comment? ws? face:face+ lastFace:lastFace? ws? comment? end_array ws?
{
if (null !== lastFace) {
face.push(lastFace);
}
return {name: "coordIndex", value: face};
}
pointArray
= name:("point" / "vector") ws? begin_array comment? ws? pointArray:point+ comment? end_array ws?
{
return {name: name, value: pointArray};
}
generic_property
= ws? name:identifier ws value:value ws comment:comment?
{
var p = { name:name, value:value };
// you could change a color property here by returning r g b instead of x y z
if (null !== comment) {
p.comment = comment;
}
return p;
}
identifier "identifier"
= o:[^0-9\-\+ '"#\,\.\[\]\{\}\r\n\t]p:([^ '"#\,\.\[\]\{\}\r\n\t]+)? { return o + (p ? p.join('').trim():''); }
/* ----- Arrays (The VRML way) ----- */
array "array"
= begin_array
it:(c:comment / r:route / v:(v:value ws value_separator? { return v; } ) )*
end_array
{
var a = [];
for (var i=0; i < it.length; i++) {
var value = it[i];
if (undefined !== value.src) {
// value is a route, add to global routes
routes.push(value);
} else if (undefined !== value.comment) {
// value is a comment
if (undefined === a.comments) {
a.comments = [];
}
a.comments.push(value);
} else {
// this is what we are looking for: a value for in our array!
a.push(value);
}
}
return a;
}
/* ----- Values ----- */
value "value"
= false
/ points
/ OrientationInterpolator
/ face
/ null
/ true
/ nodeDefinition
/ node
/ point
/ pointArray
/ vector
/ vector2
/ use_statement
/ array
/ number
/ identifier
/ url
/ quoted_string
false = "false" / "FALSE" { return false; }
null = "null" / "NULL" { return null; }
true = "true" / "TRUE" { return true; }
/* ----- Numbers ----- */
number "number"
= minus? ((int frac?) / (frac)) exp? { return parseFloat(text()); }
decimal_point = "."
digit1_9 = [1-9]
e = [eE]
exp = e (minus / plus)? DIGIT+
frac = decimal_point DIGIT*
int = s:int_start c:int_continued {return s + c;}
int_start = zero / digit1_9
int_continued = i:DIGIT* {return i.join('');}
minus = "-"
plus = "+"
zero = "0"
/* ----- VRML Grammar ----- */
comment
= ws "#" text:[^\n]* ws { return { comment: text.join('').trim() }; }
route
= ws "ROUTE" ws src:route_part ws "TO" ws target:route_part ws
{
// create an index that is the name of the source node, for later retrieval of the route by name of the source
var index = src.name;
var route = { source: src, target: target };
// put it in the global routes collection
if ('undefined' === typeof routes[index]) {
routes[index] = [];
}
routes[index].push(route);
return route;
}
route_part
= name:identifier "." property:identifier
{ return { name: name, property: property }; }
begin_array = ws? "[" ws?
end_array = ws? "]" ws?
begin_node = ws? "{" ws?
end_node = ws? "}" ws?
value_separator = ws? "," ws?
name_separator = ws
ws "whitespace"
= ws:[ \t\n\r]*
{ return ws.join('');}
space
= " "
point
= p:vector ","? ws comment? { return p; }
points
= point / comment
vector
= ws? x:number ws y:number ws z:number ws comment?
{ return {x:x, y:y, z:z}; }
/* for example scale of a texture is a vec2 */
vector2
= ws x:number ws y:number ws comment?
{ return {x:x, y:y}; }
def
= ws? "DEF" ws?
{ return true; }
use_statement
= ws use ws name:identifier
{
var obj = nodeDefinitions[name];
if (undefined === obj) {
console.log(name + ' not found in nodeDefinitions');
return obj; // undefined obj
}
if ('function' === typeof obj.clone) {
return obj.clone();
}
return obj;
}
use
= "USE"
{ return true; }
face
= points:index+ "-1" ws ","? ws
{ return points; }
lastFace
= points:index+ ws ","? ws
{ return points; }
index
= i:int (( ws? ","? " "?) / " "+)
{ return i }
url
= ws begin_array ws quote uri:uri quote ws end_array ws
{ return uri; }
uri
= i:[^"]* dot:"." ext:("jpg" / "jpeg" / "gif" / "wrl")
{ return i + dot + ext; }
quoted_string
= ws quote s:[^"]* quote ws
{ return s.join(''); }
quote
= '"'
/* ----- Core ABNF Rules ----- */
/* See RFC 4234, Appendix B (http://tools.ietf.org/html/rfc4627). */
DIGIT = [0-9]
HEXDIG = [0-9a-f]i