Skip to content

Commit 1d21c6c

Browse files
authored
Merge pull request #17 from nearprotocol/bindgen-sugar
Implement encode/decode sugar for generated bindings
2 parents 906b8fd + 6ebfaeb commit 1d21c6c

File tree

9 files changed

+436
-637
lines changed

9 files changed

+436
-637
lines changed

dist/assemblyscript.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/assemblyscript.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/definitions.ts

Lines changed: 137 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import {
3434
indent
3535
} from "./util";
36+
import { Source, NodeKind, ImportStatement, DeclarationStatement, ExportStatement } from "./ast";
3637

3738
/** Walker base class. */
3839
abstract class ExportsWalker {
@@ -140,6 +141,9 @@ export class NEARBindingsBuilder extends ExportsWalker {
140141
private sb: string[] = [];
141142
private generatedEncodeFunctions = new Set<string>();
142143
private generatedDecodeFunctions = new Set<string>();
144+
private exportedClasses: Class[] = [];
145+
private exportedFunctions: Function[] = [];
146+
private filesByImport = new Map<string, string>();
143147

144148
static build(program: Program): string {
145149
return new NEARBindingsBuilder(program).build();
@@ -154,14 +158,33 @@ export class NEARBindingsBuilder extends ExportsWalker {
154158
}
155159

156160
visitClass(element: Class): void {
157-
// Do nothing
161+
if (!element.is(CommonFlags.EXPORT)) {
162+
return;
163+
}
164+
this.exportedClasses.push(element);
158165
}
159166

160167
visitFunction(element: Function): void {
168+
if (!element.is(CommonFlags.EXPORT)) {
169+
return;
170+
}
171+
this.exportedFunctions.push(element);
161172
this.generateArgsParser(element);
162173
this.generateWrapperFunction(element);
163174
}
164175

176+
visitInterface(element: Interface): void {
177+
// Do nothing
178+
}
179+
180+
visitField(element: Field): void {
181+
throw new Error("Shouldn't be called");
182+
}
183+
184+
visitNamespace(element: Element): void {
185+
// Do nothing
186+
}
187+
165188
private generateArgsParser(element: Function) {
166189
let signature = element.signature;
167190
let fields = signature.parameterNames ? signature.parameterNames.map((paramName, i) => {
@@ -175,7 +198,7 @@ export class NEARBindingsBuilder extends ExportsWalker {
175198
`);
176199
if (signature.parameterNames) {
177200
fields.forEach((field) => {
178-
this.sb.push(`__near_param_${field.simpleName}: ${field.type};`);
201+
this.sb.push(`__near_param_${field.simpleName}: ${this.wrappedTypeName(field.type)};`);
179202
});
180203
this.generateHandlerMethods("this.__near_param_", fields);
181204
} else {
@@ -196,9 +219,9 @@ export class NEARBindingsBuilder extends ExportsWalker {
196219
handler.decoder = new JSONDecoder<__near_ArgsParser_${element.simpleName}>(handler);
197220
handler.decoder.deserialize(json);`);
198221
if (returnType.toString() != "void") {
199-
this.sb.push(`let result = ${element.simpleName}(`);
222+
this.sb.push(`let result = wrapped_${element.simpleName}(`);
200223
} else {
201-
this.sb.push(`${element.simpleName}(`);
224+
this.sb.push(`wrapped_${element.simpleName}(`);
202225
}
203226
if (signature.parameterNames) {
204227
this.sb.push(signature.parameterNames.map(paramName => `handler.__near_param_${paramName}`).join(","));
@@ -238,7 +261,7 @@ export class NEARBindingsBuilder extends ExportsWalker {
238261
this.sb.push("setNull(name: string): void {");
239262
fields.forEach((field) => {
240263
this.sb.push(`if (name == "${field.simpleName}") {
241-
${valuePrefix}${field.simpleName} = <${field.type.toString()}>null;
264+
${valuePrefix}${field.simpleName} = <${this.wrappedTypeName(field.type)}>null;
242265
return;
243266
}`);
244267
});
@@ -272,7 +295,7 @@ export class NEARBindingsBuilder extends ExportsWalker {
272295
fields.forEach((field) => {
273296
if (!(field.type.toString() in this.typeMapping)) {
274297
this.sb.push(`if (name == "${field.simpleName}") {
275-
${valuePrefix}${field.simpleName} = __near_decode_${this.encodeType(field.type)}(this.buffer, this.decoder.state);
298+
${valuePrefix}${field.simpleName} = <${field.type}>__near_decode_${this.encodeType(field.type)}(this.buffer, this.decoder.state);
276299
return false;
277300
}`);
278301
}
@@ -295,7 +318,7 @@ export class NEARBindingsBuilder extends ExportsWalker {
295318
}`);
296319
} else {
297320
this.sb.push(`pushObject(name: string): bool {
298-
${valuePrefix}.push(__near_decode_${this.encodeType(fieldType)}(this.buffer, this.decoder.state));
321+
${valuePrefix}.push(<${fieldType}>__near_decode_${this.encodeType(fieldType)}(this.buffer, this.decoder.state));
299322
return false;
300323
}
301324
pushArray(name: string): bool {
@@ -304,12 +327,13 @@ export class NEARBindingsBuilder extends ExportsWalker {
304327
this.handledRoot = true;
305328
return true;
306329
}
307-
${valuePrefix}.push(__near_decode_${this.encodeType(fieldType)}(this.buffer, this.decoder.state));
330+
${valuePrefix}.push(<${fieldType}>__near_decode_${this.encodeType(fieldType)}(this.buffer, this.decoder.state));
308331
return false;
309332
}`);
310333
}
311334
}
312335

336+
313337
private generateEncodeFunction(type: Type) {
314338
if (!type.classReference) {
315339
return;
@@ -321,12 +345,17 @@ export class NEARBindingsBuilder extends ExportsWalker {
321345
}
322346
this.generatedEncodeFunctions.add(typeName);
323347

348+
let methodName = `__near_encode_${typeName}`;
349+
if (this.tryUsingImport(type, methodName)) {
350+
return;
351+
}
352+
324353
if (this.isArrayType(type)) {
325354
// Array
326355
this.generateEncodeFunction(type.classReference.typeArguments![0]);
327356

328357
this.sb.push(`export function __near_encode_${typeName}(
329-
value: ${type.toString()},
358+
value: ${this.wrappedTypeName(type)},
330359
encoder: JSONEncoder): void {`);
331360
this.sb.push(`for (let i = 0; i < value.length; i++) {`);
332361
this.generateFieldEncoder(type.classReference.typeArguments![0], "null", "value[i]");
@@ -339,7 +368,7 @@ export class NEARBindingsBuilder extends ExportsWalker {
339368
});
340369

341370
this.sb.push(`export function __near_encode_${typeName}(
342-
value: ${type.toString()},
371+
value: ${this.wrappedTypeName(type)},
343372
encoder: JSONEncoder): void {`);
344373
this.getFields(type.classReference).forEach((field) => {
345374
let fieldType = field.type;
@@ -352,13 +381,31 @@ export class NEARBindingsBuilder extends ExportsWalker {
352381
this.sb.push("}");
353382
}
354383

384+
private tryUsingImport(type: Type, methodName: string): bool {
385+
let importedFile = this.filesByImport.get(type.classReference!.simpleName);
386+
if (importedFile) {
387+
if (this.hasExport(importedFile, methodName)) {
388+
this.sb.push(`import { ${methodName} } from "${importedFile}";`);
389+
return true;
390+
}
391+
}
392+
return false;
393+
}
394+
395+
private hasExport(importedFile: string, name: string): bool {
396+
let importedSource = this.program.sources.filter(
397+
s => "./" + s.normalizedPath == importedFile + ".ts")[0];
398+
399+
return this.getExports(importedSource).filter(d => d.name.text == name).length > 0;
400+
}
401+
355402
private generateHandler(type: Type) {
356403
let typeName = this.encodeType(type);
357404
this.sb.push(`export class __near_JSONHandler_${typeName} extends ThrowingJSONHandler {
358405
buffer: Uint8Array;
359406
decoder: JSONDecoder<__near_JSONHandler_${typeName}>;
360407
handledRoot: boolean = false;
361-
value: ${type} = new ${type}();`);
408+
value: ${this.wrappedTypeName(type)} = new ${this.wrappedTypeName(type)}();`);
362409
if (this.isArrayType(type)) {
363410
this.generateArrayHandlerMethods("this.value", type.classReference!.typeArguments![0]);
364411
} else {
@@ -367,6 +414,22 @@ export class NEARBindingsBuilder extends ExportsWalker {
367414
this.sb.push("}\n");
368415
}
369416

417+
private wrappedTypeName(type: Type): string {
418+
if (!type.classReference) {
419+
return type.toString();
420+
}
421+
let cls = type.classReference;
422+
if (this.exportedClasses.indexOf(cls) != -1) {
423+
return "wrapped_" + cls.simpleName;
424+
}
425+
if (cls.typeArguments && cls.typeArguments.length > 0) {
426+
return cls.prototype.simpleName + "<" +
427+
cls.typeArguments.map(argType => this.wrappedTypeName(argType)).join(", ") +
428+
">"
429+
}
430+
return cls.simpleName;
431+
}
432+
370433
private generateDecodeFunction(type: Type) {
371434
if (!type.classReference) {
372435
return;
@@ -378,6 +441,11 @@ export class NEARBindingsBuilder extends ExportsWalker {
378441
}
379442
this.generatedDecodeFunctions.add(typeName);
380443

444+
let methodName = `__near_decode_${typeName}`;
445+
if (this.tryUsingImport(type, methodName)) {
446+
return;
447+
}
448+
381449
this.generateHandler(type);
382450
if (this.isArrayType(type)) {
383451
// Array
@@ -390,7 +458,7 @@ export class NEARBindingsBuilder extends ExportsWalker {
390458
}
391459

392460
this.sb.push(`export function __near_decode_${typeName}(
393-
buffer: Uint8Array, state: DecoderState): ${type} {
461+
buffer: Uint8Array, state: DecoderState):${this.wrappedTypeName(type)} {
394462
let handler = new __near_JSONHandler_${typeName}();
395463
handler.buffer = buffer;
396464
handler.decoder = new JSONDecoder<__near_JSONHandler_${typeName}>(handler);
@@ -406,7 +474,7 @@ export class NEARBindingsBuilder extends ExportsWalker {
406474
let pushType = this.isArrayType(fieldType) ? "Array" : "Object";
407475
this.sb.push(`if (${sourceExpr} != null) {
408476
encoder.push${pushType}(${fieldExpr});
409-
__near_encode_${this.encodeType(fieldType)}(${sourceExpr}, encoder);
477+
__near_encode_${this.encodeType(fieldType)}(<${fieldType}>${sourceExpr}, encoder);
410478
encoder.pop${pushType}();
411479
} else {
412480
encoder.setNull(${fieldExpr});
@@ -444,23 +512,25 @@ export class NEARBindingsBuilder extends ExportsWalker {
444512
return <Field[]>[...element.members.values()].filter(member => member instanceof Field);
445513
}
446514

447-
visitInterface(element: Interface): void {
448-
// Do nothing
449-
}
515+
build(): string {
516+
let mainSource = this.program.sources
517+
.filter(s => s.normalizedPath.indexOf("~lib") != 0)[0];
518+
this.copyImports(mainSource);
450519

451-
visitField(element: Field): void {
452-
throw new Error("Shouldn't be called");
453-
}
520+
this.walk();
454521

455-
visitNamespace(element: Element): void {
456-
// Do nothing
457-
}
522+
this.exportedClasses.forEach(c => {
523+
this.generateEncodeFunction(c.type);
524+
this.generateDecodeFunction(c.type);
525+
});
458526

459-
build(): string {
460-
this.sb.push(`
527+
let allExported = (<Element[]>this.exportedClasses).concat(<Element[]>this.exportedFunctions);
528+
let allImportsStr = allExported.map(c => `${c.simpleName} as wrapped_${c.simpleName}`).join(", ");
529+
this.sb = [`
461530
import { near } from "./near";
462531
import { JSONEncoder} from "./json/encoder"
463532
import { JSONDecoder, ThrowingJSONHandler, DecoderState } from "./json/decoder"
533+
import {${allImportsStr}} from "./${mainSource.normalizedPath.replace(".ts", "")}";
464534
465535
// Runtime functions
466536
@external("env", "return_value")
@@ -469,13 +539,51 @@ export class NEARBindingsBuilder extends ExportsWalker {
469539
declare function input_read_len(): u32;
470540
@external("env", "input_read_into")
471541
declare function input_read_into(ptr: usize): void;
472-
`);
473-
let mainSource = this.program.sources
474-
.filter(s => s.normalizedPath.indexOf("~lib") != 0)[0];
475-
this.sb.push(mainSource.text);
476-
this.walk();
542+
`].concat(this.sb);
543+
this.exportedClasses.forEach(c => {
544+
this.sb.push(`export class ${c.simpleName} extends ${this.wrappedTypeName(c.type)} {
545+
static decode(json: Uint8Array): ${c.simpleName} {
546+
return <${c.simpleName}>__near_decode_${this.encodeType(c.type)}(json, null);
547+
}
548+
549+
encode(): Uint8Array {
550+
let encoder: JSONEncoder = new JSONEncoder();
551+
encoder.pushObject(null);
552+
__near_encode_${this.encodeType(c.type)}(<${c.simpleName}>this, encoder);
553+
encoder.popObject();
554+
return encoder.serialize();
555+
}
556+
}`);
557+
})
477558
return this.sb.join("\n");
478559
}
560+
561+
private copyImports(mainSource: Source): any {
562+
this.getImports(mainSource).forEach(statement => {
563+
if (statement.declarations) {
564+
let declarationsStr = statement.declarations!
565+
.map(declaration => `${declaration.externalName.text} as ${declaration.name.text}`)
566+
.join(",");
567+
this.sb.push(`import {${declarationsStr}} from "${statement.path.value}";`);
568+
statement.declarations.forEach(d => {
569+
this.filesByImport.set(d.name.text, statement.path.value);
570+
});
571+
}
572+
});
573+
}
574+
575+
private getImports(source: Source): ImportStatement[] {
576+
return <ImportStatement[]>source.statements
577+
.filter(statement => statement.kind == NodeKind.IMPORT);
578+
}
579+
580+
private getExports(source: Source): DeclarationStatement[] {
581+
let declarations = <DeclarationStatement[]>source.statements
582+
.filter(statement =>
583+
statement.kind == NodeKind.FUNCTIONDECLARATION ||
584+
statement.kind == NodeKind.CLASSDECLARATION);
585+
return declarations.filter(d => d.isTopLevelExport);
586+
}
479587
}
480588

481589
/** A WebIDL definitions builder. */

tests/near-bindgen/main.ts

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,13 @@
11
import "allocator/arena";
2-
// TODO: Why cannot import from index?
3-
// import { JSONEncoder, JSONDecoder } from "./json";
4-
import { JSONEncoder } from "./json/encoder";
5-
import { JSONDecoder, DecoderState, JSONHandler, ThrowingJSONHandler } from "./json/decoder";
2+
3+
// These imports need to be copied properly
64
import { near } from "./near"
5+
import { near as bazinga_near } from "./near"
6+
import { FooBar, ContainerClass, AnotherContainerClass } from "./model_near";
77

88
@external("env", "log")
99
declare function log(str: string): void;
1010

11-
export class FooBar {
12-
foo: i32 = 0;
13-
bar: i32 = 1;
14-
flag: bool;
15-
baz: string = "123";
16-
//foobar: Uint8Array;
17-
arr: Array<Array<string>>;
18-
}
19-
20-
export class ContainerClass {
21-
foobar: FooBar
22-
}
23-
24-
export class AnotherContainerClass {
25-
foobar: FooBar
26-
}
27-
2811
export function doNothing(): void {
2912

3013
}
@@ -41,4 +24,4 @@ export function getFoobar(container: ContainerClass): AnotherContainerClass {
4124

4225
export function convertFoobars(foobars: Array<FooBar>): Array<ContainerClass> {
4326
return foobars.map<ContainerClass>((it: FooBar, i: i32, arr: Array<FooBar>): ContainerClass => { let container = new ContainerClass(); container.foobar = it; return container; });
44-
}
27+
}

0 commit comments

Comments
 (0)