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