Skip to content

Commit 2c1013c

Browse files
committed
feat(jstree): type inference version 1
1 parent af0ecea commit 2c1013c

File tree

3 files changed

+137
-25
lines changed

3 files changed

+137
-25
lines changed

README.md

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ this libary is going to provide you a simple way of writing typed js code.
1212
this code can run and tested directly with js.
1313
but with types it can be transpiled to other languages, too.
1414

15-
a type checker is in process, too. there will be a simple checking without type inference,
16-
this can be archived with typescript, but not at runtime.
15+
type inference is supported by type checker.
1716

1817
| environment | jsto... checker | typescript |
1918
| ------------------------ | :-------------: | :--------: |
@@ -25,9 +24,7 @@ this can be archived with typescript, but not at runtime.
2524
| ----------- | :-------------: | :--------: |
2625
| functions |||
2726
| arguments |||
28-
| prototypes |||
29-
| classes |||
30-
| attributes |||
27+
| classes |||
3128
| methods |||
3229
| variables |||
3330
| arithmetics |||
@@ -37,7 +34,7 @@ this can be archived with typescript, but not at runtime.
3734
- [x] generate JSTree out of js
3835
- ESTree + type annotations
3936
- [ ] index.d.ts file with generics for typescript
40-
- [ ] type inference
37+
- [x] type inference
4138
- [ ] jstoGLSL transpiler great for performance
4239
- [x] generate valid glsl code
4340
- [ ] js transformations to glsl
@@ -53,17 +50,12 @@ this can be archived with typescript, but not at runtime.
5350
- [x] painting (slowly) to image buffer
5451
- [x] vector arithmetic (component wise) operator support
5552
- [x] partly matrix algebraic operator support
56-
- [ ] multithreading
5753
- [ ] typesafety
5854

5955
in future
6056

61-
- [ ] jstoWASM transpiler great for headless rendering
62-
- [ ] first iteration implement glsl behavior (vector and matrix)
63-
inspired by <https://github.com/AssemblyScript/assemblyscript>
64-
use decompiler to compare <https://v8.dev/blog/wasm-decompile>
65-
- [ ] jstoWASM simulator
66-
- [ ] what other languages do we need?
57+
- [ ] jstoWorker
58+
- [ ] transpile js code without overhead of types and arithmetics
6759

6860
# JSTree extended from ESTree
6961

src/jstree.js

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import * as acorn from 'acorn';
22

3-
function handleParam(param) {
3+
function handleParam(param, options) {
44
const { type, left, right } = param;
55
if (type !== 'AssignmentPattern') {
66
throw new Error(`handleParam() no type defined for ${param}`);
77
}
88
if (right.type === 'Identifier') {
99
const typeAnnotation = right.name;
10-
return { ...left, typeAnnotation };
10+
const p = { ...left, typeAnnotation };
11+
options.scope[left.name] = typeAnnotation;
12+
return p;
1113
} else if (right.type === 'CallExpression') {
1214
param.left.typeAnnotation = right.callee.name;
1315
[param.right] = right.arguments;
16+
options.scope[param.left.name] = param.left.typeAnnotation;
1417
return param;
1518
}
1619
throw new Error(`dont know ${right}`);
@@ -28,7 +31,7 @@ function handleParams(fn, options) {
2831
[param.right] = right.arguments;
2932
}
3033

31-
return handleParam(param);
34+
return handleParam(param, options);
3235
});
3336

3437
let { body } = fn;
@@ -41,8 +44,22 @@ function handleParams(fn, options) {
4144
return { ...fn, body, params };
4245
}
4346

