From 9fcb145b55263511298435efc0ad779027a76713 Mon Sep 17 00:00:00 2001 From: Dane Johnson Date: Fri, 7 Jan 2022 13:48:36 -0600 Subject: [PATCH] Get the parser working in Rust --- src/rust-base/.gitignore | 1 + src/rust-base/Cargo.lock | 61 ++++++++++++++ src/rust-base/Cargo.toml | 10 +++ src/rust-base/my_header.h | 19 +++++ src/rust-base/src/lib.rs | 173 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 264 insertions(+) create mode 100644 src/rust-base/.gitignore create mode 100644 src/rust-base/Cargo.lock create mode 100644 src/rust-base/Cargo.toml create mode 100644 src/rust-base/my_header.h create mode 100644 src/rust-base/src/lib.rs diff --git a/src/rust-base/.gitignore b/src/rust-base/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/src/rust-base/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/src/rust-base/Cargo.lock b/src/rust-base/Cargo.lock new file mode 100644 index 0000000..5e6b4a2 --- /dev/null +++ b/src/rust-base/Cargo.lock @@ -0,0 +1,61 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "peg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af728fe826811af3b38c37e93de6d104485953ea373d656eebae53d6987fcd2c" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4536be147b770b824895cbad934fccce8e49f14b4c4946eaa46a6e4a12fcdc16" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "storybook" +version = "0.1.0" +dependencies = [ + "peg", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/src/rust-base/Cargo.toml b/src/rust-base/Cargo.toml new file mode 100644 index 0000000..8e4f7f2 --- /dev/null +++ b/src/rust-base/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "storybook" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +peg = "0.8.0" diff --git a/src/rust-base/my_header.h b/src/rust-base/my_header.h new file mode 100644 index 0000000..27e7e2a --- /dev/null +++ b/src/rust-base/my_header.h @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include + +struct Page; + +template +struct Result; + +template +struct Vec; + +extern "C" { + +Result, ParseError> parse_story(const str *string); + +} // extern "C" diff --git a/src/rust-base/src/lib.rs b/src/rust-base/src/lib.rs new file mode 100644 index 0000000..1a57fcc --- /dev/null +++ b/src/rust-base/src/lib.rs @@ -0,0 +1,173 @@ +#[derive(Debug, PartialEq)] +pub struct Page { + id: String, + body: String, + footer: Footer, +} + +#[derive(Debug, PartialEq)] +pub enum Footer { + Ending, + Goto(String), + Choices(Vec) +} + +#[derive(Debug, PartialEq)] +pub struct Choice { + option: u32, + flavor: String, + redirect: String, + stat_check: Option, + stat_change: Option +} + +#[derive(Debug, PartialEq)] +pub struct StatCheck { + stat: String, + value: i32, + rel: char, +} + +#[derive(Debug, PartialEq)] +pub struct StatChange { + stat: String, + addend: i32, +} + + +peg::parser! { + grammar storybook_parser() for str { + pub rule story() -> Vec + = blankline()* ps:page()+ eof() { ps } + rule page() -> Page + = h:header() b:body() f:footer() blankline()* { + Page { + id: h, + body: b, + footer: f + } + } + rule header() -> String + = i:id() __ blankline()* { i } + rule id() -> String + = i:$(['A'..='Z'|'_']+) _ { String::from(i) } + rule body() -> String + = b:$((!footer() [_])+) { String::from(b.trim()) } + rule footer() -> Footer + = "THE END" __ { Footer::Ending } / + "GOTO" _ i:id() __ { Footer::Goto(i) } / + c:choice()+ { Footer::Choices(c) } + rule choice() -> Choice + = o:$(['0'..='9']+) ")" _ f:$((!redirect() [_])+) r:redirect() __ { + let (redirect, stat_change, stat_check) = r; + Choice { + option: o.parse().unwrap(), + flavor: String::from(f.trim()), + redirect, + stat_change, + stat_check + } + } + rule redirect() -> (String, Option, Option) + = ck:stat_check()? "[" i:id() "]" _ cg:stat_change()? { (i, cg, ck) } + rule stat_change() -> StatChange + = "(" a:$(['+'|'-'] ['0'..='9']+) _ n:stat_name() ")" _ { StatChange { + addend: a.parse().unwrap(), + stat: n + }} + rule stat_check() -> StatCheck + = "<" n:stat_name() _ v:$(['0'..='9']+) r:['+'|'-'] ">" _ { StatCheck { + stat: n, + value: v.parse().unwrap(), + rel: r + }} + rule stat_name() -> String + = n:$(['a'..='z'|'A'..='Z']+) { String::from(n) } + rule blankline() + = _ __ + rule _ + = [' '|'\t']* + rule __ + = "\r\n" / "\r" / "\n" + rule eof() + = ![_] + } +} + +pub fn parse_story(string: &str) -> Result, + peg::error::ParseError< + peg::str::LineCol>> { + storybook_parser::story(string) +} + +#[cfg(test)] +mod tests { + use super::*; + const STORY: &str = r" +START + +One day, Dane took a really big shit. + +GOTO DIALOG + +DIALOG + +Dane: 'Wow, that was a really big shit!' + +1) Die [DIE] +2) Don't die [DONTDIE] (-1 Honor) + +DIE + +Then he died. + +Fuck you, Dane! + +THE END +"; + #[test] + fn storybook_parser_test() { + assert_eq!( + parse_story(STORY), + Ok(vec![ + Page { + id: String::from("START"), + body: String::from("One day, Dane took a really big shit."), + footer: Footer::Goto(String::from("DIALOG")) + }, + Page { + id: String::from("DIALOG"), + body: String::from("Dane: 'Wow, that was a really big shit!'"), + footer: Footer::Choices(vec![ + Choice { + option: 1, + flavor: String::from("Die"), + redirect: String::from("DIE"), + stat_check: None, + stat_change: None, + }, + Choice { + option: 2, + flavor: String::from("Don't die"), + redirect: String::from("DONTDIE"), + stat_change: Some(StatChange { + stat: String::from("Honor"), + addend: -1, + }), + stat_check: Some(StatCheck { + stat: String::from("Strength"), + value: 100, + rel: '+', + }) + } + ]) + }, + Page { + id: String::from("DIE"), + body: String::from("Then he died.\n\nFuck you, Dane!"), + footer: Footer::Ending, + } + ]) + ); + } +}