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