47+
function extractFromScope(scope, name, args) {
48+
if (!scope) {
49+
return name;
50+
}
51+
const sc = scope[name];
52+
if (!sc) {
53+
return name;
54+
}
55+
if (typeof sc === 'function') {
56+
return sc.apply(undefined, args.map(({ name }) => scope[name]));
57+
}
58+
return sc;
59+
}
60+
4461
function extractType(node, target, options) {
45-
const { qualifiers, integer, float, string, boolean } = options;
62+
const { qualifiers, integer, float, string, boolean, scope, operators } = options;
4663
const { type, name, callee, arguments: args, value, raw } = node;
4764

4865
if (type === 'CallExpression' || type === 'NewExpression') {
@@ -73,13 +90,16 @@ function extractType(node, target, options) {
7390
} else {
7491

7592
const typeAnnotation = callee.name;
76-
77-
78-
target.typeAnnotation = typeAnnotation;
93+
target.typeAnnotation = extractFromScope(scope, typeAnnotation, args);
7994
target.newInit = node;
8095
}
8196
} else if (type === 'Identifier') {
82-
target.typeAnnotation = name;
97+
if (scope && scope[name]) {
98+
target.typeAnnotation = extractFromScope(scope, name, []);
99+
target.newInit = node;
100+
} else {
101+
target.typeAnnotation = name;
102+
}
83103
} else if (type === 'NumericLiteral' || type === 'Literal') {
84104
if (typeof value === 'number') {
85105
if (raw.indexOf('.') < 0) {
@@ -113,8 +133,14 @@ function extractType(node, target, options) {
113133
value: operator === '-' ? -value : value
114134
};
115135
} else if (type === 'ArrowFunctionExpression') {
116-
target.newInit = handleParams(node, options);
136+
const newScope = { ...scope };
137+
target.newInit = handleParams(node, { ...options, scope: newScope });
117138
target.newInit.returnType = 'void';
139+
} else if (operators && type === 'BinaryExpression') {
140+
const left = extractFromScope(scope, node.left.name, []);
141+
const right = extractFromScope(scope, node.right.name, []);
142+
target.typeAnnotation = operators(left, node.operator, right);
143+
target.newInit = node;
118144
} else {
119145
target.newInit = node;
120146
}
@@ -160,6 +186,9 @@ function handleNode(node, options) {
160186
const { newInit = null, typeAnnotation = null, qualifier = null } = extractType(init, {}, options);
161187
node.id = { ...node.id, typeAnnotation, qualifier };
162188
node.init = newInit;
189+
190+
options.scope[node.id.name] = typeAnnotation;
191+
163192
return node;
164193
}
165194

@@ -169,10 +198,10 @@ function handleNode(node, options) {
169198
return node;
170199
}
171200

172-
export function parse(input, { qualifiers = [], float = 'Number', integer = float, string = 'String', boolean = 'Boolean', locations = false, ranges = false, ...options } = {}) {
201+
export function parse(input, { qualifiers = [], float = 'Number', integer = float, string = 'String', boolean = 'Boolean', locations = false, ranges = false, operators, scope = {}, ...options } = {}) {
173202
// TODO: use onToken !!!!
174203
const ast = acorn.parse(input, { ...options, locations, ranges, ecmaVersion: 6 });
175-
const node = handleNode(ast, { qualifiers, integer, float, string, boolean });
204+
const node = handleNode(ast, { qualifiers, integer, float, string, boolean, scope, operators });
176205

177206
return node;
178207
}

test/jstree.js

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ describe('jstree autodetect primitive tests', () => {
411411
assert.equal(init.raw, 'true');
412412
});
413413

414-
it('works with cls', () => {
414+
it('works with class', () => {
415415

416416
const node = parse(`
417417
let MyType = cls({
@@ -435,4 +435,95 @@ describe('jstree autodetect primitive tests', () => {
435435
assert.equal(vNormal.typeAnnotation, 'Vec4');
436436
assert.equal(vNormal.key.name, 'vNormal');
437437
});
438+
439+
it('auto detects type for inline variables', () => {
440+
const node = parse(`let x = Vec2((y = Vec2()) => {
441+
let foo = y;
442+
return foo;
443+
});`);
444+
445+
const [declarator] = node.body[0].declarations[0].init.body.body[0].declarations;
446+
const { id, init } = declarator;
447+
448+
assert.equal(id.type, 'Identifier');
449+
assert.equal(id.typeAnnotation, 'Vec2');
450+
assert.equal(id.name, 'foo');
451+
assert.equal(id.qualifier, null);
452+
453+
assert.equal(init.type, 'Identifier');
454+
assert.equal(init.name, 'y');
455+
456+
});
457+
458+
it('auto detects type from builtin function', () => {
459+
const node = parse(`let x = Vec2((y = Vec2()) => {
460+
let foo = normalize(y);
461+
return foo;
462+
});`, { scope: {
463+
normalize: (arg) => arg
464+
} });
465+
466+
const [declarator] = node.body[0].declarations[0].init.body.body[0].declarations;
467+
const { id, init } = declarator;
468+
469+
assert.equal(id.type, 'Identifier');
470+
assert.equal(id.typeAnnotation, 'Vec2');
471+
assert.equal(id.name, 'foo');
472+
assert.equal(id.qualifier, null);
473+
474+
assert.equal(init.type, 'CallExpression');
475+
assert.equal(init.callee.name, 'normalize');
476+
477+
});
478+
479+
it('auto detects type from mathematical calculations', () => {
480+
const node = parse(`let x = Vec2((y = Vec2(), z = Mat3()) => {
481+
let foo = z * z;
482+
let bar = y * foo;
483+
return foo;
484+
});`, { operators: (left, operator, right) => {
485+
if (left === 'Mat3' && right === 'Mat3') {
486+
return 'Mat3';
487+
} if (left === 'Vec2' && right === 'Mat3') {
488+
return 'Vec2';
489+
}
490+
} });
491+
492+
const [decl1] = node.body[0].declarations[0].init.body.body[0].declarations;
493+
const [decl2] = node.body[0].declarations[0].init.body.body[1].declarations;
494+
495+
const { id: id1 } = decl1;
496+
const { id: id2 } = decl2;
497+
498+
assert.equal(id1.type, 'Identifier');
499+
assert.equal(id1.typeAnnotation, 'Mat3');
500+
assert.equal(id1.name, 'foo');
501+
assert.equal(id1.qualifier, null);
502+
503+
assert.equal(id2.type, 'Identifier');
504+
assert.equal(id2.typeAnnotation, 'Vec2');
505+
assert.equal(id2.name, 'bar');
506+
assert.equal(id2.qualifier, null);
507+
508+
});
509+
510+
it('explicit type priority over auto detects type', () => {
511+
const node = parse(`let x = Vec2((y = Vec2()) => {
512+
let foo = Vec3(normalize(y));
513+
return foo;
514+
});`, { scope: {
515+
normalize: (arg) => arg
516+
} });
517+
518+
const [declarator] = node.body[0].declarations[0].init.body.body[0].declarations;
519+
const { id, init } = declarator;
520+
521+
assert.equal(id.type, 'Identifier');
522+
assert.equal(id.typeAnnotation, 'Vec3');
523+
assert.equal(id.name, 'foo');
524+
assert.equal(id.qualifier, null);
525+
526+
assert.equal(init.type, 'CallExpression');
527+
assert.equal(init.callee.name, 'Vec3');
528+
});
438529
});

0 commit comments

Comments
 (0)