Many debug improvements. Close #52.
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
-lib utest
|
||||
-debug
|
||||
-main tests.ExamplesTestMain
|
||||
-js js
|
||||
-js index.js
|
||||
|
||||
15
hank/DebugMacros.hx
Normal file
15
hank/DebugMacros.hx
Normal file
@@ -0,0 +1,15 @@
|
||||
package hank;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
class DebugMacros {
|
||||
public static macro function watch(e: Expr): Expr {
|
||||
switch (e.expr) {
|
||||
case EConst(CIdent(i)):
|
||||
return macro trace('$i:' + $e);
|
||||
default:
|
||||
throw 'Can only watch variables (for now)';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class FileLoadingMacro {
|
||||
if (file.endsWith("/")) {
|
||||
files.remove(file);
|
||||
|
||||
files = files.concat(recursiveLoop(file, []));
|
||||
files = files.concat(recursiveLoop(file));
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
@@ -48,7 +48,8 @@ class FileLoadingMacro {
|
||||
}
|
||||
|
||||
// this function is nabbed from https://code.haxe.org/category/beginner/using-filesystem.html
|
||||
static function recursiveLoop(directory:String, files: Array<String>): Array<String> {
|
||||
static function recursiveLoop(directory:String, ?files: Array<String>): Array<String> {
|
||||
if (files == null) files = [];
|
||||
if (sys.FileSystem.exists(directory)) {
|
||||
trace("directory found: " + directory);
|
||||
for (file in sys.FileSystem.readDirectory(directory)) {
|
||||
|
||||
@@ -4,6 +4,8 @@ using StringTools;
|
||||
|
||||
import haxe.ds.Option;
|
||||
|
||||
typedef PreloadedFiles = Map<String, String>;
|
||||
|
||||
/**
|
||||
A position in a HankBuffer, used for debugging.
|
||||
**/
|
||||
@@ -83,14 +85,25 @@ class HankBuffer {
|
||||
return new HankBuffer('_', text, 1, 1);
|
||||
}
|
||||
|
||||
// TODO document why this is private -- to prevent the user from using sys at runtime
|
||||
@:allow(hank.Parser, hank.FileLoadingMacro)
|
||||
private static function FromFile(path: String) {
|
||||
public static function FromFile(path: String, ?files: PreloadedFiles) {
|
||||
// Keep a raw buffer of the file for tracking accurate file positions
|
||||
#if sys
|
||||
var rawBuffer = sys.io.File.getContent(path);
|
||||
#else
|
||||
if (files == null || !files.exists(path)) {
|
||||
throw 'Tried to open file $path that was not pre-loaded';
|
||||
}
|
||||
var rawBuffer = files[path];
|
||||
#end
|
||||
return new HankBuffer(path, rawBuffer);
|
||||
}
|
||||
|
||||
public function lines(): Array<String> {
|
||||
var lines = cleanBuffer.split('\n');
|
||||
drop(cleanBuffer);
|
||||
return lines;
|
||||
}
|
||||
|
||||
function stripComments(s: String, o: String, c: String, dc: Bool): String {
|
||||
while (s.indexOf(o) != -1) {
|
||||
var start = s.indexOf(o);
|
||||
|
||||
@@ -8,18 +8,24 @@ class LogUtil {
|
||||
var linesFromFile = '';
|
||||
for (item in stack) {
|
||||
switch (item) {
|
||||
case FilePos(null, file, line):
|
||||
if (file != lastFile) {
|
||||
lastFile = file;
|
||||
trace(linesFromFile);
|
||||
linesFromFile = '$file:$line';
|
||||
} else {
|
||||
linesFromFile += ':$line';
|
||||
}
|
||||
case FilePos(method, file, line):
|
||||
var relevantPart = Std.string(if (method != null) method else line);
|
||||
if (file != lastFile) {
|
||||
lastFile = file;
|
||||
trace(linesFromFile);
|
||||
|
||||
linesFromFile = '$file:$relevantPart';
|
||||
} else {
|
||||
linesFromFile += ':$relevantPart';
|
||||
}
|
||||
case other:
|
||||
trace('Stack contains unsupported element: $other');
|
||||
trace(stack);
|
||||
trace('Stack contains unsupported element: $other');
|
||||
trace(stack);
|
||||
}
|
||||
}
|
||||
if (linesFromFile.length > 0) {
|
||||
trace(linesFromFile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using StringTools;
|
||||
using Extensions.Extensions;
|
||||
import hank.HankAST.ExprType;
|
||||
import hank.Choice.Choice;
|
||||
import hank.HankBuffer;
|
||||
|
||||
/**
|
||||
Parses Hank scripts into ASTs for a Story object to interpret. Additional parsing happens in Alt.hx and Output.hx
|
||||
@@ -55,7 +56,7 @@ class Parser {
|
||||
return parsedAST;
|
||||
}
|
||||
|
||||
public function parseFile(f: String, includedFile = false) : HankAST {
|
||||
public function parseFile(f: String, ?files: PreloadedFiles, includedFile = false) : HankAST {
|
||||
var directory = '';
|
||||
var lastSlashIdx = f.lastIndexOf('/');
|
||||
if (lastSlashIdx != -1) {
|
||||
@@ -63,7 +64,7 @@ class Parser {
|
||||
f = f.substr(lastSlashIdx+1);
|
||||
}
|
||||
|
||||
buffers.insert(0, HankBuffer.FromFile(directory + f));
|
||||
buffers.insert(0, HankBuffer.FromFile(directory + f, files));
|
||||
|
||||
while (buffers.length > 0) {
|
||||
var position = buffers[0].position();
|
||||
@@ -74,7 +75,7 @@ class Parser {
|
||||
var expr = parseExpr(buffers[0], position);
|
||||
switch(expr) {
|
||||
case EIncludeFile(file):
|
||||
parseFile(directory + file, true);
|
||||
parseFile(directory + file, files, true);
|
||||
case ENoOp:
|
||||
// Drop no-ops from the AST
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package hank;
|
||||
|
||||
import hank.HankBuffer;
|
||||
using StringTools;
|
||||
import haxe.ds.Option;
|
||||
|
||||
@@ -69,19 +70,15 @@ class Story {
|
||||
return story;
|
||||
}
|
||||
|
||||
public static function FromFile(script: String, ?randomSeed: Int): Story {
|
||||
public static function FromAST(script: String, ast: HankAST, ?randomSeed: Int): Story {
|
||||
var random = new Random(randomSeed);
|
||||
|
||||
var parser = new Parser();
|
||||
var ast = parser.parseFile(script);
|
||||
|
||||
var storyTree = StoryNode.FromAST(ast);
|
||||
var nodeScopes = [storyTree];
|
||||
var viewCounts = storyTree.createViewCounts();
|
||||
|
||||
var hInterface = new HInterface(storyTree, viewCounts);
|
||||
|
||||
var story = new Story(random, parser, ast, storyTree, nodeScopes, viewCounts, hInterface);
|
||||
var story = new Story(random, new Parser(), ast, storyTree, nodeScopes, viewCounts, hInterface);
|
||||
hInterface.addVariable('story', story);
|
||||
|
||||
story.runRootIncludedHaxe(script);
|
||||
@@ -89,6 +86,11 @@ class Story {
|
||||
return story;
|
||||
}
|
||||
|
||||
public static function FromFile(script: String, ?files: PreloadedFiles, ?randomSeed: Int): Story {
|
||||
var parser = new Parser();
|
||||
var ast = parser.parseFile(script, files);
|
||||
return Story.FromAST(script, ast, randomSeed);
|
||||
}
|
||||
|
||||
/* Go through each included file executing all Haxe embedded at root level */
|
||||
private function runRootIncludedHaxe(rootFile: String) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package hank;
|
||||
using StringTools;
|
||||
import hank.Story.StoryFrame;
|
||||
import hank.LogUtil;
|
||||
import hank.HankBuffer;
|
||||
|
||||
import haxe.CallStack;
|
||||
import utest.Assert;
|
||||
@@ -10,47 +11,77 @@ import utest.Assert;
|
||||
class StoryTestCase extends utest.Test {
|
||||
|
||||
var testsDirectory: String;
|
||||
var files: PreloadedFiles = new Map();
|
||||
|
||||
public function new(testsDirectory: String) {
|
||||
public function new(testsDirectory: String, ?preloadedFiles: PreloadedFiles) {
|
||||
super();
|
||||
this.testsDirectory = testsDirectory;
|
||||
if (preloadedFiles != null)
|
||||
this.files = preloadedFiles;
|
||||
}
|
||||
|
||||
public function testAllExamples() {
|
||||
var exampleTranscripts: Map<String, Array<String>> = new Map();
|
||||
#if sys
|
||||
var exampleFolders = sys.FileSystem.readDirectory(testsDirectory);
|
||||
|
||||
// Iterate through every example in the examples folder
|
||||
for (folder in exampleFolders) {
|
||||
trace('Running tests for example "${folder}"');
|
||||
var files = sys.FileSystem.readDirectory('${testsDirectory}/${folder}');
|
||||
exampleTranscripts.set(folder, [for(file in files) if (file.endsWith('.hlog')) file]);
|
||||
}
|
||||
#else
|
||||
for (file in files.keys()) {
|
||||
var parts = file.split('/');
|
||||
|
||||
for (file in files) {
|
||||
if (file.endsWith('.hlog')) {
|
||||
if (parts[0] != testsDirectory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var disabled = file.indexOf("disabled") != -1;
|
||||
var debug = file.indexOf("debug") != -1;
|
||||
var partial = file.indexOf("partial") != -1;
|
||||
if (!disabled) {
|
||||
trace(' Running ${file}');
|
||||
try {
|
||||
validateAgainstTranscript('${testsDirectory}/${folder}/main.hank', '${testsDirectory}/${folder}/${file}', !partial, debug);
|
||||
} catch (e: Dynamic) {
|
||||
trace('Error testing $folder/$file at transcript line $lastTranscriptLine: $e');
|
||||
LogUtil.prettyPrintStack(CallStack.exceptionStack());
|
||||
Assert.fail();
|
||||
}
|
||||
var folder = parts[1];
|
||||
if (!exampleTranscripts.exists(folder)) {
|
||||
exampleTranscripts.set(folder, new Array());
|
||||
}
|
||||
|
||||
if (parts[2].endsWith('.hlog')) {
|
||||
exampleTranscripts[folder].push(parts[2]);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
for (folder in exampleTranscripts.keys()) {
|
||||
if (folder.startsWith('_')) {
|
||||
trace('Skipping tests for example "${folder}"');
|
||||
continue;
|
||||
}
|
||||
trace('Running tests for example "${folder}"');
|
||||
|
||||
for (file in exampleTranscripts[folder]) {
|
||||
var disabled = file.indexOf("disabled") != -1;
|
||||
var debug = file.indexOf("debug") != -1;
|
||||
var partial = file.indexOf("partial") != -1;
|
||||
if (!disabled) {
|
||||
trace(' Running ${file}');
|
||||
#if !sys
|
||||
validateAgainstTranscript('${testsDirectory}/${folder}/main.hank', '${testsDirectory}/${folder}/${file}', !partial, debug);
|
||||
#else
|
||||
try {
|
||||
validateAgainstTranscript('${testsDirectory}/${folder}/main.hank', '${testsDirectory}/${folder}/${file}', !partial, debug);
|
||||
} catch (e: Dynamic) {
|
||||
trace('Error testing $folder/$file at transcript line $lastTranscriptLine: $e');
|
||||
LogUtil.prettyPrintStack(CallStack.exceptionStack());
|
||||
Assert.fail();
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var lastTranscriptLine = 0;
|
||||
|
||||
private function validateAgainstTranscript(storyFile: String, transcriptFile: String, fullTranscript: Bool = true, debug: Bool = false) {
|
||||
var buffer = HankBuffer.FromFile(transcriptFile, files);
|
||||
var transcriptLines = buffer.lines();
|
||||
|
||||
var transcriptLines = sys.io.File.getContent(transcriptFile).split('\n');
|
||||
// If the transcript starts with a random seed, make sure the story uses that seed
|
||||
var randomSeed = null;
|
||||
if (transcriptLines[0].startsWith('@')) {
|
||||
@@ -59,14 +90,20 @@ class StoryTestCase extends utest.Test {
|
||||
}
|
||||
|
||||
var story: Story = null;
|
||||
|
||||
// It's easier to debug exceptions on web if they travel up the stack
|
||||
#if !sys
|
||||
story = Story.FromFile(storyFile, files, randomSeed);
|
||||
#else
|
||||
try {
|
||||
story = Story.FromFile(storyFile, randomSeed);
|
||||
story = Story.FromFile(storyFile, files, randomSeed);
|
||||
} catch (e: Dynamic) {
|
||||
trace('Error parsing $storyFile: $e');
|
||||
LogUtil.prettyPrintStack(CallStack.exceptionStack());
|
||||
Assert.fail();
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
story.hInterface.addVariable("DEBUG", debug);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package hank;
|
||||
|
||||
import haxe.ds.Option;
|
||||
import hank.DebugMacros.watch;
|
||||
|
||||
@:allow(tests.StoryTreeTest)
|
||||
class StoryNode {
|
||||
@@ -70,10 +71,13 @@ class StoryNode {
|
||||
case EGather(Some(name), _, _) | EChoice({id: _, onceOnly: _, label: Some(name), condition: _, depth: _, output: _, divertTarget: _}):
|
||||
var node = new StoryNode(exprIndex);
|
||||
if (lastKnot == null) {
|
||||
// watch(root);
|
||||
root.addChild(name, node);
|
||||
} else if (lastStitch == null) {
|
||||
// watch(lastKnot);
|
||||
lastKnot.addChild(name, node);
|
||||
} else {
|
||||
// watch(lastStitch);
|
||||
lastStitch.addChild(name, node);
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -2,8 +2,13 @@ package tests;
|
||||
import utest.Test;
|
||||
import hank.StoryTestCase;
|
||||
|
||||
@:build(hank.FileLoadingMacro.build(["examples/"]))
|
||||
class ExamplesTestMain extends Test {
|
||||
public static function main() {
|
||||
utest.UTest.run([new StoryTestCase("examples")]);
|
||||
utest.UTest.run([new StoryTestCase("examples"
|
||||
#if !sys
|
||||
, files
|
||||
#end
|
||||
)]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user