Skip to content

Commit ed87cb6

Browse files
andreiradutomblind
authored andcommitted
Generator functions (#384)
* -added generateAnnonymousIdentifier * -added support to transpile generator functions and yield calls * -added tests * -fixed hoisting for generator functions * -cleaned up formatting and moved the return logic in the generator itself, instead of yield/return * -added error check * -removed superfluous check * -underscodes++ * -removed unused function * -missing local * -added generator for...of test * -removed unused function * -cleaned up tests * -spacing
1 parent ccb2338 commit ed87cb6

File tree

3 files changed

+223
-9
lines changed

3 files changed

+223
-9
lines changed

src/LuaAST.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,12 @@ export function createIdentifier(
813813
return expression;
814814
}
815815

816+
export function createAnnonymousIdentifier(tsOriginal?: ts.Node, parent?: Node): Identifier {
817+
const expression = createNode(SyntaxKind.Identifier, tsOriginal, parent) as Identifier;
818+
expression.text = "____";
819+
return expression;
820+
}
821+
816822
export interface TableIndexExpression extends Expression {
817823
kind: SyntaxKind.TableIndexExpression;
818824
table: Expression;

src/LuaTransformer.ts

Lines changed: 159 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {TSTLErrors} from "./TSTLErrors";
1010

1111
export type StatementVisitResult = tstl.Statement | tstl.Statement[] | undefined;
1212
export type ExpressionVisitResult = tstl.Expression | undefined;
13-
1413
export enum ScopeType {
1514
File = 0x1,
1615
Function = 0x2,
@@ -1031,6 +1030,144 @@ export class LuaTransformer {
10311030
});
10321031
}
10331032

1033+
private transformGeneratorFunction(
1034+
parameters: ts.NodeArray<ts.ParameterDeclaration>,
1035+
body: ts.Block,
1036+
transformedParameters: tstl.Identifier[],
1037+
dotsLiteral: tstl.DotsLiteral,
1038+
spreadIdentifier?: tstl.Identifier
1039+
): [tstl.Statement[], Scope]
1040+
{
1041+
this.importLuaLibFeature(LuaLibFeature.Symbol);
1042+
const [functionBody, functionScope] = this.transformFunctionBody(
1043+
parameters,
1044+
body,
1045+
spreadIdentifier
1046+
);
1047+
1048+
const coroutineIdentifier = tstl.createIdentifier("____co");
1049+
const valueIdentifier = tstl.createIdentifier("____value");
1050+
const errIdentifier = tstl.createIdentifier("____err");
1051+
const itIdentifier = tstl.createIdentifier("____it");
1052+
1053+
//local ____co = coroutine.create(originalFunction)
1054+
const coroutine =
1055+
tstl.createVariableDeclarationStatement(coroutineIdentifier,
1056+
tstl.createCallExpression(
1057+
tstl.createTableIndexExpression(tstl.createIdentifier("coroutine"),
1058+
tstl.createStringLiteral("create")
1059+
),
1060+
[tstl.createFunctionExpression(
1061+
tstl.createBlock(functionBody),
1062+
transformedParameters,
1063+
dotsLiteral,
1064+
spreadIdentifier),
1065+
]
1066+
)
1067+
);
1068+
1069+
const nextBody = [];
1070+
// coroutine.resume(__co, ...)
1071+
const resumeCall = tstl.createCallExpression(
1072+
tstl.createTableIndexExpression(
1073+
tstl.createIdentifier("coroutine"),
1074+
tstl.createStringLiteral("resume")
1075+
),
1076+
[coroutineIdentifier, tstl.createDotsLiteral()]
1077+
);
1078+
1079+
// ____err, ____value = coroutine.resume(____co, ...)
1080+
nextBody.push(tstl.createVariableDeclarationStatement(
1081+
[errIdentifier, valueIdentifier],
1082+
resumeCall)
1083+
);
1084+
1085+
//coroutine.status(____co) ~= "dead";
1086+
const coStatus = tstl.createCallExpression(
1087+
tstl.createTableIndexExpression(
1088+
tstl.createIdentifier("coroutine"),
1089+
tstl.createStringLiteral("status")
1090+
),
1091+
[coroutineIdentifier]
1092+
);
1093+
const status = tstl.createBinaryExpression(
1094+
coStatus,
1095+
tstl.createStringLiteral("dead"),
1096+
tstl.SyntaxKind.EqualityOperator
1097+
);
1098+
nextBody.push(status);
1099+
//if(not ____err){error(____value)}
1100+
const errorCheck = tstl.createIfStatement(
1101+
tstl.createUnaryExpression(
1102+
errIdentifier,
1103+
tstl.SyntaxKind.NotOperator
1104+
),
1105+
tstl.createBlock([
1106+
tstl.createExpressionStatement(
1107+
tstl.createCallExpression(
1108+
tstl.createIdentifier("error"),
1109+
[valueIdentifier]
1110+
)
1111+
),
1112+
])
1113+
);
1114+
nextBody.push(errorCheck);
1115+
//{done = coroutine.status(____co) ~= "dead"; value = ____value}
1116+
const iteratorResult = tstl.createTableExpression([
1117+
tstl.createTableFieldExpression(
1118+
status,
1119+
tstl.createStringLiteral("done")
1120+
),
1121+
tstl.createTableFieldExpression(
1122+
valueIdentifier,
1123+
tstl.createStringLiteral("value")
1124+
),
1125+
]);
1126+
nextBody.push(tstl.createReturnStatement([iteratorResult]));
1127+
1128+
//function(____, ...)
1129+
const nextFunctionDeclaration = tstl.createFunctionExpression(
1130+
tstl.createBlock(nextBody),
1131+
[tstl.createAnnonymousIdentifier()],
1132+
tstl.createDotsLiteral());
1133+
1134+
//____it = {next = function(____, ...)}
1135+
const iterator = tstl.createVariableDeclarationStatement(
1136+
itIdentifier,
1137+
tstl.createTableExpression([
1138+
tstl.createTableFieldExpression(
1139+
nextFunctionDeclaration,
1140+
tstl.createStringLiteral("next")
1141+
),
1142+
])
1143+
);
1144+
1145+
const symbolIterator = tstl.createTableIndexExpression(
1146+
tstl.createIdentifier("Symbol"),
1147+
tstl.createStringLiteral("iterator")
1148+
);
1149+
1150+
const block = [
1151+
coroutine,
1152+
iterator,
1153+
//____it[Symbol.iterator] = {return ____it}
1154+
tstl.createAssignmentStatement(
1155+
tstl.createTableIndexExpression(
1156+
itIdentifier,
1157+
symbolIterator
1158+
),
1159+
tstl.createFunctionExpression(
1160+
tstl.createBlock(
1161+
[tstl.createReturnStatement([itIdentifier])]
1162+
)
1163+
)
1164+
),
1165+
//return ____it
1166+
tstl.createReturnStatement([itIdentifier]),
1167+
];
1168+
return [block, functionScope];
1169+
}
1170+
10341171
public transformFunctionDeclaration(functionDeclaration: ts.FunctionDeclaration): StatementVisitResult {
10351172
// Don't transform functions without body (overload declarations)
10361173
if (!functionDeclaration.body) {
@@ -1044,22 +1181,28 @@ export class LuaTransformer {
10441181
const [params, dotsLiteral, restParamName] = this.transformParameters(functionDeclaration.parameters, context);
10451182

10461183
const name = this.transformIdentifier(functionDeclaration.name);
1047-
const [body, functionScope] = this.transformFunctionBody(
1048-
functionDeclaration.parameters,
1049-
functionDeclaration.body,
1050-
restParamName
1051-
);
1184+
const [body, functionScope] = functionDeclaration.asteriskToken
1185+
? this.transformGeneratorFunction(
1186+
functionDeclaration.parameters,
1187+
functionDeclaration.body,
1188+
params,
1189+
dotsLiteral,
1190+
restParamName
1191+
)
1192+
: this.transformFunctionBody(
1193+
functionDeclaration.parameters,
1194+
functionDeclaration.body,
1195+
restParamName
1196+
);
10521197
const block = tstl.createBlock(body);
10531198
const functionExpression = tstl.createFunctionExpression(block, params, dotsLiteral, restParamName);
1054-
10551199
// Remember symbols referenced in this function for hoisting later
10561200
if (!this.options.noHoisting && name.symbolId !== undefined) {
10571201
const scope = this.peekScope();
10581202
if (!scope.functionDefinitions) { scope.functionDefinitions = new Map(); }
10591203
const functionInfo = {referencedSymbols: functionScope.referencedSymbols || new Set()};
10601204
scope.functionDefinitions.set(name.symbolId, functionInfo);
10611205
}
1062-
10631206
return this.createLocalOrExportedOrGlobalDeclaration(name, functionExpression, functionDeclaration);
10641207
}
10651208

@@ -1219,6 +1362,12 @@ export class LuaTransformer {
12191362
return tstl.createExpressionStatement(this.transformExpression(expression));
12201363
}
12211364

1365+
public transformYield(expression: ts.YieldExpression): tstl.Expression {
1366+
return tstl.createCallExpression(
1367+
tstl.createTableIndexExpression(tstl.createIdentifier("coroutine"), tstl.createStringLiteral("yield")),
1368+
expression.expression?[this.transformExpression(expression.expression)]:[], expression);
1369+
}
1370+
12221371
public transformReturn(statement: ts.ReturnStatement): tstl.Statement {
12231372
if (statement.expression) {
12241373
const returnType = tsHelper.getContainingFunctionReturnType(statement, this.checker);
@@ -1731,6 +1880,8 @@ export class LuaTransformer {
17311880
return this.transformSpreadElement(expression as ts.SpreadElement);
17321881
case ts.SyntaxKind.NonNullExpression:
17331882
return this.transformExpression((expression as ts.NonNullExpression).expression);
1883+
case ts.SyntaxKind.YieldExpression:
1884+
return this.transformYield(expression as ts.YieldExpression);
17341885
case ts.SyntaxKind.EmptyStatement:
17351886
return undefined;
17361887
case ts.SyntaxKind.NotEmittedStatement:
@@ -1871,7 +2022,6 @@ export class LuaTransformer {
18712022
throw TSTLErrors.UnsupportedUnionAccessor(lhs);
18722023
}
18732024
}
1874-
18752025
return tstl.createAssignmentStatement(
18762026
this.transformExpression(lhs) as tstl.IdentifierOrTableIndexExpression,
18772027
right,

test/unit/functions.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,64 @@ export class FunctionTests {
389389
Expect(result).toBe("foobar");
390390
}
391391

392+
@TestCase(1, 1)
393+
@TestCase(2, 42)
394+
@Test("Generator functions value")
395+
public generatorFunctionValue(iterations: number, expectedResult: number): void {
396+
const code = `function* seq(value: number) {
397+
let a = yield value + 1;
398+
return 42;
399+
}
400+
const gen = seq(0);
401+
let ret: number;
402+
for(let i = 0; i < ${iterations}; ++i)
403+
{
404+
ret = gen.next(i).value;
405+
}
406+
return ret;
407+
`;
408+
const result = util.transpileAndExecute(code);
409+
Expect(result).toBe(expectedResult);
410+
}
411+
412+
@TestCase(1, false)
413+
@TestCase(2, true)
414+
@Test("Generator functions done")
415+
public generatorFunctionDone(iterations: number, expectedResult: boolean): void {
416+
const code = `function* seq(value: number) {
417+
let a = yield value + 1;
418+
return 42;
419+
}
420+
const gen = seq(0);
421+
let ret: boolean;
422+
for(let i = 0; i < ${iterations}; ++i)
423+
{
424+
ret = gen.next(i).done;
425+
}
426+
return ret;
427+
`;
428+
const result = util.transpileAndExecute(code);
429+
Expect(result).toBe(expectedResult);
430+
}
431+
432+
@Test("Generator for..of")
433+
public generatorFunctionForOf(): void {
434+
const code = `function* seq() {
435+
yield(1);
436+
yield(2);
437+
yield(3);
438+
return 4;
439+
}
440+
let result = 0;
441+
for(let i of seq())
442+
{
443+
result = result * 10 + i;
444+
}
445+
return result`;
446+
const result = util.transpileAndExecute(code);
447+
Expect(result).toBe(123);
448+
}
449+
392450
@Test("Function local overriding export")
393451
public functionLocalOverridingExport(): void {
394452
const code =

0 commit comments

Comments
 (0)