Files
hank/hank/HankAST.hx
Nat Quayle Nelson b631e7d0ee
Some checks failed
/ test (push) Failing after 48s
Support gather at a choice
2025-10-26 08:27:36 -05:00

128 lines
3.4 KiB
Haxe

package hank;
import haxe.ds.Option;
import hank.Alt.AltInstance;
import hank.Choice.ChoiceInfo;
import hank.Choice.ChoicePointInfo;
enum ExprType {
EIncludeFile(path:String);
EOutput(o:Output);
EDivert(targets:Array<String>);
EThread(target:String);
EKnot(name:String);
EStitch(name:String);
ENoOp;
EHaxeLine(haxe:String);
EHaxeBlock(haxe:String);
EGather(label:Option<String>, depth:Int, expr:ExprType);
// Hank pre-tag-implementation: Choices are the most complicated expressions
EChoice(c:Choice);
// Tags: Hold my beer
ETagged(e: ExprType, tags:Array<String>);
}
typedef HankExpr = {
var position:HankBuffer.Position;
var expr:ExprType;
}
typedef HankAST = Array<HankExpr>;
/**
Implements helper functions for navigating a Hank AST.
**/
class ASTExtension {
public static function findFile(ast:HankAST, path:String) {
for (i in 0...ast.length) {
var expr = ast[i];
if (expr.position.file == path) {
return i;
}
}
return -1;
}
public static function findEOF(ast:HankAST, path:String) {
for (i in 0...ast.length) {
var expr = ast[ast.length - 1 - i];
if (expr.position.file == path) {
return ast.length - 1 - i + 1;
}
}
return -1;
}
static function tryAddFunc(choices: Array<ChoiceInfo>, expectedDepth: Int, c: Choice, tags: Array<String>) {
var valid = (c.depth == expectedDepth);
if (valid) choices.push({choice:c,tags:tags});
return valid;
}
/**
Collect every choice in the choice point starting at the given index.
**/
public static function collectChoices(ast:HankAST, startingIndex:Int, depth:Int):ChoicePointInfo {
var choices = new Array<ChoiceInfo>();
var lastChoiceIndex = 0;
var tryAdd = tryAddFunc.bind(choices, depth);
if (startingIndex > ast.length || startingIndex < 0) {
throw 'Trying to collect choices starting from expr ${startingIndex + 1}/${ast.length}';
}
var currentFile = ast[startingIndex].position.file;
for (i in startingIndex...findEOF(ast, currentFile)) {
switch (ast[i].expr) {
// Gather choices of the current depth
case EChoice(choice):
if (tryAdd(choice, [])) lastChoiceIndex = i;
case ETagged(EChoice(choice), tags):
if (tryAdd(choice, tags)) lastChoiceIndex = i;
// Choice immediatly after gather at current depth:
case EGather(_, d, EChoice(choice)) if(i == startingIndex && d == depth):
if (tryAdd(choice, [])) lastChoiceIndex = i;
case EGather(_, d, ETagged(EChoice(choice), tags)) if(i == startingIndex && d == depth):
if (tryAdd(choice, tags)) lastChoiceIndex = i;
// Stop at the next gather of this depth
case EGather(_, d, _) if (d == depth):
break;
// Stop at knot or stitch declarations
case EKnot(_) | EStitch(_):
break;
default:
}
}
return {choices: choices, fallbackIndex: lastChoiceIndex};
}
public static function findNextGather(ast:HankAST, path:String, startingIndex:Int, maxDepth:Int):Int {
for (i in startingIndex...findEOF(ast, path)) {
switch (ast[i].expr) {
case EGather(_, depth, _) | ETagged(EGather(_, depth, _), _):
if (depth <= maxDepth)
return i;
default:
}
}
return -1;
}
public static function indexOfChoice(ast:HankAST, id:Int):Int {
for (i in 0...ast.length) {
var expr = ast[i].expr;
switch (expr) {
case EChoice(c) | ETagged(EChoice(c), _) | EGather(_, _, EChoice(c)):
if (c.id == id)
return i;
default:
}
}
return -1;
}
}