WIP scanning, analysis and AST todo
This commit is contained in:
		
							parent
							
								
									dc6b360583
								
							
						
					
					
						commit
						38eab409c6
					
				@ -1,4 +1,61 @@
 | 
			
		||||
[Duct Tape]
 | 
			
		||||
d:The tape that does it all.
 | 
			
		||||
DuctTape:
 | 
			
		||||
	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 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 };
 | 
			
		||||
 | 
			
		||||
@ -38,21 +60,22 @@ pub const CyoParser = struct {
 | 
			
		||||
        var files_contents = std.StringHashMap([]const u8).init(allocator);
 | 
			
		||||
        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| {
 | 
			
		||||
            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.*});
 | 
			
		||||
            const tokens = files_tokens.get(key.*);
 | 
			
		||||
            if (tokens) |t| {
 | 
			
		||||
                t.deinit();
 | 
			
		||||
            }
 | 
			
		||||
            allocator.free(key.*);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // var path_buf: [128]u8 = undefined;
 | 
			
		||||
 | 
			
		||||
        files_tokens.deinit();
 | 
			
		||||
        // const cyo_dir_path = cyo_dir.realpath(pathname: []const u8, out_buffer: []u8)
 | 
			
		||||
 | 
			
		||||
        // 2. process files
 | 
			
		||||
 | 
			
		||||
        // 2a. lexical - validate file syntax
 | 
			
		||||
        // 2b. syntactic parsing
 | 
			
		||||
        // 2c. semantic - create objects and scenes
 | 
			
		||||
@ -61,6 +84,7 @@ pub const CyoParser = struct {
 | 
			
		||||
        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 {
 | 
			
		||||
        var cyo_iter = cyo_dir.iterate();
 | 
			
		||||
        while (cyo_iter.next() catch |err| {
 | 
			
		||||
@ -69,11 +93,15 @@ pub const CyoParser = struct {
 | 
			
		||||
        }) |cyo_entry| {
 | 
			
		||||
            switch (cyo_entry.kind) {
 | 
			
		||||
                .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) |_| {
 | 
			
		||||
                        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);
 | 
			
		||||
                    var cyo_file = try std.fs.openFileAbsolute(file_path, .{ .mode = .read_only });
 | 
			
		||||
 | 
			
		||||
@ -84,7 +112,7 @@ pub const CyoParser = struct {
 | 
			
		||||
                    for (0..depth) |_| {
 | 
			
		||||
                        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);
 | 
			
		||||
                    defer allocator.free(dir_path);
 | 
			
		||||
 | 
			
		||||
@ -92,18 +120,220 @@ pub const CyoParser = struct {
 | 
			
		||||
                    try walkDirs(allocator, new_cyo_dir, depth + 1, files_contents);
 | 
			
		||||
                },
 | 
			
		||||
                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" {
 | 
			
		||||
    // 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");
 | 
			
		||||
    defer std.testing.allocator.free(cyo_test_dir_path);
 | 
			
		||||
    var cyo_parser = try CyoParser.init(std.testing.allocator, cyo_test_dir_path);
 | 
			
		||||
    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]
 | 
			
		||||
d:The tape that does it all.
 | 
			
		||||
DuctTape:
 | 
			
		||||
	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.
 | 
			
		||||
 | 
			
		||||
[Warehouse.d]
 | 
			
		||||
	_onExamine:
 | 
			
		||||
		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