feat: code generation for simple choice statements, testing machinery
This commit is contained in:
parent
fac5a968e3
commit
ee26be6254
13 changed files with 304 additions and 70 deletions
120
src/AstGen.zig
120
src/AstGen.zig
|
|
@ -758,6 +758,7 @@ fn switchStmt(
|
|||
try case_indexes.ensureUnusedCapacity(gpa, switch_stmt.cases.len);
|
||||
defer case_indexes.deinit(gpa);
|
||||
|
||||
// TODO: Length checks.
|
||||
const switch_cases = switch_stmt.cases[0 .. switch_stmt.cases.len - 1];
|
||||
for (switch_cases) |case_stmt| {
|
||||
// TODO: Maybe make this non-nullable
|
||||
|
|
@ -855,78 +856,77 @@ fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void
|
|||
return gi.fail(.unknown_identifier, identifier_node);
|
||||
}
|
||||
|
||||
fn choiceStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
|
||||
const Choice = struct {
|
||||
label_index: usize,
|
||||
start_expression: ?*const Ast.Node,
|
||||
option_expression: ?*const Ast.Node,
|
||||
inner_expression: ?*const Ast.Node,
|
||||
block_stmt: ?*const Ast.Node,
|
||||
};
|
||||
fn choiceStarStmt(gi: *GenIr, _: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
return stringLiteral(gi, node);
|
||||
}
|
||||
|
||||
const branch_list = stmt_node.data.list.items orelse unreachable;
|
||||
assert(branch_list.len != 0);
|
||||
fn choiceStmt(
|
||||
parent_block: *GenIr,
|
||||
scope: *Scope,
|
||||
stmt_node: *const Ast.Node,
|
||||
) InnerError!void {
|
||||
const astgen = parent_block.astgen;
|
||||
const gpa = astgen.gpa;
|
||||
const choice_branches = stmt_node.data.list.items.?;
|
||||
assert(choice_branches.len != 0);
|
||||
|
||||
const gpa = gen.astgen.gpa;
|
||||
var choice_list: std.ArrayListUnmanaged(Choice) = .empty;
|
||||
defer choice_list.deinit(gpa);
|
||||
try choice_list.ensureUnusedCapacity(gpa, branch_list.len);
|
||||
const choice_br = try parent_block.makeBlockInst(.choice_br);
|
||||
var case_indexes: std.ArrayListUnmanaged(u32) = .empty;
|
||||
try case_indexes.ensureUnusedCapacity(gpa, choice_branches.len);
|
||||
defer case_indexes.deinit(gpa);
|
||||
|
||||
for (branch_list) |branch_stmt| {
|
||||
for (choice_branches) |branch_stmt| {
|
||||
assert(branch_stmt.tag == .choice_star_stmt or branch_stmt.tag == .choice_plus_stmt);
|
||||
const branch_data = branch_stmt.data.bin;
|
||||
const branch_expr = branch_data.lhs orelse unreachable;
|
||||
const branch_expr_data = branch_expr.data.choice_expr;
|
||||
const label_index = try gen.makeLabel();
|
||||
const branch_expr = branch_data.lhs.?.data.choice_expr;
|
||||
var op_1: Ir.Inst.Ref = .none;
|
||||
var op_2: Ir.Inst.Ref = .none;
|
||||
var op_3: Ir.Inst.Ref = .none;
|
||||
|
||||
if (branch_expr_data.start_expr) |node| {
|
||||
try stringLiteral(gen, node);
|
||||
try gen.emitSimpleInst(.stream_push);
|
||||
if (branch_expr.start_expr) |node| {
|
||||
op_1 = try choiceStarStmt(parent_block, scope, node);
|
||||
}
|
||||
if (branch_expr_data.option_expr) |node| {
|
||||
try stringLiteral(gen, node);
|
||||
try gen.emitSimpleInst(.stream_push);
|
||||
if (branch_expr.option_expr) |node| {
|
||||
op_2 = try choiceStarStmt(parent_block, scope, node);
|
||||
}
|
||||
if (branch_expr.inner_expr) |node| {
|
||||
op_3 = try choiceStarStmt(parent_block, scope, node);
|
||||
}
|
||||
|
||||
const fixup_offset = try gen.emitJumpInst(.br_push);
|
||||
_ = try gen.makeFixup(.{
|
||||
.mode = .absolute,
|
||||
.label_index = label_index,
|
||||
.code_offset = fixup_offset,
|
||||
});
|
||||
var sub_block = parent_block.makeSubBlock();
|
||||
defer sub_block.unstack();
|
||||
if (branch_data.rhs) |branch_body| {
|
||||
_ = try blockStmt(&sub_block, scope, branch_body);
|
||||
}
|
||||
_ = try sub_block.addUnaryNode(.implicit_ret, .none);
|
||||
|
||||
choice_list.appendAssumeCapacity(.{
|
||||
.label_index = label_index,
|
||||
.start_expression = branch_expr_data.start_expr,
|
||||
.inner_expression = branch_expr_data.inner_expr,
|
||||
.option_expression = branch_expr_data.option_expr,
|
||||
.block_stmt = branch_data.rhs,
|
||||
});
|
||||
const body = sub_block.instructionsSlice();
|
||||
const case_extra_len = @typeInfo(Ir.Inst.SwitchBr.Case).@"struct".fields.len + body.len;
|
||||
try astgen.extra.ensureUnusedCapacity(gpa, case_extra_len);
|
||||
const extra_index = astgen.addExtraAssumeCapacity(
|
||||
Ir.Inst.ChoiceBr.Case{
|
||||
.operand_1 = op_1,
|
||||
.operand_2 = op_2,
|
||||
.operand_3 = op_3,
|
||||
.body_len = @intCast(body.len),
|
||||
},
|
||||
);
|
||||
astgen.appendBlockBody(body);
|
||||
case_indexes.appendAssumeCapacity(extra_index);
|
||||
}
|
||||
|
||||
try gen.emitSimpleInst(.br_table);
|
||||
try gen.emitSimpleInst(.br_select_index);
|
||||
try gen.emitSimpleInst(.br_dispatch);
|
||||
try parent_block.instructions.append(gpa, choice_br);
|
||||
const extra_len = @typeInfo(Ir.Inst.ChoiceBr).@"struct".fields.len + case_indexes.items.len;
|
||||
try astgen.extra.ensureUnusedCapacity(gpa, extra_len);
|
||||
|
||||
for (choice_list.items) |choice| {
|
||||
gen.setLabel(choice.label_index);
|
||||
|
||||
if (choice.start_expression) |expr_node| {
|
||||
try stringLiteral(gen, expr_node);
|
||||
try gen.emitSimpleInst(.stream_push);
|
||||
}
|
||||
if (choice.inner_expression) |expr_node| {
|
||||
try stringLiteral(gen, expr_node);
|
||||
try gen.emitSimpleInst(.stream_push);
|
||||
}
|
||||
|
||||
try gen.emitSimpleInst(.stream_flush);
|
||||
if (choice.block_stmt) |block| {
|
||||
try blockStmt(gen, scope, block);
|
||||
} else {
|
||||
try gen.emitSimpleInst(.exit);
|
||||
}
|
||||
}
|
||||
astgen.instructions.items[@intFromEnum(choice_br)].data.payload = .{
|
||||
.payload_index = astgen.addExtraAssumeCapacity(
|
||||
Ir.Inst.ChoiceBr{
|
||||
.cases_len = @intCast(choice_branches.len),
|
||||
},
|
||||
),
|
||||
};
|
||||
astgen.extra.appendSliceAssumeCapacity(case_indexes.items[0..]);
|
||||
}
|
||||
|
||||
fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
||||
|
|
@ -982,7 +982,7 @@ fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void {
|
|||
.temp_decl => try tempDecl(gi, &child_scope, inner_node),
|
||||
.assign_stmt => try assignStmt(gi, &child_scope, inner_node),
|
||||
.content_stmt => try contentStmt(gi, &child_scope, inner_node),
|
||||
//.choice_stmt => try choiceStmt(gen, scope, inner_node),
|
||||
.choice_stmt => try choiceStmt(gi, &child_scope, inner_node),
|
||||
.expr_stmt => try exprStmt(gi, &child_scope, inner_node),
|
||||
else => unreachable,
|
||||
};
|
||||
|
|
|
|||
44
src/Ir.zig
44
src/Ir.zig
|
|
@ -75,6 +75,8 @@ pub const Inst = struct {
|
|||
switch_br,
|
||||
content_push,
|
||||
content_flush,
|
||||
choice_br,
|
||||
implicit_ret,
|
||||
};
|
||||
|
||||
pub const Data = union {
|
||||
|
|
@ -138,6 +140,17 @@ pub const Inst = struct {
|
|||
body_len: u32,
|
||||
};
|
||||
};
|
||||
|
||||
pub const ChoiceBr = struct {
|
||||
cases_len: u32,
|
||||
|
||||
pub const Case = struct {
|
||||
operand_1: Ref,
|
||||
operand_2: Ref,
|
||||
operand_3: Ref,
|
||||
body_len: u32,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const Global = struct {
|
||||
|
|
@ -326,6 +339,35 @@ const Render = struct {
|
|||
try io_w.writeAll(")");
|
||||
}
|
||||
|
||||
fn renderChoiceBr(r: *Render, ir: Ir, inst: Inst) Error!void {
|
||||
const io_w = r.writer;
|
||||
const data = inst.data.payload;
|
||||
const choice_extra = ir.extraData(Inst.ChoiceBr, data.payload_index);
|
||||
const options_slice = ir.bodySlice(choice_extra.end, choice_extra.data.cases_len);
|
||||
|
||||
try io_w.print("{s}(\n", .{@tagName(inst.tag)});
|
||||
|
||||
for (options_slice) |option_index| {
|
||||
const case_extra = ir.extraData(Inst.ChoiceBr.Case, @intFromEnum(option_index));
|
||||
const body_slice = ir.bodySlice(case_extra.end, case_extra.data.body_len);
|
||||
const old_len = try r.prefix.pushChildPrefix(r.gpa);
|
||||
defer r.prefix.restore(old_len);
|
||||
|
||||
try r.prefix.writeIndent(io_w);
|
||||
try renderInstRef(r, case_extra.data.operand_1);
|
||||
try io_w.writeAll(", ");
|
||||
try renderInstRef(r, case_extra.data.operand_2);
|
||||
try io_w.writeAll(", ");
|
||||
try renderInstRef(r, case_extra.data.operand_3);
|
||||
|
||||
try io_w.print(" = ", .{});
|
||||
try renderBodyInner(r, ir, body_slice);
|
||||
try io_w.writeAll(",\n");
|
||||
}
|
||||
try r.prefix.writeIndent(io_w);
|
||||
try io_w.writeAll(")");
|
||||
}
|
||||
|
||||
fn renderKnotDecl(r: *Render, ir: Ir, inst: Inst) Error!void {
|
||||
const io_w = r.writer;
|
||||
const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index);
|
||||
|
|
@ -417,6 +459,8 @@ const Render = struct {
|
|||
},
|
||||
.content_push => try r.renderUnary(inst),
|
||||
.content_flush => try r.renderUnary(inst),
|
||||
.choice_br => try r.renderChoiceBr(ir, inst),
|
||||
.implicit_ret => try r.renderUnary(inst),
|
||||
}
|
||||
try io_w.writeAll("\n");
|
||||
}
|
||||
|
|
|
|||
84
src/Sema.zig
84
src/Sema.zig
|
|
@ -238,6 +238,76 @@ fn irContentFlush(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref {
|
|||
return chunk.addByteOp(.stream_flush);
|
||||
}
|
||||
|
||||
fn irChoiceBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const choice_extra = sema.ir.extraData(Ir.Inst.ChoiceBr, data.payload_index);
|
||||
const options_slice = sema.ir.bodySlice(choice_extra.end, choice_extra.data.cases_len);
|
||||
|
||||
var branch_labels: std.ArrayListUnmanaged(usize) = .empty;
|
||||
try branch_labels.ensureUnusedCapacity(sema.gpa, options_slice.len + 1);
|
||||
defer branch_labels.deinit(sema.gpa);
|
||||
|
||||
for (options_slice) |option_index| {
|
||||
const case_extra = sema.ir.extraData(Ir.Inst.ChoiceBr.Case, @intFromEnum(option_index));
|
||||
const case_label = try chunk.addLabel();
|
||||
branch_labels.appendAssumeCapacity(case_label);
|
||||
|
||||
switch (case_extra.data.operand_1) {
|
||||
.none => {},
|
||||
else => |content| {
|
||||
const content_inst = chunk.resolveInst(content);
|
||||
_ = try chunk.doLoad(content_inst);
|
||||
_ = try chunk.addByteOp(.stream_push);
|
||||
},
|
||||
}
|
||||
switch (case_extra.data.operand_2) {
|
||||
.none => {},
|
||||
else => |content| {
|
||||
const content_inst = chunk.resolveInst(content);
|
||||
_ = try chunk.doLoad(content_inst);
|
||||
_ = try chunk.addByteOp(.stream_push);
|
||||
},
|
||||
}
|
||||
|
||||
try chunk.addFixupAbsolute(.br_push, case_label);
|
||||
}
|
||||
|
||||
_ = try chunk.addByteOp(.br_table);
|
||||
_ = try chunk.addByteOp(.br_select_index);
|
||||
_ = try chunk.addByteOp(.br_dispatch);
|
||||
|
||||
for (options_slice, branch_labels.items) |option_index, label| {
|
||||
const case_extra = sema.ir.extraData(Ir.Inst.ChoiceBr.Case, @intFromEnum(option_index));
|
||||
const body_slice = sema.ir.bodySlice(case_extra.end, case_extra.data.body_len);
|
||||
|
||||
chunk.setLabel(label);
|
||||
|
||||
switch (case_extra.data.operand_1) {
|
||||
.none => {},
|
||||
else => |content| {
|
||||
const content_inst = chunk.resolveInst(content);
|
||||
_ = try chunk.doLoad(content_inst);
|
||||
_ = try chunk.addByteOp(.stream_push);
|
||||
},
|
||||
}
|
||||
switch (case_extra.data.operand_3) {
|
||||
.none => {},
|
||||
else => |content| {
|
||||
const content_inst = chunk.resolveInst(content);
|
||||
_ = try chunk.doLoad(content_inst);
|
||||
_ = try chunk.addByteOp(.stream_push);
|
||||
},
|
||||
}
|
||||
_ = try chunk.addByteOp(.stream_flush);
|
||||
|
||||
try blockBodyInner(sema, chunk, body_slice);
|
||||
}
|
||||
}
|
||||
|
||||
fn irImplicitRet(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref {
|
||||
return chunk.addByteOp(.exit);
|
||||
}
|
||||
|
||||
fn irDeclRef(sema: *Sema, _: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.string;
|
||||
return sema.getGlobal(data.start);
|
||||
|
|
@ -358,6 +428,11 @@ fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) Inner
|
|||
},
|
||||
.content_push => try irContentPush(sema, chunk, inst),
|
||||
.content_flush => try irContentFlush(sema, chunk, inst),
|
||||
.choice_br => {
|
||||
try irChoiceBr(sema, chunk, inst);
|
||||
continue;
|
||||
},
|
||||
.implicit_ret => try irImplicitRet(sema, chunk, inst),
|
||||
};
|
||||
try chunk.inst_map.put(gpa, inst, ref);
|
||||
}
|
||||
|
|
@ -446,6 +521,15 @@ const Chunk = struct {
|
|||
});
|
||||
}
|
||||
|
||||
fn addFixupAbsolute(chunk: *Chunk, op: Story.Opcode, label: usize) !void {
|
||||
const code_ref = try chunk.addJumpOp(op);
|
||||
return chunk.fixups.append(chunk.sema.gpa, .{
|
||||
.mode = .absolute,
|
||||
.label_index = @intCast(label),
|
||||
.code_offset = code_ref.index,
|
||||
});
|
||||
}
|
||||
|
||||
fn addLabel(chunk: *Chunk) error{OutOfMemory}!usize {
|
||||
const label_index = chunk.labels.items.len;
|
||||
try chunk.labels.append(chunk.sema.gpa, .{
|
||||
|
|
|
|||
|
|
@ -490,6 +490,8 @@ pub const LoadOptions = struct {
|
|||
dump_writer: ?*std.Io.Writer = null,
|
||||
stderr_writer: *std.Io.Writer,
|
||||
use_color: bool = true,
|
||||
dump_ast: bool = false,
|
||||
dump_ir: bool = false,
|
||||
};
|
||||
|
||||
pub fn selectChoiceIndex(story: *Story, index: usize) !void {
|
||||
|
|
@ -508,11 +510,13 @@ pub fn loadFromString(
|
|||
const arena = arena_allocator.allocator();
|
||||
const ast = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0);
|
||||
|
||||
if (options.dump_writer) |w| {
|
||||
try w.writeAll("=== AST ===\n");
|
||||
try ast.render(gpa, w, .{
|
||||
.use_color = options.use_color,
|
||||
});
|
||||
if (options.dump_ast) {
|
||||
if (options.dump_writer) |w| {
|
||||
try w.writeAll("=== AST ===\n");
|
||||
try ast.render(gpa, w, .{
|
||||
.use_color = options.use_color,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (ast.errors.len > 0) {
|
||||
try ast.renderErrors(gpa, options.stderr_writer, .{
|
||||
|
|
@ -532,10 +536,12 @@ pub fn loadFromString(
|
|||
return error.CompilationFailed;
|
||||
}
|
||||
|
||||
if (options.dump_writer) |w| {
|
||||
try w.writeAll("=== Semantic IR ===\n");
|
||||
try sem_ir.dumpInfo(w);
|
||||
try sem_ir.render(gpa, w);
|
||||
if (options.dump_ir) {
|
||||
if (options.dump_writer) |w| {
|
||||
try w.writeAll("=== Semantic IR ===\n");
|
||||
try sem_ir.dumpInfo(w);
|
||||
try sem_ir.render(gpa, w);
|
||||
}
|
||||
}
|
||||
|
||||
var compiled = try Sema.compile(gpa, &sem_ir);
|
||||
|
|
|
|||
12
src/main.zig
12
src/main.zig
|
|
@ -34,6 +34,8 @@ fn mainArgs(
|
|||
var arg_index: usize = 1;
|
||||
var compile_only: bool = false;
|
||||
var dump_ast: bool = false;
|
||||
var dump_ir: bool = false;
|
||||
var dump_story: bool = false;
|
||||
var use_stdin: bool = false;
|
||||
var use_color: bool = false;
|
||||
|
||||
|
|
@ -46,6 +48,10 @@ fn mainArgs(
|
|||
compile_only = true;
|
||||
} else if (std.mem.eql(u8, arg, "--dump-ast")) {
|
||||
dump_ast = true;
|
||||
} else if (std.mem.eql(u8, arg, "--dump-ir")) {
|
||||
dump_ir = true;
|
||||
} else if (std.mem.eql(u8, arg, "--dump-story")) {
|
||||
dump_story = true;
|
||||
} else if (std.mem.eql(u8, arg, "--use-color")) {
|
||||
use_color = true;
|
||||
} else {
|
||||
|
|
@ -86,10 +92,14 @@ fn mainArgs(
|
|||
.stderr_writer = &stderr_writer.interface,
|
||||
.dump_writer = &stdout_writer.interface,
|
||||
.use_color = use_color,
|
||||
.dump_ast = dump_ast,
|
||||
.dump_ir = dump_ir,
|
||||
});
|
||||
defer story.deinit();
|
||||
|
||||
try story.dump(&stderr_writer.interface);
|
||||
if (dump_story) {
|
||||
try story.dump(&stderr_writer.interface);
|
||||
}
|
||||
if (compile_only) return;
|
||||
|
||||
while (!story.is_exited and story.can_advance) {
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ test {
|
|||
_ = tokenizer;
|
||||
_ = Ast;
|
||||
_ = Story;
|
||||
_ = @import("runtime_tests.zig");
|
||||
}
|
||||
|
|
|
|||
73
src/runtime_tests.zig
Normal file
73
src/runtime_tests.zig
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
const std = @import("std");
|
||||
const fatal = std.process.fatal;
|
||||
const ink = @import("root.zig");
|
||||
|
||||
const Options = struct {
|
||||
input_reader: *std.Io.Reader,
|
||||
error_writer: *std.Io.Writer,
|
||||
transcript_writer: *std.Io.Writer,
|
||||
};
|
||||
|
||||
fn testRunner(gpa: std.mem.Allocator, source_bytes: [:0]const u8, options: Options) !void {
|
||||
const io_r = options.input_reader;
|
||||
const io_w = options.transcript_writer;
|
||||
var story = try ink.Story.loadFromString(gpa, source_bytes, .{
|
||||
.stderr_writer = options.error_writer,
|
||||
});
|
||||
defer story.deinit();
|
||||
|
||||
while (!story.is_exited and story.can_advance) {
|
||||
while (story.can_advance) {
|
||||
const content_text = try story.advance(gpa);
|
||||
defer gpa.free(content_text);
|
||||
if (content_text.len != 0) {
|
||||
try io_w.print("{s}\n", .{content_text});
|
||||
}
|
||||
}
|
||||
if (story.current_choices.items.len > 0) {
|
||||
for (story.current_choices.items, 0..) |*choice, index| {
|
||||
const choice_text = try choice.text.toOwnedSlice(gpa);
|
||||
defer gpa.free(choice_text);
|
||||
try io_w.print("{d}: {s}\n", .{ index + 1, choice_text });
|
||||
}
|
||||
try io_w.print("?> ", .{});
|
||||
|
||||
const input_line = try io_r.takeDelimiter('\n');
|
||||
if (input_line) |bytes| {
|
||||
const choice_index = try std.fmt.parseUnsigned(usize, bytes, 10);
|
||||
try story.selectChoiceIndex(if (choice_index == 0) 0 else choice_index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return io_w.flush();
|
||||
}
|
||||
|
||||
fn testRuntimeFixture(comptime fixture: []const u8) !void {
|
||||
const test_root = "testdata/";
|
||||
const source_bytes = @embedFile(test_root ++ fixture ++ "/story.ink");
|
||||
const transcript_bytes = @embedFile(test_root ++ fixture ++ "/transcript.txt");
|
||||
const input_bytes = @embedFile(test_root ++ fixture ++ "/input.txt");
|
||||
var stderr_buffer: [1024]u8 = undefined;
|
||||
const gpa = std.testing.allocator;
|
||||
const stderr = std.fs.File.stderr();
|
||||
|
||||
var io_r = std.Io.Reader.fixed(input_bytes);
|
||||
var io_w = std.Io.Writer.Allocating.init(gpa);
|
||||
defer io_w.deinit();
|
||||
var stderr_writer = stderr.writer(&stderr_buffer);
|
||||
|
||||
try testRunner(gpa, source_bytes, .{
|
||||
.input_reader = &io_r,
|
||||
.error_writer = &stderr_writer.interface,
|
||||
.transcript_writer = &io_w.writer,
|
||||
});
|
||||
return std.testing.expectEqualSlices(u8, transcript_bytes, io_w.written());
|
||||
}
|
||||
|
||||
test "fixture - hello world" {
|
||||
try testRuntimeFixture("hello-world");
|
||||
}
|
||||
|
||||
test "fixture - monsieur-fogg" {
|
||||
try testRuntimeFixture("monsieur-fogg");
|
||||
}
|
||||
0
src/testdata/hello-world/input.txt
vendored
Normal file
0
src/testdata/hello-world/input.txt
vendored
Normal file
1
src/testdata/hello-world/story.ink
vendored
Normal file
1
src/testdata/hello-world/story.ink
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Hello, world!
|
||||
1
src/testdata/hello-world/transcript.txt
vendored
Normal file
1
src/testdata/hello-world/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Hello, world!
|
||||
1
src/testdata/monsieur-fogg/input.txt
vendored
Normal file
1
src/testdata/monsieur-fogg/input.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
3
|
||||
7
src/testdata/monsieur-fogg/story.ink
vendored
Normal file
7
src/testdata/monsieur-fogg/story.ink
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"What's that?" my master asked.
|
||||
* "I am somewhat tired[."]," I repeated.
|
||||
"Really," he responded. "How deleterious."
|
||||
* "Nothing, Monsieur!"[] I replied.
|
||||
"Very good, then."
|
||||
* "I said, this journey is appalling[."] and I want no more of it."
|
||||
"Ah," he replied, not unkindly. "I see you are feeling frustrated. Tomorrow, things will improve."
|
||||
6
src/testdata/monsieur-fogg/transcript.txt
vendored
Normal file
6
src/testdata/monsieur-fogg/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"What's that?" my master asked.
|
||||
1: "I am somewhat tired."
|
||||
2: "Nothing, Monsieur!"
|
||||
3: "I said, this journey is appalling."
|
||||
?> "I said, this journey is appalling and I want no more of it."
|
||||
"Ah," he replied, not unkindly. "I see you are feeling frustrated. Tomorrow, things will improve."
|
||||
Loading…
Add table
Add a link
Reference in a new issue