WIP scanning, analysis and AST todo
This commit is contained in:
parent
dc6b360583
commit
38eab409c6
@ -1,4 +1,61 @@
|
|||||||
[Duct Tape]
|
DuctTape:
|
||||||
d:The tape that does it all.
|
str="Duct Tape"
|
||||||
|
status=[.Full, .Empty]
|
||||||
|
location=Warehouse
|
||||||
|
|
||||||
[Flash Light]
|
FlashLight:
|
||||||
|
str="Flash Light"
|
||||||
|
status=[.Bright, .Dead, .Broken]
|
||||||
|
_onExamine:
|
||||||
|
if $self.status = .Bright:
|
||||||
|
A handy tool for seeing your way out.
|
||||||
|
else if $self.status = .Dead:
|
||||||
|
A handy tool for seeing your way out, unfortunately its out of batteries.
|
||||||
|
else:
|
||||||
|
A handy tool for seeing your way out, unfortunately its busted.
|
||||||
|
|
||||||
|
_onUse:
|
||||||
|
if $context.args.contains(Batteries) and $self.status = .Dead:
|
||||||
|
$self.status = .Bright
|
||||||
|
$player.inventory.remove(Batteries)
|
||||||
|
|
||||||
|
You screw the back on tight, and the light comes on!
|
||||||
|
_onFix:
|
||||||
|
if $context.args.contains(DuctTape) and $self.status = .Broken:
|
||||||
|
$self.status = .Dead
|
||||||
|
$DuctTape.status = .Empty
|
||||||
|
|
||||||
|
Nothing duct tape cant fix! The flashlight just needs some power.
|
||||||
|
|
||||||
|
Batteries:
|
||||||
|
_onExamine:
|
||||||
|
Energized little cylinders, good for something...
|
||||||
|
|
||||||
|
# Hammer:
|
||||||
|
|
||||||
|
# [Hammer]
|
||||||
|
# desc= Good for breaking things
|
||||||
|
# status=Solid
|
||||||
|
# rel=Flash Light=>Broken
|
||||||
|
|
||||||
|
# [Key1]
|
||||||
|
# name= Key
|
||||||
|
# desc= A mysterious key
|
||||||
|
|
||||||
|
# [Key2]
|
||||||
|
# name= Key
|
||||||
|
# desc= A different key, not to be confused with the other
|
||||||
|
|
||||||
|
# [Locked Chest]
|
||||||
|
# decs= A large chest with two key holes
|
||||||
|
# status=Locked,Closed,Opened
|
||||||
|
# *Open=
|
||||||
|
# &Locked::Its locked
|
||||||
|
# &Opened::Its already open
|
||||||
|
# &:s.Opened:The large chest swings open.
|
||||||
|
# *Close=
|
||||||
|
# &Locked::[Its locked, and therefore closed. You arent strong enough to break it with your hands.<Hammer.s=&Solid>You cant break it open, but maybe with something tough enough...]
|
||||||
|
# &Closed::Its already closed!!
|
||||||
|
# &Opened:s.Closed:The chest closes with a clunk.
|
||||||
|
# *Unlock=
|
||||||
|
# &
|
||||||
|
|||||||
161
cyo_spec.md
Normal file
161
cyo_spec.md
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# Mindset
|
||||||
|
|
||||||
|
In the world of text adventures, at any given moment, a player:
|
||||||
|
- Resides in a specific location
|
||||||
|
- Can issue any arbitrary command
|
||||||
|
- Said command should never cause an error
|
||||||
|
- Posseses an inventory with any number of objects
|
||||||
|
-
|
||||||
|
|
||||||
|
All entities in a text adventure world are typically broken into two categories: scenes and objects.
|
||||||
|
But, I believe there is good reason to abstract all entities into just objects.
|
||||||
|
|
||||||
|
Then, objects are duck-typed into specific things by way of their available methods.
|
||||||
|
Methods include but are not limited to:
|
||||||
|
- `_onEnter`, used for scenes, to read out text of the location
|
||||||
|
- `_onExit`, used for scenes, to direct the player to the next destination
|
||||||
|
- `_onAcquire`, used for objects that are meant to be kept
|
||||||
|
- `_onInpect`, used for any object when the player wishes to know more
|
||||||
|
|
||||||
|
A graph markdown was deemed insufficient for cyano, as my stories are not going to fit cleanly along
|
||||||
|
some API. What I really will need to be able to do is code it, as any good engine of any kind.
|
||||||
|
|
||||||
|
Goals for the language:
|
||||||
|
|
||||||
|
## 1. Reading as First Class
|
||||||
|
A story is only worth what its words convey, and you only have a good story if you can read it.
|
||||||
|
|
||||||
|
Imagine a file whose contents read:
|
||||||
|
```
|
||||||
|
func goToWindow:
|
||||||
|
print("You approach the window and its clear you could survive the jump");
|
||||||
|
input = get_input()
|
||||||
|
if input == "jump":
|
||||||
|
print("You fall gracefully to the ground")
|
||||||
|
else if
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
It honestly would be just as well to scrap this project and write a one-off in python.
|
||||||
|
The purpose of writing an entire engine would be to pull away all of the unnecessary bits and bytes
|
||||||
|
of sequencing, IO, object relations and requirements and let the writer focus on the actual writing.
|
||||||
|
|
||||||
|
Instead picture a file like this:
|
||||||
|
```
|
||||||
|
_onEnter:
|
||||||
|
You approach the window and its clear you could survive the jump
|
||||||
|
|
||||||
|
_onJump:
|
||||||
|
You fall gracefully to the ground
|
||||||
|
```
|
||||||
|
|
||||||
|
This is still the outset, so more syntax may be required, but the goal would be to make everything
|
||||||
|
else get out of the way, and reading your story takes the front seat.
|
||||||
|
|
||||||
|
## 2a. Arbitrary Functionality
|
||||||
|
A story can take us anywhere. So why would the engine that powers it only allow objects to have one of up to
|
||||||
|
a maximum of three conditions (Opened, Closed, Locked).
|
||||||
|
|
||||||
|
In reality, a chest could be Opened, Broken, Dusty, Sentient and who knows what else. The goal would be to allow
|
||||||
|
the writer to have utmost freedom in writing the story they want to tell.
|
||||||
|
|
||||||
|
## 2b. Safe Operation
|
||||||
|
However, we dont want a dynamic story to have dead ends, missing links, or endless loops. When a chest depends on
|
||||||
|
a key that doesnt exist? Thats no good. We want as many conditions as possible to be checked as soon as possible
|
||||||
|
in the writing process to help make the greatest story that has yet to be experienced.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Cyo Spec
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Cyano's underlying framework is using callbacks. You can think of it as defining a series of triggers and effects.
|
||||||
|
Essentially, nothing happens without the user doing something, so we dont need the cyano engine to "run" code.
|
||||||
|
Instead, it should read the user input, figure out what they mean, then lookup the associated trigger that the user is tripping.
|
||||||
|
|
||||||
|
Triggers could be anything:
|
||||||
|
- Entering a room
|
||||||
|
- Combining things in their inventory
|
||||||
|
- Putting a monkey idol on a pillar
|
||||||
|
- Reaching zero health
|
||||||
|
|
||||||
|
So, with these two concepts: entities and callbacks (triggers), we can create any story with any sort of form.
|
||||||
|
|
||||||
|
Want to make it RPG? Create a player entity with stats, and create triggers like `_onKillMob`, `_onLevelUp`.
|
||||||
|
Traditional text escape adventure? Chest entity with `_onUnlock`, `_onOpen`.
|
||||||
|
|
||||||
|
## Nitty Gritty
|
||||||
|
|
||||||
|
### Entities
|
||||||
|
Defining Entities:
|
||||||
|
- A file can define multiple entities only if these definitions are qualified.
|
||||||
|
- A file cannot have unqualified and qualified definitions.
|
||||||
|
- A file can only define one set of unqualified definitions and no others.
|
||||||
|
- Entity identifiers are case-insensitive, and can only contain letters, numbers, and underscores.
|
||||||
|
|
||||||
|
Qualified Definition:
|
||||||
|
```cyo
|
||||||
|
scenes.cyo
|
||||||
|
|
||||||
|
_onEnter_LivingRoom:
|
||||||
|
_onLeave_LivingRoom:
|
||||||
|
_onExplode_LivingRoom:
|
||||||
|
```
|
||||||
|
|
||||||
|
Unqualified Definition:
|
||||||
|
```cyo
|
||||||
|
living_room.cyo
|
||||||
|
|
||||||
|
_onEnter:
|
||||||
|
_onLeave:
|
||||||
|
_onExplode:
|
||||||
|
```
|
||||||
|
|
||||||
|
Both would create internally a LivingRoom entity.
|
||||||
|
In the case of an unqualified definition, the basename of the file is used as the entities identifier. Underscores in
|
||||||
|
the filename will be removed. If an underscore is desired in the identifier, the filename would need two:
|
||||||
|
`living__room.cyo`
|
||||||
|
|
||||||
|
### Global State
|
||||||
|
Global state is accessible within any callback with said object properties TBD but would look like:
|
||||||
|
```cyo
|
||||||
|
|
||||||
|
_onRead:
|
||||||
|
if $player.wearingPendant:
|
||||||
|
The book's text comes to life!
|
||||||
|
|
||||||
|
else:
|
||||||
|
The book's dull pages seem ready to fall apart with age.
|
||||||
|
```
|
||||||
|
|
||||||
|
Attributes that would be handy for the global state to hold:
|
||||||
|
- Inventory
|
||||||
|
- Current location
|
||||||
|
- Recent action(s)
|
||||||
|
- Arbitrary stats (author defined)
|
||||||
|
|
||||||
|
Should these all reside in the `$player` object? This will probably evolve as need arises.
|
||||||
|
|
||||||
|
### Entity State
|
||||||
|
You want to open a door, but only if the pressure plate in the room is weighed down with something. Entities need to be
|
||||||
|
able to define and track arbitrary internal state. This might look something like:
|
||||||
|
```cyo
|
||||||
|
PressurePlate:
|
||||||
|
pressed=false
|
||||||
|
|
||||||
|
_onInspect_PressurePlate:
|
||||||
|
if $pressed:
|
||||||
|
"Its depressed, now what?"
|
||||||
|
else if $player.:
|
||||||
|
"Its barely noticable on the dusty stone floor, but you can just make out the lip of the plate. Something heavy could depress it."
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Callback Rules
|
||||||
@ -1,7 +1,29 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const CyoContent = @import("../cyo/cyo.zig").content;
|
const CyoContent = @import("../cyo/cyo.zig").content;
|
||||||
|
|
||||||
const DEFAULT_CYO_SOURCE_PATH = "cyo";
|
const DEFAULT_CYO_SOURCE_PATH = "./cyo";
|
||||||
|
|
||||||
|
const Token = struct { line: u32, pos: u32, lexeme: Lexeme, contents: []const u8 };
|
||||||
|
|
||||||
|
const Lexeme = enum {
|
||||||
|
Lt, // <
|
||||||
|
Gt, // >
|
||||||
|
LeftParen, // (
|
||||||
|
RightParen, // )
|
||||||
|
LeftBracket, // [
|
||||||
|
RightBracket, // ]
|
||||||
|
Text, // Foo bar blah. Another.
|
||||||
|
Dollar, // $
|
||||||
|
Period, // .
|
||||||
|
Colon, // :
|
||||||
|
Equals, // =
|
||||||
|
Hashtag, // #
|
||||||
|
Underscore, // _
|
||||||
|
Newline, // \n
|
||||||
|
Tab, // \t
|
||||||
|
Space, // ' '
|
||||||
|
Eof, //
|
||||||
|
};
|
||||||
|
|
||||||
pub const CyoError = error{ BadSource, BadIter };
|
pub const CyoError = error{ BadSource, BadIter };
|
||||||
|
|
||||||
@ -38,21 +60,22 @@ pub const CyoParser = struct {
|
|||||||
var files_contents = std.StringHashMap([]const u8).init(allocator);
|
var files_contents = std.StringHashMap([]const u8).init(allocator);
|
||||||
try walkDirs(allocator, cyo_dir, 0, &files_contents);
|
try walkDirs(allocator, cyo_dir, 0, &files_contents);
|
||||||
|
|
||||||
var iter = files_contents.keyIterator();
|
var files_tokens = try lexCyoFiles(allocator, files_contents);
|
||||||
|
printFilesTokens(files_tokens);
|
||||||
|
|
||||||
|
var iter = files_tokens.keyIterator();
|
||||||
while (iter.next()) |key| {
|
while (iter.next()) |key| {
|
||||||
const content = files_contents.get(key.*);
|
const tokens = files_tokens.get(key.*);
|
||||||
if (content) |c| {
|
if (tokens) |t| {
|
||||||
std.debug.print("Got key: {s}\nContent:{s}\n\n", .{ key.*, c });
|
t.deinit();
|
||||||
} else {
|
|
||||||
std.debug.print("Got key: {s}\nContent empty\n\n", .{key.*});
|
|
||||||
}
|
}
|
||||||
|
allocator.free(key.*);
|
||||||
}
|
}
|
||||||
|
files_tokens.deinit();
|
||||||
// var path_buf: [128]u8 = undefined;
|
|
||||||
|
|
||||||
// const cyo_dir_path = cyo_dir.realpath(pathname: []const u8, out_buffer: []u8)
|
// const cyo_dir_path = cyo_dir.realpath(pathname: []const u8, out_buffer: []u8)
|
||||||
|
|
||||||
// 2. process files
|
// 2. process files
|
||||||
|
|
||||||
// 2a. lexical - validate file syntax
|
// 2a. lexical - validate file syntax
|
||||||
// 2b. syntactic parsing
|
// 2b. syntactic parsing
|
||||||
// 2c. semantic - create objects and scenes
|
// 2c. semantic - create objects and scenes
|
||||||
@ -61,6 +84,7 @@ pub const CyoParser = struct {
|
|||||||
return CyoContent{ .allocator = allocator, .files_contents = files_contents };
|
return CyoContent{ .allocator = allocator, .files_contents = files_contents };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively walks through directory to get all .cyo files
|
||||||
fn walkDirs(allocator: std.mem.Allocator, cyo_dir: std.fs.Dir, depth: u8, files_contents: *std.StringHashMap([]const u8)) !void {
|
fn walkDirs(allocator: std.mem.Allocator, cyo_dir: std.fs.Dir, depth: u8, files_contents: *std.StringHashMap([]const u8)) !void {
|
||||||
var cyo_iter = cyo_dir.iterate();
|
var cyo_iter = cyo_dir.iterate();
|
||||||
while (cyo_iter.next() catch |err| {
|
while (cyo_iter.next() catch |err| {
|
||||||
@ -69,11 +93,15 @@ pub const CyoParser = struct {
|
|||||||
}) |cyo_entry| {
|
}) |cyo_entry| {
|
||||||
switch (cyo_entry.kind) {
|
switch (cyo_entry.kind) {
|
||||||
.file => {
|
.file => {
|
||||||
// std.fs.cyo_entry.name;
|
// only grab `.cyo` files
|
||||||
|
if (cyo_entry.name.len <= 3 or !std.mem.eql(u8, ".cyo", cyo_entry.name[cyo_entry.name.len - 4 .. cyo_entry.name.len])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (0..depth) |_| {
|
for (0..depth) |_| {
|
||||||
std.debug.print("\t", .{});
|
std.debug.print("\t", .{});
|
||||||
}
|
}
|
||||||
std.debug.print("- File: {s}\n", .{cyo_entry.name});
|
// std.debug.print("- File: {s}\n", .{cyo_entry.name});
|
||||||
const file_path = try cyo_dir.realpathAlloc(allocator, cyo_entry.name);
|
const file_path = try cyo_dir.realpathAlloc(allocator, cyo_entry.name);
|
||||||
var cyo_file = try std.fs.openFileAbsolute(file_path, .{ .mode = .read_only });
|
var cyo_file = try std.fs.openFileAbsolute(file_path, .{ .mode = .read_only });
|
||||||
|
|
||||||
@ -84,7 +112,7 @@ pub const CyoParser = struct {
|
|||||||
for (0..depth) |_| {
|
for (0..depth) |_| {
|
||||||
std.debug.print("\t", .{});
|
std.debug.print("\t", .{});
|
||||||
}
|
}
|
||||||
std.debug.print("Dir: {s}\n", .{cyo_entry.name});
|
// std.debug.print("Dir: {s}\n", .{cyo_entry.name});
|
||||||
const dir_path = try cyo_dir.realpathAlloc(allocator, cyo_entry.name);
|
const dir_path = try cyo_dir.realpathAlloc(allocator, cyo_entry.name);
|
||||||
defer allocator.free(dir_path);
|
defer allocator.free(dir_path);
|
||||||
|
|
||||||
@ -92,18 +120,220 @@ pub const CyoParser = struct {
|
|||||||
try walkDirs(allocator, new_cyo_dir, depth + 1, files_contents);
|
try walkDirs(allocator, new_cyo_dir, depth + 1, files_contents);
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
std.debug.print("ignoring other types...", .{});
|
// std.debug.print("ignoring other types...", .{});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn printFilesContents(files_contents: std.StringHashMap([]const u8)) void {
|
||||||
|
var iter = files_contents.keyIterator();
|
||||||
|
while (iter.next()) |key| {
|
||||||
|
const content = files_contents.get(key.*);
|
||||||
|
if (content) |c| {
|
||||||
|
std.debug.print("Got key: {s}\nContent:{s}\n\n", .{ key.*, c });
|
||||||
|
} else {
|
||||||
|
std.debug.print("Got key: {s}\nContent empty\n\n", .{key.*});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lexCyoFiles(allocator: std.mem.Allocator, files_contents: std.StringHashMap([]const u8)) !std.StringHashMap(std.ArrayList(Token)) {
|
||||||
|
var files_tokens = std.StringHashMap(std.ArrayList(Token)).init(allocator);
|
||||||
|
errdefer {
|
||||||
|
var iter = files_tokens.keyIterator();
|
||||||
|
while (iter.next()) |key| {
|
||||||
|
const tokens = files_tokens.get(key.*);
|
||||||
|
if (tokens) |t| {
|
||||||
|
t.deinit();
|
||||||
|
}
|
||||||
|
allocator.free(key.*);
|
||||||
|
}
|
||||||
|
files_tokens.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
var iter = files_contents.keyIterator();
|
||||||
|
while (iter.next()) |key| {
|
||||||
|
const content = files_contents.get(key.*);
|
||||||
|
if (content == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokens = std.ArrayList(Token).init(allocator);
|
||||||
|
const c = content.?;
|
||||||
|
var i: u32 = 0;
|
||||||
|
var line: u32 = 1;
|
||||||
|
var col: u32 = 1;
|
||||||
|
while (i < c.len) {
|
||||||
|
const lexeme = charToLexeme(c[i]);
|
||||||
|
switch (lexeme) {
|
||||||
|
// whitespace
|
||||||
|
.Space, .Tab => {
|
||||||
|
const char_repeats = greedyCapture(c, i);
|
||||||
|
for (0..char_repeats) |_| {
|
||||||
|
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + char_repeats] });
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
i += char_repeats;
|
||||||
|
},
|
||||||
|
.Newline => {
|
||||||
|
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + 1] });
|
||||||
|
line += 1;
|
||||||
|
col = 1;
|
||||||
|
i += 1;
|
||||||
|
},
|
||||||
|
// symbols
|
||||||
|
.Gt,
|
||||||
|
.Lt,
|
||||||
|
.LeftParen,
|
||||||
|
.RightParen,
|
||||||
|
.LeftBracket,
|
||||||
|
.RightBracket,
|
||||||
|
.Dollar,
|
||||||
|
.Period,
|
||||||
|
.Equals,
|
||||||
|
.Colon,
|
||||||
|
.Underscore,
|
||||||
|
=> {
|
||||||
|
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + 1] });
|
||||||
|
// check length of content is one
|
||||||
|
std.debug.assert(tokens.items[tokens.items.len - 1].contents.len == 1);
|
||||||
|
|
||||||
|
col += 1;
|
||||||
|
i += 1;
|
||||||
|
},
|
||||||
|
// text
|
||||||
|
.Text => {
|
||||||
|
const text_length = captureText(c, i);
|
||||||
|
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + text_length] });
|
||||||
|
|
||||||
|
col += text_length;
|
||||||
|
i += text_length;
|
||||||
|
},
|
||||||
|
.Hashtag => {
|
||||||
|
const to_end_of_line = captureLine(c, i);
|
||||||
|
// TODO for testing, remove as we dont need to save comments
|
||||||
|
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + to_end_of_line] });
|
||||||
|
|
||||||
|
col += to_end_of_line;
|
||||||
|
i += to_end_of_line;
|
||||||
|
},
|
||||||
|
.Eof => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add eof token
|
||||||
|
try tokens.append(.{ .line = line, .pos = col, .lexeme = Lexeme.Eof, .contents = "" });
|
||||||
|
|
||||||
|
// Add tokens to hashmap
|
||||||
|
const key_copy = try allocator.alloc(u8, key.len);
|
||||||
|
std.mem.copyForwards(u8, key_copy, key.*);
|
||||||
|
try files_tokens.put(key_copy, tokens);
|
||||||
|
}
|
||||||
|
return files_tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printFilesTokens(files_tokens: std.StringHashMap(std.ArrayList(Token))) void {
|
||||||
|
var iter = files_tokens.keyIterator();
|
||||||
|
while (iter.next()) |key| {
|
||||||
|
std.debug.print("File: {s}", .{key.*});
|
||||||
|
const tokens = files_tokens.get(key.*);
|
||||||
|
if (tokens) |ts| {
|
||||||
|
for (ts.items) |token| {
|
||||||
|
std.debug.print("\tGot Token: {s}\tCol{d}:L{d}\t{s}\n", .{ @tagName(token.lexeme), token.pos, token.line, token.contents });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn charToLexeme(char: u8) Lexeme {
|
||||||
|
switch (char) {
|
||||||
|
' ' => {
|
||||||
|
return .Space;
|
||||||
|
},
|
||||||
|
'\t' => {
|
||||||
|
return .Tab;
|
||||||
|
},
|
||||||
|
'\n' => {
|
||||||
|
return .Newline;
|
||||||
|
},
|
||||||
|
'<' => {
|
||||||
|
return .Lt;
|
||||||
|
},
|
||||||
|
'>' => {
|
||||||
|
return .Gt;
|
||||||
|
},
|
||||||
|
'(' => {
|
||||||
|
return .LeftParen;
|
||||||
|
},
|
||||||
|
')' => {
|
||||||
|
return .RightParen;
|
||||||
|
},
|
||||||
|
'[' => {
|
||||||
|
return .LeftBracket;
|
||||||
|
},
|
||||||
|
']' => {
|
||||||
|
return .RightBracket;
|
||||||
|
},
|
||||||
|
'$' => {
|
||||||
|
return .Dollar;
|
||||||
|
},
|
||||||
|
'.' => {
|
||||||
|
return .Period;
|
||||||
|
},
|
||||||
|
'=' => {
|
||||||
|
return .Equals;
|
||||||
|
},
|
||||||
|
':' => {
|
||||||
|
return .Colon;
|
||||||
|
},
|
||||||
|
'_' => {
|
||||||
|
return .Underscore;
|
||||||
|
},
|
||||||
|
'#' => {
|
||||||
|
return .Hashtag;
|
||||||
|
},
|
||||||
|
else => return .Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// given a char, it returns the number of bytes that char repeats forward starting at `i`
|
||||||
|
fn greedyCapture(seq: []const u8, i: u32) u32 {
|
||||||
|
const cap_char = seq[i];
|
||||||
|
var j = i;
|
||||||
|
while (j < seq.len and seq[j] == cap_char) {
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
return j - i;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn captureText(seq: []const u8, i: u32) u32 {
|
||||||
|
var j = i;
|
||||||
|
while (j < seq.len and charToLexeme(seq[j]) == .Text) {
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
return j - i;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn captureLine(seq: []const u8, i: u32) u32 {
|
||||||
|
var j = i;
|
||||||
|
while (j < seq.len) {
|
||||||
|
const lex = charToLexeme(seq[j]);
|
||||||
|
if (lex == .Newline or lex == .Eof) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
return j - i;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test "parse test" {
|
test "parse test" {
|
||||||
|
// TODO, programatically create a test directory instead of relying on the test directory to have the right contents
|
||||||
const cyo_test_dir_path = try std.fs.cwd().realpathAlloc(std.testing.allocator, "./test/cyo_test_dir");
|
const cyo_test_dir_path = try std.fs.cwd().realpathAlloc(std.testing.allocator, "./test/cyo_test_dir");
|
||||||
defer std.testing.allocator.free(cyo_test_dir_path);
|
defer std.testing.allocator.free(cyo_test_dir_path);
|
||||||
var cyo_parser = try CyoParser.init(std.testing.allocator, cyo_test_dir_path);
|
var cyo_parser = try CyoParser.init(std.testing.allocator, cyo_test_dir_path);
|
||||||
defer cyo_parser.deinit();
|
defer cyo_parser.deinit();
|
||||||
|
|
||||||
try std.testing.expectEqual(6, cyo_parser.cyo_content.files_contents.count());
|
// Verify reading in of correct files
|
||||||
|
try std.testing.expectEqual(7, cyo_parser.cyo_content.files_contents.count());
|
||||||
}
|
}
|
||||||
|
|||||||
1
test/cyo_test_dir/actions.cyo
Normal file
1
test/cyo_test_dir/actions.cyo
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -1,4 +1,32 @@
|
|||||||
[Duct Tape]
|
DuctTape:
|
||||||
d:The tape that does it all.
|
str="Duct Tape"
|
||||||
|
status=[.Full, .Empty]
|
||||||
|
location=Warehouse
|
||||||
|
|
||||||
[Flash Light]
|
FlashLight:
|
||||||
|
str="Flash Light"
|
||||||
|
status=[.Bright, .Dead, .Broken]
|
||||||
|
_onExamine:
|
||||||
|
if $self.status = .Bright:
|
||||||
|
A handy tool for seeing your way out.
|
||||||
|
else if $self.status = .Dead:
|
||||||
|
A handy tool for seeing your way out, unfortunately its out of batteries.
|
||||||
|
else:
|
||||||
|
A handy tool for seeing your way out, unfortunately its busted.
|
||||||
|
|
||||||
|
_onUse:
|
||||||
|
if $context.args.contains(Batteries) and $self.status = .Dead:
|
||||||
|
$self.status = .Bright
|
||||||
|
$player.inventory.remove(Batteries)
|
||||||
|
|
||||||
|
You screw the back on tight, and the light comes on!
|
||||||
|
_onFix:
|
||||||
|
if $context.args.contains(DuctTape) and $self.status = .Broken:
|
||||||
|
$self.status = .Dead
|
||||||
|
$DuctTape.status = .Empty
|
||||||
|
|
||||||
|
Nothing duct tape cant fix! The flashlight just needs some power.
|
||||||
|
|
||||||
|
Batteries:
|
||||||
|
_onExamine:
|
||||||
|
Energized little cylinders, good for something...
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
[Warehouse]
|
Warehouse:
|
||||||
|
_onEnter:
|
||||||
|
Your vision blurs as you fumble for the tablet. The glow of your fingers is nearly gone. The salty tablet is the last
|
||||||
|
sensation before everything goes dark.
|
||||||
|
<pause3>
|
||||||
|
<clear>
|
||||||
|
You awaken. How long has it been? Thanks to your dull glow, you can read the clock on the wall. 8:36. Only a few hours.
|
||||||
|
Thank God you brought one with you. <p1> But its not a cure, just a band-aid. <p4> Time is ticking. <p1> You've got 24 hours
|
||||||
|
to breathe, better get back before then.
|
||||||
|
|
||||||
Your vision blurs as you fumble for the tablet. The glow of your fingers is nearly gone. The salty tablet is the last
|
_onExamine:
|
||||||
sensation before everything goes dark.
|
It might have been an Amazon warehouse. Everything seems sacked. Empty shelves twist in some corporate labrynth before you.
|
||||||
|
Would have been nice to have something to show for this trip. But information isn't useless.
|
||||||
---
|
|
||||||
|
|
||||||
You awaken. How long has it been? Thanks to your dull glow, you can read the clock on the wall. 8:36. Only a few hours.
|
|
||||||
Thank God you brought one with you. <p1> But its not a cure, just a band-aid. <p4> Time is ticking. <p1> You've got 24 hours
|
|
||||||
to breathe, better get back before then.
|
|
||||||
|
|
||||||
[Warehouse.d]
|
|
||||||
It might have been an Amazon warehouse. Everything seems sacked. Empty shelves twist in some corporate labrynth before you.
|
|
||||||
Would have been nice to have something to show for this trip. But information isn't useless.
|
|
||||||
|
|
||||||
[Warehouse.i]
|
|
||||||
Duct Tape
|
|
||||||
|
|||||||
@ -1 +1,25 @@
|
|||||||
[Bathroom]
|
Bathroom:
|
||||||
|
washed=false
|
||||||
|
|
||||||
|
_onEnter:
|
||||||
|
Its a normal bathroom.
|
||||||
|
if not $washed:
|
||||||
|
Should probably wash up.
|
||||||
|
|
||||||
|
_onLook:
|
||||||
|
Bathroom complete with sink, faucet, the whole lot.
|
||||||
|
|
||||||
|
_onWash:
|
||||||
|
$washed=true
|
||||||
|
Nice cold water.
|
||||||
|
|
||||||
|
_beforeOnWash:
|
||||||
|
if $washed:
|
||||||
|
You dont need to wash again.
|
||||||
|
_block
|
||||||
|
|
||||||
|
_afterOnWash:
|
||||||
|
if not $previous.bathroom.washed:
|
||||||
|
You feel refreshed.
|
||||||
|
<pause2>
|
||||||
|
Time to get to work.
|
||||||
|
|||||||
1
test/cyo_test_dir/scenes/bathroom.foo
Normal file
1
test/cyo_test_dir/scenes/bathroom.foo
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Bathroom]
|
||||||
Loading…
Reference in New Issue
Block a user