diff --git a/examples/main/extra.hank b/examples/main/extra.hank index 601d349..afc7000 100644 --- a/examples/main/extra.hank +++ b/examples/main/extra.hank @@ -1,4 +1,7 @@ // comments in Hank start with a double-slash +/* Or you can split comments +across more than one line */ +/* Or you can use block comments inline */ ~ var demo_var = "dynamic content"; // Hank scripts can embed Haxe logic by starting a line with '~' ~ var part1 = "haxe"; ~ var part2 = "expressions"; diff --git a/hank/Parser.hx b/hank/Parser.hx index a08f607..dd04a63 100644 --- a/hank/Parser.hx +++ b/hank/Parser.hx @@ -5,6 +5,7 @@ import haxe.ds.Option; enum OutputType { Text(t: String); // Pure text that is always displayed HExpression(h: String); // An embedded Haxe expression whose value will be inserted + InlineComment(c: String); // A block comment in the middle of an output sequence ToggleOutput(o: Output); // Output that will sometime be displayed (i.e. [bracketed] section in a choice text) } @@ -12,8 +13,11 @@ typedef Output = Array; enum ExprType { EIncludeFile(path: String); - EOutput(o: Output); + EOutput(output: Output); EDivert(target: String); + EKnot(name: String); + EStitch(name: String); + EComment(message: String); ENoOp; } @@ -28,7 +32,12 @@ typedef HankAST = Array; class Parser { var symbols = [ 'INCLUDE ' => include, + '//' => lineComment, + '/*' => blockComment, '->' => divert, + '===' => knot, + '==' => knot, + '=' => stitch ]; var buffers: Array = []; @@ -53,10 +62,12 @@ class Parser { var nextLine = buffers[0].takeLine('lr'); switch (nextLine) { case Some(line): - var expr = parseLine(line, position); + var expr = parseLine(line, buffers[0], position); switch (expr) { case EIncludeFile(file): parseFile(directory + file, true); + case ENoOp: + // Drop no-ops from the AST default: ast.push({ position: position, @@ -80,35 +91,80 @@ class Parser { return parsedAST; } - function parseLine(line: String, position: FileBuffer.Position) : ExprType { + function parseLine(line: String, restOfBuffer: FileBuffer, position: FileBuffer.Position) : ExprType { if (StringTools.trim(line).length == 0) { return ENoOp; } + for (symbol in symbols.keys()) { if (StringTools.startsWith(line, symbol)) { - return symbols[symbol](line, position); + return symbols[symbol](line, restOfBuffer, position); } } - return output(line, position); + return output(line, buffers[0], position); } - static function include(line: String, position: FileBuffer.Position) : ExprType { + /** Split the given line into n tokens, throwing an error if there are any number of tokens other than n **/ + static function lineTokens(line:String, n: Int, position: FileBuffer.Position) { var tokens = line.split(' '); - if (tokens.length != 2) { - throw 'Include file error at ${position}: ${tokens.length} tokens provided--should be 2.'; + if (tokens.length != n) { + throw 'Include file error at ${position}: ${tokens.length} tokens provided--should be ${n}.\nLine: `${line}`'; + } + return tokens; + } + + static function lineComment(line: String, rob: FileBuffer, position: FileBuffer.Position) : ExprType { + return EComment(line.substr(2)); + } + + static function blockComment(line: String, rob: FileBuffer, position: FileBuffer.Position) : ExprType { + var text = line.substr(2); + rob.give(text + '\n'); + + var nesting = 1; + var endIdx = -1; + + while (nesting > 0) { + var nextCommentTerminator = rob.peekWhichComesNext(['/*', '*/']); + switch (nextCommentTerminator) { + case Some([0, index]): + nesting += 1; + case Some([1, index]): + nesting -= 1; + endIdx = index; + case None: + throw 'Block comment starting at ${position} never terminates!'; + } } - var file = tokens[1]; - return EIncludeFile(file); + text = rob.take(endIdx); + rob.drop('*/'); + return EComment(text); } - static function divert(line: String, position: FileBuffer.Position) : ExprType { - return EDivert(""); + static function include(line: String, rob: FileBuffer, position: FileBuffer.Position) : ExprType { + var tokens = lineTokens(line, 2, position); + return EIncludeFile(tokens[1]); } - static function output(line: String, position: FileBuffer.Position) : ExprType { + static function divert(line: String, rob: FileBuffer, position: FileBuffer.Position) : ExprType { + var tokens = lineTokens(line, 2, position); + return EDivert("tokens[1]"); + } + + static function output(line: String, rob: FileBuffer, position: FileBuffer.Position) : ExprType { return EOutput([Text("")]); } + static function knot(line: String, rob: FileBuffer, position: FileBuffer.Position) : ExprType { + var tokens = lineTokens(line, 2, position); + return EKnot("tokens[1]"); + } + + static function stitch(line: String, rob: FileBuffer, position: FileBuffer.Position) : ExprType { + var tokens = lineTokens(line, 2, position); + return EStitch("tokens[1]"); + } + } \ No newline at end of file diff --git a/tests/ParserTest.hx b/tests/ParserTest.hx index 3a6c740..8200416 100644 --- a/tests/ParserTest.hx +++ b/tests/ParserTest.hx @@ -1,11 +1,28 @@ package tests; import hank.Parser; +import hank.Parser.HankAST; class ParserTest extends utest.Test { + var ast: HankAST; + + function nextExpr() { + var next = ast[0]; + ast.remove(next); + return next.expr; + } + + function assertNextExpr(expr: ExprType) { + HankAssert.equals(expr, nextExpr()); + } + + function testParseMain() { var parser = new Parser(); - var ast = parser.parseFile('examples/main/main.hank'); - trace (ast); + ast = parser.parseFile('examples/main/main.hank'); + assertNextExpr(EComment(" comments in Hank start with a double-slash")); + assertNextExpr(EComment(" Or you can split comments\nacross more than one line ")); + assertNextExpr(EComment(" Or you can use block comments inline ")); + } } \ No newline at end of file