circom/src/construction_phase.js
src/construction_phase.jsRaw
1/*
2 Copyright 2018 0KIMS association.
3
4 This file is part of circom (Zero Knowledge Circuit Compiler).
5
6 circom is a free software: you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 circom is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with circom. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20/*
21
22 The exec functions, return an object of the form:
23
24 {
25 t: "C" or "V" or "S"
26 s: Accumulated Sizes
27 v: [] only when "V" Array of values where each value is:
28 {
29 t: "N", "S", "LC", "QEX", "NQ"
30 v: When "N" its the bigInt of the number
31 coefs: { sIdx: vCoef } when "LC" the values of the coefs
32 a,b,c: a LC object of a quadratic expression.
33 }
34 refId: In case of a variable, the reference of a variable.
35 sIdx: When signal, the sIdx
36 cIdx: When component, the cIdx
37 }
38
39
40
41
42
43 */
44
45const path = require("path");
46const fs = require("fs");
47const assert = require("assert");
48
49const iterateAST = require("./iterateast");
50const utils = require("./utils");
51
52const bigInt = require("big-integer");
53
54const LCAlgebra = require("./lcalgebra");
55const parser = require("../parser/jaz.js").parser;
56
57/* TODO: Add lines information
58
59function setLines(dst, first, last) {
60 last = last || first;
61 dst.first_line = first.first_line;
62 dst.first_column = first.first_column;
63 dst.last_line = last.last_line;
64 dst.last_column = last.last_column;
65}
66*/
67
68module.exports = constructionPhase;
69
70const NQVAL = {t: "V", s:[1,0], v: [{t:"NQ"}]};
71
72function constructionPhase(ctx, srcFile) {
73
74 const fullFileName = srcFile;
75 const fullFilePath = path.dirname(fullFileName);
76
77 const src = fs.readFileSync(fullFileName, "utf8");
78 ctx.ast = parser.parse(src);
79
80 assert(ctx.ast.type == "BLOCK");
81
82 ctx.lc = new LCAlgebra(ctx.field);
83 ctx.filePath= fullFilePath;
84 ctx.fileName= fullFileName;
85 ctx.includedFiles = {};
86 ctx.includedFiles[fullFileName] = src.split("\n");
87
88 ctx.refs = [];
89 createRefs(ctx, ctx.ast);
90 exec(ctx, ctx.ast);
91
92}
93
94function exec(ctx, ast) {
95 if (!ast) {
96 return ctx.throwError(ast, "Null AST");
97 }
98 if (ast.type == "NUMBER") {
99 return execNumber(ctx,ast);
100 } else if (ast.type == "VARIABLE") {
101 return execVariable(ctx, ast);
102 } else if (ast.type == "PIN") {
103 return execPin(ctx, ast);
104 } else if (ast.type == "OP") {
105 if (ast.op == "=") {
106 return execAssignement(ctx, ast);
107 } else if (ast.op == "<--") {
108 return execAssignement(ctx, ast);
109 } else if (ast.op == "<==") {
110 return execSignalAssignConstrain(ctx, ast);
111 } else if (ast.op == "===") {
112 return execConstrain(ctx, ast);
113 } else if (ast.op == "+=") {
114 return execOpOp(ctx, ast, "add", "LEFT");
115 } else if (ast.op == "*=") {
116 return execOpOp(ctx, ast, "mul", "LEFT");
117 } else if (ast.op == "+") {
118 return execOp(ctx, ast, "add", 2);
119 } else if (ast.op == "-") {
120 return execOp(ctx, ast, "sub", 2);
121 } else if (ast.op == "UMINUS") {
122 return execOp(ctx, ast, "neg", 1);
123 } else if (ast.op == "*") {
124 return execOp(ctx, ast, "mul", 2);
125 } else if (ast.op == "%") {
126 return execOp(ctx, ast, "mod", 2);
127 } else if (ast.op == "PLUSPLUSRIGHT") {
128 return execOpOp(ctx, ast, "add", "RIGHT");
129 } else if (ast.op == "PLUSPLUSLEFT") {
130 return execOpOp(ctx, ast, "add", "LEFT");
131 } else if (ast.op == "MINUSMINUSRIGHT") {
132 return execOpOp(ctx, ast, "sub", "RIGHT");
133 } else if (ast.op == "MINUSMINUSLEFT") {
134 return execOpOp(ctx, ast, "sub", "LEFT");
135 } else if (ast.op == "/") {
136 return execOp(ctx, ast, "div", 2);
137 } else if (ast.op == "\\") {
138 return execOp(ctx, ast, "idiv", 2);
139 } else if (ast.op == "**") {
140 return execOp(ctx, ast, "pow", 2);
141 } else if (ast.op == "&") {
142 return execOp(ctx, ast, "band", 2);
143 } else if (ast.op == "|") {
144 return execOp(ctx, ast, "bor", 2);
145 } else if (ast.op == "^") {
146 return execOp(ctx, ast, "bxor", 2);
147 } else if (ast.op == "~") {
148 return execOp(ctx, ast, "bnot", 1);
149 } else if (ast.op == "&&") {
150 return execOp(ctx, ast, "land", 2);
151 } else if (ast.op == "||") {
152 return execOp(ctx, ast, "lor", 2);
153 } else if (ast.op == "!") {
154 return execOp(ctx, ast, "lnot", 1);
155 } else if (ast.op == "<<") {
156 return execOp(ctx, ast, "shl", 2);
157 } else if (ast.op == ">>") {
158 return execOp(ctx, ast, "shr", 2);
159 } else if (ast.op == "<") {
160 return execOp(ctx, ast, "lt", 2);
161 } else if (ast.op == ">") {
162 return execOp(ctx, ast, "gt", 2);
163 } else if (ast.op == "<=") {
164 return execOp(ctx, ast, "leq", 2);
165 } else if (ast.op == ">=") {
166 return execOp(ctx, ast, "geq", 2);
167 } else if (ast.op == "==") {
168 return execOp(ctx, ast, "eq", 2);
169 } else if (ast.op == "!=") {
170 return execOp(ctx, ast, "neq", 2);
171 } else if (ast.op == "?") {
172 return execTerCon(ctx, ast);
173 } else {
174 ctx.throwError(ast, "Invalid operation: " + ast.op);
175 }
176 } else if (ast.type == "DECLARE") {
177 if (ast.declareType == "COMPONENT") {
178 return execDeclareComponent(ctx, ast);
179 } else if ((ast.declareType == "SIGNALIN")||
180 (ast.declareType == "SIGNALOUT")||
181 (ast.declareType == "SIGNAL")) {
182 return execDeclareSignal(ctx, ast);
183 } else if (ast.declareType == "VARIABLE") {
184 return execDeclareVariable(ctx, ast);
185 } else {
186 ctx.throwError(ast, "Invalid declaration: " + ast.declareType);
187 }
188 } else if (ast.type == "FUNCTIONCALL") {
189 return execFunctionCall(ctx, ast);
190 } else if (ast.type == "BLOCK") {
191 return execBlock(ctx, ast);
192 } else if (ast.type == "COMPUTE") {
193 return execCompute(ctx, ast);
194 } else if (ast.type == "FOR") {
195 return execLoop(ctx, ast);
196 } else if (ast.type == "WHILE") {
197 return execLoop(ctx, ast);
198 } else if (ast.type == "IF") {
199 return execIf(ctx, ast);
200 } else if (ast.type == "RETURN") {
201 return execReturn(ctx, ast);
202 } else if (ast.type == "TEMPLATEDEF") {
203 return execTemplateDef(ctx, ast);
204 } else if (ast.type == "FUNCTIONDEF") {
205 return execFunctionDef(ctx, ast);
206 } else if (ast.type == "INCLUDE") {
207 return execInclude(ctx, ast);
208 } else if (ast.type == "ARRAY") {
209 return execArray(ctx, ast);
210 } else {
211 ctx.throwError(ast, "Invalid AST node type: " + ast.type);
212 }
213}
214
215function execNumber(ctx, ast) {
216 return {
217 t: "V",
218 s:[1,0],
219 v: [{
220 t: "N",
221 v: bigInt(ast.value)
222 }]
223 };
224}
225
226
227function execTemplateDef(ctx, ast) {
228 ctx.templates[ast.name] = {
229 block: ast.block,
230 params: ast.params,
231 fileName: ctx.fileName,
232 filePath: ctx.filePath,
233 };
234}
235
236function execFunctionDef(ctx, ast) {
237 ctx.functions[ast.name] = {
238 block: ast.block,
239 params: ast.params,
240 fileName: ctx.fileName,
241 filePath: ctx.filePath
242 };
243}
244
245function execDeclareComponent(ctx, ast) {
246 if (ast.name.type != "VARIABLE") return ctx.throwError( ast, "Invalid component name");
247
248 const sizes=[];
249 for (let i=0; i< ast.name.selectors.length; i++) {
250 const sizeRef = exec(ctx, ast.name.selectors[i]);
251 if (ctx.error) return;
252
253 const size = val(ctx, sizeRef);
254 if (size.t != "N") return ctx.throwError( ast.name.selectors[i], "expected a number");
255
256 sizes.push( size.v.toJSNumber() );
257 }
258
259 let cIdx = ctx.addComponent(ast.name.name, sizes);
260 if (!Array.isArray(cIdx)) cIdx = [cIdx, cIdx+1];
261
262
263 ctx.refs[ast.refId].s = utils.accSizes(sizes);
264 ctx.refs[ast.refId].cIdx = cIdx[0];
265
266 return ctx.refs[ast.refId];
267}
268
269
270function execDeclareSignal(ctx, ast) {
271
272 if (ast.name.type != "VARIABLE") return ctx.throwError(ast, "Invalid component name");
273
274 const sizes=[];
275 for (let i=0; i< ast.name.selectors.length; i++) {
276 const size = exec(ctx, ast.name.selectors[i]);
277 if (ctx.error) return;
278 if (size.s[0] != 1) return ctx.throwError(ast, "Size cannot be an array");
279 if (size.v[0].t != "N") return ctx.throwError(ast, "Size must be declared in construction time");
280 sizes.push( size.v[0].v.toJSNumber() );
281 }
282
283 let sIdx = ctx.addSignal(ast.name.name, sizes);
284 if (!Array.isArray(sIdx)) sIdx = [sIdx, sIdx+1];
285 for (let i=sIdx[0]; i<sIdx[1]; i++) {
286 ctx.signals[i] = {
287 o: 0,
288 e: -1
289 };
290
291 if (ast.declareType == "SIGNALIN") {
292 ctx.signals[i].o |= ctx.IN;
293 ctx.components[ctx.currentComponent].nInSignals+=1;
294 }
295 if (ast.declareType == "SIGNALOUT") {
296 ctx.signals[i].o |= ctx.OUT;
297 }
298 if (ast.private ) {
299 ctx.signals[i].o |= ctx.PRV;
300 }
301 if (ctx.main) {
302 ctx.signals[i].o |= ctx.MAIN;
303 }
304
305 // ctx.components[ctx.currentComponent].signals.push(i);
306 }
307
308 const v = ctx.refs[ast.refId];
309 v.s = utils.accSizes(sizes);
310 v.sIdx = sIdx[0];
311
312 return v;
313}
314
315function execDeclareVariable(ctx, ast) {
316
317 if (ast.name.type != "VARIABLE") return ctx.throwError(ast, "Invalid linear combination name");
318
319 const sizes=[];
320 for (let i=0; i< ast.name.selectors.length; i++) {
321 const size = exec(ctx, ast.name.selectors[i]);
322 if (ctx.error) return;
323 if (size.s[0] != 1) return ctx.throwError(ast, "Size cannot be an array");
324 if (size.v[0].t != "N") return ctx.throwError(ast, "Size must be declared in construction time");
325 sizes.push( size.v[0].v.toJSNumber() );
326 }
327
328 const v = ctx.refs[ast.refId];
329
330 v.s = utils.accSizes(sizes);
331 v.v = new Array(v.s[0]);
332
333 return v;
334}
335
336function execAssignement(ctx, ast) {
337 let left;
338 const leftSels=[];
339 if (ast.values[0].type == "DECLARE") {
340 left = exec(ctx, ast.values[0]);
341 if (ctx.error) return;
342 } else if (ast.values[0].type == "PIN") {
343 left = execPin(ctx, ast.values[0]);
344 if (ctx.error) return;
345 } else {
346 if (!utils.isDefined(ast.values[0].refId)) return ctx.throwError(ast, "Assigning to a non variable");
347 left = ctx.refs[ast.values[0].refId];
348 if (ast.values[0].selectors) {
349 for (let i=0; i< ast.values[0].selectors.length; i++) {
350 const sel = exec(ctx, ast.values[0].selectors[i]);
351 if (ctx.error) return;
352
353 if (sel.s[0] != 1) return ctx.throwError(ast, "Selector cannot be an array");
354 if (sel.v[0].t != "N") return {t: "NQ"};
355
356 leftSels.push( sel.v[0].v.toJSNumber() );
357 }
358 }
359
360 }
361
362 if (!left.s) return ctx.throwError(ast, "variable. not defined yet");
363
364 if (left.t == "C") return execInstantiateComponet(ctx, left, ast.values[1], leftSels);
365 if ((left.t == "S")&&( ["<--", "<==", "-->", "==>"].indexOf(ast.op) < 0)) return ctx.throwError(ast, "Cannot assign to a signal with `=` use <-- or <== ops");
366 if ((left.t == "V")&&( ["<--", "<==", "-->", "==>"].indexOf(ast.op) >= 0)) return ctx.throwError(ast, `Cannot assign to a var with ${ast.op}. use = op`);
367
368 const right = exec(ctx, ast.values[1]);
369 if (ctx.error) return;
370
371 if (!utils.sameSizes(left.s.slice(leftSels.length),right.s)) return ctx.throwError(ast, "Sizes in assignment must be the same");
372
373 let o = 0;
374 for (let i=0; i<leftSels.length; i++) o += leftSels[i]*left.s[i+1];
375 if (left.t == "V") {
376 if (right.t=="V") {
377 for (let i=0; i<right.s[0]; i++) {
378 left.v[o+i]=right.v[i];
379 }
380 } else if (right.t == "S") {
381 for (let i=0; i<right.s[0]; i++) {
382 left.v[o+i]={t: "LC", coefs: {}};
383 left.v[o+i].coefs[right.sIdx+i] = ctx.field.one;
384 }
385 }
386 } else if ( left.t == "S") {
387 if (right.t=="V") {
388 for (let i=0; i<right.s[0]; i++) {
389 const ev = ctx.lc.evaluate(ctx, right.v[i]);
390 setSignalValue(left.sIdx + o +i, ev);
391 }
392 } else if (right.t == "S") {
393 for (let i=0; i<right.s[0]; i++) {
394 joinSignals(left.sIdx + o + i, right.sIdx + i);
395 }
396 }
397 }
398
399 return right;
400
401 function setSignalValue(dSIdx, v) {
402 let sDest = ctx.signals[dSIdx];
403 while (sDest.e>=0) sDest=ctx.signals[sDest.e];
404
405 if (utils.isDefined(sDest.v)) return ctx.throwError(ast, "Signals cannot be assigned twice");
406
407 if (v !== null) {
408 sDest.v = v;
409 }
410 }
411
412 function joinSignals(dIdx, sIdx) {
413 let sDest=ctx.signals[dIdx];
414 let isOut = (sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT);
415 while (sDest.e>=0) {
416 sDest=ctx.signals[sDest.e];
417 isOut = isOut || ((sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT));
418 }
419
420 if (utils.isDefined(sDest.v)) return ctx.throwError(ast, "Signals cannot be assigned twice");
421
422 let sSrc = ctx.signals[sIdx];
423 let isIn = (sSrc.o & ctx.MAIN)&&(sSrc.o & ctx.IN);
424 while (sSrc.e>=0) {
425 sSrc=ctx.signals[sSrc.e];
426 isIn = isIn || ((sSrc.o & ctx.MAIN)&&(sSrc.o & ctx.IN));
427 }
428
429 // Skip if an out is assigned directly to an input.
430 if ((!isIn)||(!isOut)) {
431 sDest.e = sIdx;
432 } else {
433 if (utils.isDefined(sSrc.v)) sDest.v = sSrc.v;
434 }
435 }
436}
437
438
439
440function execInstantiateComponet(ctx, vr, fn, sels) {
441
442 if (fn.type != "FUNCTIONCALL") return ctx.throwError(fn, "Right type of instantiate component must be a function call");
443
444 const templateName = fn.name;
445
446 const template = ctx.templates[templateName];
447 if (!template) return ctx.throwError("Invalid Template");
448
449 const paramValues = [];
450 for (let i=0; i< fn.params.length; i++) {
451 const v = exec(ctx, fn.params[i]);
452 if (ctx.error) return;
453 for (let j=0; j<v.s[0]; j++) {
454 if (v.v[j].t != "N") ctx.throwError("Parameters of a template must be constant");
455 }
456 paramValues.push(v);
457 }
458 if (template.params.length != paramValues.length) ctx.throwError(fn, "Invalid Number of parameters");
459
460 let o=0;
461 for (let i=0; i<sels.length; i++) o += sels[i] * vr.s[i+1];
462 for (let i=o; i<o+vr.s[sels.length]; i++) {
463 instantiateComponent(vr.cIdx+i);
464 }
465
466 function instantiateComponent(cIdx) {
467
468 function extractValue(sizes, arr) {
469 if (sizes[0] == 1) return arr[0].v.toString();
470 const res = [];
471 for (let i=0; i<sizes[0]; i += sizes[1]) {
472 res.push(extractValue(sizes.slice(1), arr.slice(i, i+sizes[1])));
473 }
474 return res;
475 }
476
477 if (ctx.components[cIdx]) return ctx.throwError(fn, "Component already instantiated");
478
479 const oldComponent = ctx.currentComponent;
480 const oldFileName = ctx.fileName;
481 const oldFilePath = ctx.filePath;
482 const oldMain = ctx.main;
483 const oldRefs = ctx.refs;
484
485 if (ctx.currentComponent==-1) {
486 ctx.main=true;
487 } else {
488 ctx.main=false;
489 }
490
491 ctx.currentComponent = cIdx;
492
493 ctx.components[cIdx] = {
494 params: {},
495 names: ctx.newTableName(),
496 nInSignals: 0,
497 template: templateName
498 };
499
500
501 if (template.params.length != paramValues.length) return ctx.throwError(fn, "Invalid number of parameters: " + templateName);
502 ctx.refs = [];
503
504 const scope = {};
505 for (let i=0; i< template.params.length; i++) {
506 ctx.refs.push({
507 t: "V",
508 s: paramValues[i].s,
509 v: paramValues[i].v,
510 refId: i
511 });
512 scope[template.params[i]] = i;
513 ctx.components[cIdx].params[template.params[i]] = extractValue(paramValues[i].s, paramValues[i].v);
514 }
515
516 createRefs(ctx, template.block, scope);
517
518 ctx.fileName = template.fileName;
519 ctx.filePath = template.filePath;
520
521 execBlock(ctx, template.block);
522
523 ctx.refs = oldRefs;
524 ctx.fileName = oldFileName;
525 ctx.filePath = oldFilePath;
526 ctx.currentComponent = oldComponent;
527 ctx.main = oldMain;
528 }
529}
530
531
532function execBlock(ctx, ast) {
533 for (let i=0; i<ast.statements.length; i++) {
534 exec(ctx, ast.statements[i]);
535 if (ctx.returnValue) return;
536 if (ctx.error) return;
537 }
538}
539
540function clone(a) {
541 const res = {
542 t: a.t,
543 s: a.s
544 };
545 if (a.t == "V") {
546 res.v = new Array(a.v.length);
547 for (let i=0; i<a.v.length; i++) res.v[i] = a.v[i];
548 } else if (a.t == "S") {
549 res.sIdx = a.sIdx;
550 } else if (a.t == "C") {
551 res.cIdx = a.cIdx;
552 }
553 return res;
554}
555
556function execFunctionCall(ctx, ast) {
557
558 if (ast.name == "log") {
559 const v = exec(ctx, ast.params[0]);
560 const ev = val(ctx, v, ast);
561 if (ev.v) {
562 console.log(ev.v.toString());
563 } else {
564 console.log(JSON.stringify(ev));
565 }
566 return;
567 }
568 if (ast.name == "assert") {
569 const v = exec(ctx, ast.params[0]);
570 const ev = val(ctx, v, ast);
571 if (ev.isZero()) return ctx.throwError(ast, "Assertion failed");
572 }
573
574 const fnc = ctx.functions[ast.name];
575
576 if (!fnc) return ctx.throwError("Function not defined");
577
578 const paramValues = [];
579 for (let i=0; i< ast.params.length; i++) {
580 const v = exec(ctx, ast.params[i]);
581 if (ctx.error) return;
582
583 paramValues.push(v);
584 }
585
586 if (ast.params.length != paramValues.length) ctx.throwError(ast, "Invalid Number of parameters");
587
588 const oldFileName = ctx.fileName;
589 const oldFilePath = ctx.filePath;
590 const oldRefs = ctx.refs;
591
592 ctx.fileName = fnc.fileName;
593 ctx.filePath = fnc.filePath;
594 ctx.refs = [];
595
596 const scope = {};
597 for (let i=0; i< fnc.params.length; i++) {
598 const entry = clone(paramValues[i]);
599 entry.refId = i;
600 scope[fnc.params[i]] = i;
601 ctx.refs.push(entry);
602 }
603
604 createRefs(ctx, fnc.block, scope);
605
606 execBlock(ctx, fnc.block);
607
608 const res = ctx.returnValue;
609 ctx.returnValue = null;
610
611 ctx.fileName = oldFileName;
612 ctx.filePath = oldFilePath;
613 ctx.refs = oldRefs;
614
615 return res;
616}
617
618function execReturn(ctx, ast) {
619 ctx.returnValue = exec(ctx, ast.value);
620 return;
621}
622
623
624function execVariable(ctx, ast) {
625
626 const v = ctx.refs[ast.refId];
627
628 const sels = [];
629 for (let i=0; i< ast.selectors.length; i++) {
630 const sel = exec(ctx, ast.selectors[i]);
631 if (ctx.error) return;
632 if (sel.s[0] != 1) return ctx.throwError(ast, "Variable selector cannot be an array");
633 if (sel.v[0].t != "N") return NQVAL;
634 sels.push(sel.v[0].v.toJSNumber());
635 }
636
637 let o = 0;
638 let s = v.s[0];
639
640 if (sels.length > v.s.length-2) return ctx.throwError(ast, "Too many selectors");
641 for (let i=0; i<sels.length; i++) {
642 if (sels[i] >= v.s[i] / v.s[i+1]) return ctx.throwError(ast, "Out of Range");
643 if (sels[i] < 0 ) return ctx.throwError(ast, "selector negative");
644 o += sels[i] * v.s[i+1];
645 s = v.s[i+1];
646 }
647
648 if (v.t == "V") {
649 return {
650 t: "V",
651 s: v.s.slice(sels.length),
652 v: v.v.slice(o, o+s)
653 };
654 } else if (v.t == "S") {
655 return {
656 t: "S",
657 s: v.s.slice(sels.length),
658 sIdx: o + v.sIdx
659 };
660 } else if (v.t == "C") {
661 return {
662 t: "C",
663 s: v.s.slice(sels.length),
664 cIdx: o
665 };
666 } else {
667 assert(false);
668 }
669}
670
671
672
673
674function execPin(ctx, ast) {
675 const selsC = [];
676 for (let i=0; i< ast.component.selectors.length; i++) {
677 const sel = exec(ctx, ast.component.selectors[i]);
678 if (ctx.error) return;
679
680 if (sel.s[0] != 1) return ctx.throwError(ast, "Component selector cannot be an array");
681 if (sel.v[0].t != "N") return NQVAL;
682 selsC.push(sel.v[0].v.toJSNumber());
683 }
684
685 const cIdx = ctx.getComponentIdx(ast.component.name, selsC);
686 if (cIdx<0) return ctx.throwError(ast.component, "Component does not exists: "+ast.component.name);
687
688 const selsP = [];
689 for (let i=0; i< ast.pin.selectors.length; i++) {
690 const sel = exec(ctx, ast.pin.selectors[i]);
691 if (ctx.error) return;
692 if (sel.s[0] != 1) return ctx.throwError(ast, "Signal selector cannot be an array");
693 if (sel.v[0].t != "N") return NQVAL;
694 selsP.push(sel.v[0].v.toJSNumber());
695 }
696 const sIdx = ctx.components[cIdx].names.getSignalIdx(ast.pin.name, selsP);
697
698 if (sIdx<0) return ctx.throwError(ast, "Signal not defined:" + buildFullName() );
699 return {
700 t: "S",
701 sIdx: sIdx,
702 s: utils.accSizes(ctx.components[cIdx].names.o[ast.pin.name].sizes).slice(selsP.length)
703 };
704
705 function buildFullName() {
706 return ast.component.name + sels2str(selsC) + "." + ast.pin.name + sels2str(selsP);
707 }
708
709 function sels2str(sels) {
710 let S = "";
711 for (let i=0; i< sels.length; i++) {
712 S += "[" + sels[i] + "]";
713 }
714 return S;
715 }
716}
717
718function execLoop(ctx, ast) {
719
720 if (ast.init) {
721 exec(ctx, ast.init);
722 if (ctx.error) return;
723 }
724
725 let v = exec(ctx, ast.condition);
726 if (ctx.error) return;
727 if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition in loop cannot be an array");
728 if (v.v[0].t != "N") {
729 iterateAST(ast, (ast) => {
730 if (ast.type == "OP") {
731 if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
732 return ctx.throwError(ast.condition, "Constraint inside a calculating block");
733 }
734 }
735 });
736
737 // Assert no constraints
738 return;
739 }
740
741 while ((!v.v[0].v.isZero())&&(!ctx.returnValue)) {
742 exec(ctx, ast.body);
743 if (ctx.error) return;
744
745 if (ast.step) {
746 exec(ctx, ast.step);
747 if (ctx.error) return;
748 }
749
750 v = exec(ctx, ast.condition);
751 if (ctx.error) return;
752 if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition in loop cannot be an array");
753 if (v.v[0].t != "N") return ctx.throwError(ast.condition, "Condition result not a number");
754 }
755}
756
757function execCompute(ctx, ast) {
758 iterateAST(ast, (ast) => {
759 if (ast.type == "OP") {
760 if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
761 return ctx.throwError(ast.condition, "Constraint inside a calculating block");
762 }
763 }
764 });
765}
766
767function execIf(ctx, ast) {
768 let v = exec(ctx, ast.condition);
769 if (ctx.error) return;
770 if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition cannot be an array");
771 if (v.v[0].t != "N") {
772 iterateAST(ast, (ast) => {
773 if (ast.type == "OP") {
774 if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
775 return ctx.throwError(ast.condition, "Constraint inside a calculating block");
776 }
777 }
778 });
779
780 // Assert no constraints
781 return;
782 }
783
784 if (!v.v[0].v.isZero()) {
785 exec(ctx, ast.then);
786 } else {
787 if (ast.else) {
788 exec(ctx, ast.else);
789 }
790 }
791}
792
793
794function execTerCon(ctx, ast) {
795 let v = exec(ctx, ast.values[0]);
796 if (ctx.error) return;
797 if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition cannot be an array");
798 if (v.v[0].t != "N") {
799 iterateAST(ast, (ast) => {
800 if (ast.type == "OP") {
801 if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
802 return ctx.throwError(ast.condition, "Constraint inside a calculating block");
803 }
804 }
805 });
806
807 // Assert no constraints
808 return NQVAL;
809 }
810
811 if (!v.v[0].v.isZero()) {
812 return exec(ctx, ast.values[1]);
813 } else {
814 return exec(ctx, ast.values[2]);
815 }
816}
817
818function execOp(ctx, ast, op, nOps) {
819 const operands = [];
820 for (let i=0; i<nOps; i++) {
821 const a = exec(ctx, ast.values[i]);
822 if (ctx.error) return;
823 const aV = val(ctx, a, ast.values[i]);
824 operands.push(aV);
825 }
826
827 return {
828 t: "V",
829 s: [1,0],
830 v: [ctx.lc[op](...operands)]
831 };
832}
833
834
835function execOpOp(ctx, ast, op, lr) {
836
837 if (ast.values[0].type != "VARIABLE") return ctx.throwError(ast, "incrementing a non variable");
838 if (!utils.isDefined(ast.values[0].refId)) return ctx.throwError(ast, "Assigning to a non variable");
839 const left = ctx.refs[ast.values[0].refId];
840 if (left.t != "V") return ctx.throwError(ast, `Only variables can use ${ast.op} operations`);
841 let leftSels = [];
842 if (ast.values[0].selectors) {
843 for (let i=0; i< ast.values[0].selectors.length; i++) {
844 const sel = exec(ctx, ast.values[0].selectors[i]);
845 if (ctx.error) return;
846
847 if (sel.s[0] != 1) return ctx.throwError(ast, "Selector cannot be an array");
848 if (sel.v[0].t != "N") return {t: "NQ"};
849
850 leftSels.push( sel.v[0].v.toJSNumber() );
851 }
852 }
853 if (!left.s) return ctx.throwError(ast, "variable. not defined yet");
854
855 let o = 0;
856 let s = left.s[0];
857 for (let i=0; i<leftSels.length; i++) {
858 o += leftSels[i]*left.s[i+1];
859 s = left.s[i+1];
860 }
861 if (s != 1) return ctx.throwError(ast, `invalid operator ${ast.op} in an array`);
862 const resBefore = left.v[o];
863
864 let right;
865 if (ast.values[1]) {
866 const rightRef = exec(ctx, ast.values[1]);
867 if (ctx.error) return;
868 right = val(ctx, rightRef);
869 } else {
870 right = {t:"N", v: ctx.field.one};
871 }
872
873 const resAfter = ctx.lc[op](resBefore, right);
874 left.v[o] = resAfter;
875
876 if (lr == "RIGHT") {
877 return {
878 t: "V",
879 s: [1,0],
880 v: [resBefore]
881 };
882 } else {
883 return {
884 t: "V",
885 s: [1,0],
886 v: [resAfter]
887 };
888 }
889}
890
891// Extract the value of a reference.
892// Must be a single value. (cannot be an array)
893// The result is an LCAlgebra compatible object {t: ["N"|"LC"|"QEX"|"NQ"], [coefs|v|a,b,c]}
894function val(ctx, a, ast) {
895 if (a.s[0] != 1) return ctx.throwError(ast, "Array not allowed");
896 if (a.t=="V") {
897 return a.v[0];
898 } else if (a.t=="S") {
899 const res = {
900 t: "LC",
901 coefs: {}
902 };
903 let sIdx = a.sIdx;
904 while (ctx.signals[sIdx].e >= 0) sIdx = ctx.signals[sIdx].e;
905 res.coefs[sIdx] = ctx.field.one;
906 return res;
907 } else {
908 ctx.throwError(ast, "Invalid type: " + a.t);
909 }
910}
911
912function execConstrain(ctx, ast) {
913 ast.fileName = ctx.fileName;
914 ast.filePath = ctx.filePath;
915 const a = exec(ctx, ast.values[0]);
916 if (ctx.error) return;
917 const aV = val(ctx, a, ast.values[0]);
918 const b = exec(ctx, ast.values[1]);
919 if (ctx.error) return;
920 const bV = val(ctx, b, ast.values[1]);
921
922 const res = ctx.lc.sub(aV,bV);
923 if (res.type == "NQ") return ctx.throwError(ast, "Non Quadratic constraint");
924
925 if (!ctx.lc.isZero(res)) {
926 ctx.constraints.push(ctx.lc.toQEX(res));
927 if ((ctx.constraints.length % 10000 == 0)&&(ctx.constraints.length>0)) console.log("Constraints: " + ctx.constraints.length);
928 }
929
930 return a;
931}
932
933function execSignalAssignConstrain(ctx, ast) {
934 const v = execAssignement(ctx,ast);
935 if (ctx.error) return;
936 execConstrain(ctx, ast);
937 if (ctx.error) return;
938 return v;
939}
940
941function execInclude(ctx, ast) {
942 if (ast.block) {
943
944 const oldFilePath = ctx.filePath;
945 const oldFileName = ctx.fileName;
946
947 ctx.filePath = ast.filePath;
948 ctx.fileName = ast.fileName;
949
950 exec(ctx, ast.block); // Process only one.
951
952 ctx.filePath = oldFilePath;
953 ctx.fileName = oldFileName;
954 }
955}
956
957function execArray(ctx, ast) {
958 const res = {t: "V", v:[]};
959
960
961 let subSize = null;
962
963 for (let i=0; i<ast.values.length; i++) {
964 const e = exec(ctx, ast.values[i]);
965 if (i==0) {
966 subSize=e.s;
967 } else {
968 if (!utils.sameSizes(subSize, e.s)) return ctx.throwError(ast, "Array mus be homogeneous");
969 }
970 if (e.t == "V") {
971 for (let j=0; j<e.v.length;j++) res.v.push(e.v[j]);
972 } else if (e.t == "S") {
973 for (let j=0; j<e.v.length;j++) {
974 const sv = {t: "LC", coefs: {}};
975 sv.coefs[e.sIdx+j] = ctx.field.one;
976 res.v.push(sv);
977 }
978 } else {
979 return ctx.throwError(ast, "Type not allowed in array");
980 }
981 }
982
983 res.s = [ast.values.length*subSize[0], ...subSize];
984
985 return res;
986}
987
988function createRefs(ctx, ast, scope) {
989 const scopeLabels = [];
990 const scopes = scope ? [scope] : [{}];
991 iterateAST(ast, (ast, level) => {
992 while ((scopeLabels.length>0)&&(!level.startsWith(scopeLabels[scopeLabels.length-1]))) {
993 scopes.pop();
994 scopeLabels.pop();
995 }
996 if (ast.type == "DECLARE") {
997 if (ast.declareType == "COMPONENT") {
998 ast.refId = define(ast.name.name, {t: "C"});
999 } else if ((ast.declareType == "SIGNALIN")||
1000 (ast.declareType == "SIGNALOUT")||
1001 (ast.declareType == "SIGNAL")) {
1002 ast.refId = define(ast.name.name, {t: "S"});
1003 } else if (ast.declareType == "VARIABLE") {
1004 ast.refId = define(ast.name.name, {t: "V", s: null});
1005 } else {
1006 return ctx.throwError(ast, "Invalid declaration: " + ast.declareType);
1007 }
1008 } else if (["BLOCK", "FOR", "IF", "WHILE", "COMPUTE"].indexOf(ast.type) >= 0) {
1009 scopeLabels.push(level);
1010 scopes.push({});
1011 } else if (ast.type == "TEMPLATEDEF") {
1012 ast.refId = define(ast.name, {t: "T"});
1013 } else if (ast.type == "FUNCTIONDEF") {
1014 ast.refId = define(ast.name, {t: "F"});
1015 } else if (ast.type == "INCLUDE") {
1016 const incFileName = path.resolve(ctx.filePath, ast.file);
1017 const incFilePath = path.dirname(incFileName);
1018
1019 ctx.includedFiles = ctx.includedFiles || [];
1020 if (ctx.includedFiles[incFileName]) {
1021 ctx.bloc = null; // Include already included...
1022 return;
1023 }
1024
1025 const src = fs.readFileSync(incFileName, "utf8");
1026
1027 if (!src) return ctx.throwError(ast, "Include file not found: "+incFileName);
1028
1029 ctx.includedFiles[incFileName] = src.split("\n");
1030
1031 ast.block = parser.parse(src);
1032 ast.filePath = incFilePath;
1033 ast.fileName = incFileName;
1034
1035 const oldFilePath = ctx.filePath;
1036 const oldFileName = ctx.fileName;
1037 ctx.filePath = incFilePath;
1038 ctx.fileName = incFileName;
1039
1040 scopes.push({});
1041 createRefs(ctx, ast.block);
1042 scopes.pop();
1043
1044 ctx.filePath = oldFilePath;
1045 ctx.fileName = oldFileName;
1046 } else if (ast.type == "VARIABLE") {
1047 ast.refId = name2ref(ast.name);
1048 }
1049
1050 function define(name, entry) {
1051 if (scopes[scopes.length-1][name]) return ctx.throwError(ast, `Name already defined: ${name}`);
1052 for (let i=scopes.length-2; i>=0; i--) {
1053 if (scopes[i][name]) ctx.logWarning(ast, `Shadowing variable: ${name}`);
1054 }
1055 const refId = ctx.refs.length;
1056 entry.refId = refId;
1057 ctx.refs.push(entry);
1058 scopes[scopes.length-1][name] = refId;
1059 return refId;
1060 }
1061
1062 function name2ref(n) {
1063 for (let i=scopes.length-1; i>=0; i--) {
1064 if (typeof scopes[i][n] !== "undefined") return scopes[i][n];
1065 }
1066 return -1;
1067 }
1068
1069
1070 });
1071
1072}
1073
1074