Get the bodies, footers working in the c-api

This commit is contained in:
Dane Johnson 2022-01-11 15:13:54 -06:00
parent 9fcb145b55
commit c1eb29275a
7 changed files with 239 additions and 33 deletions

2
rust-ffi-demo/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
demo
demo.h

9
rust-ffi-demo/Makefile Normal file
View File

@ -0,0 +1,9 @@
LDFLAGS := -L../src/rust-base/target/debug -lstorybook -Wl,--gc-sections -lpthread -ldl
.PHONY: all clean
all: demo
demo: demo.h demo.c
$(CC) -g $^ $(LDFLAGS) -o $@
demo.h:
cbindgen -c ../src/rust-base/cbindgen.toml --crate storybook --lang c -o demo.h ../src/rust-base
clean:
rm -f demo demo.h

47
rust-ffi-demo/demo.c Normal file
View File

@ -0,0 +1,47 @@
#include <stdio.h>
#include <stdint.h>
#include "demo.h"
const char* story = "START\n"
"One day, Dane took a really big shit!\n"
"GOTO DIALOG\n"
"DIALOG\n"
"\"Wow, that was a really big shit!\"\n"
"1) Die [DIE]\n"
"2) Don't die <Strength 100+> [DONT_DIE] (-1 Honor)\n"
"DONT_DIE\n"
"But he died anyways.\n"
"GOTO DIE\n"
"DIE\n"
"Then he died. Fuck you, Dane!\n"
"THE END\n";
int main() {
Book *book = storybook_make_book(story);
while(!storybook_is_ended(book)) {
char* s = storybook_get_body(book);
printf("%s\n", s);
storybook_free_string(s);
enum footer footer = storybook_get_footer(book);
switch (footer) {
case (FOOTER_ENDING):
printf("The End\n");
storybook_advance_nooption(book);
break;
case (FOOTER_GOTO):
storybook_advance_nooption(book);
break;
case (FOOTER_CHOICES):
uint32_t option;
printf("Make a choice: ");
scanf("%d", &option);
storybook_advance_option(book, option);
}
}
storybook_free_book(book);
return 0;
}

View File

@ -3,8 +3,12 @@ name = "storybook"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[lib]
crate-type = ["staticlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
peg = "0.8.0" peg = "0.8.0"

View File

@ -0,0 +1,8 @@
language = "C"
[export.rename]
"FooterType" = "footer"
[enum]
rename_variants = "QualifiedScreamingSnakeCase"

View File

@ -1,19 +0,0 @@
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>
struct Page;
template<typename T = void, typename E = void>
struct Result;
template<typename T = void>
struct Vec;
extern "C" {
Result<Vec<Page>, ParseError<LineCol>> parse_story(const str *string);
} // extern "C"

View File

@ -1,3 +1,7 @@
// Parsing
use std::collections::HashMap;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Page { pub struct Page {
id: String, id: String,
@ -9,7 +13,7 @@ pub struct Page {
pub enum Footer { pub enum Footer {
Ending, Ending,
Goto(String), Goto(String),
Choices(Vec<Choice>) Choices(HashMap<u32, Choice>),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -56,7 +60,7 @@ peg::parser! {
rule footer() -> Footer rule footer() -> Footer
= "THE END" __ { Footer::Ending } / = "THE END" __ { Footer::Ending } /
"GOTO" _ i:id() __ { Footer::Goto(i) } / "GOTO" _ i:id() __ { Footer::Goto(i) } /
c:choice()+ { Footer::Choices(c) } c:choice()+ { Footer::Choices(c.into_iter().map(|x| (x.option, x)).collect()) }
rule choice() -> Choice rule choice() -> Choice
= o:$(['0'..='9']+) ")" _ f:$((!redirect() [_])+) r:redirect() __ { = o:$(['0'..='9']+) ")" _ f:$((!redirect() [_])+) r:redirect() __ {
let (redirect, stat_change, stat_check) = r; let (redirect, stat_change, stat_check) = r;
@ -94,10 +98,154 @@ peg::parser! {
} }
} }
pub fn parse_story(string: &str) -> Result<Vec<Page>, pub struct Book {
peg::error::ParseError< pages: HashMap<String, Page>,
peg::str::LineCol>> { current_page: Option<String>
storybook_parser::story(string) }
impl Book {
pub fn find (&mut self, id: &str) {
if self.pages.contains_key(id) {
self.current_page = Some(String::from(id));
} else {
self.current_page = None
}
}
pub fn is_ended(&self) -> bool {
self.current_page == None
}
pub fn get_current(&self) -> &Page {
match &self.current_page {
None => panic!("Asked for current page, but current page is None!"),
Some(s) => self.pages.get(s).unwrap(),
}
}
pub fn advance_nooption(&mut self) {
let footer = &self.get_current().footer;
match footer {
Footer::Ending => {self.current_page = None},
Footer::Goto(s) => {
let s = s.clone();
self.find(&s);
},
Footer::Choices(_) => panic!("No option for advance on choices page!"),
}
}
pub fn advance_option(&mut self, option: u32) {
let footer = &self.get_current().footer;
match footer {
Footer::Ending => panic!("Option provided for advance on ending page!"),
Footer::Goto(_) => panic!("Option provided for advance on goto page!"),
Footer::Choices(choices) => {
// TODO ban choices with failing stat checks
// TODO update stats based on stat changes
match choices.get(&option) {
Some(choice) => {
let s = choice.redirect.clone();
self.find(&s);
}
None => panic!("Option not a valid choice for page!"),
}
}
}
}
}
impl From<Vec<Page>> for Book {
fn from(v: Vec<Page>) -> Book {
let it = v.into_iter().map(|p| (p.id.clone(), p));
let pages = HashMap::from_iter(it);
let mut book = Book {
pages,
current_page: None,
};
book.find("START");
book
}
}
// C API
use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::c_char;
#[no_mangle]
pub extern fn storybook_make_book(string: *const c_char) -> *mut Book {
let string = unsafe {
CStr::from_ptr(string)
};
let string = string.to_str().expect("Error, input string not valid UTF-8");
let pages = storybook_parser::story(string).expect("Error, input string could not be parsed");
let book = Book::from(pages);
let book = Box::from(book);
Box::into_raw(book)
}
#[no_mangle]
pub extern fn storybook_free_book(book: *mut Book) {
unsafe {
Box::from_raw(book);
}
}
#[no_mangle]
pub extern fn storybook_free_string(s: *mut c_char) {
unsafe {
drop(CString::from_raw(s));
}
}
fn get_book_from_ref<'a> (book: *mut Book) -> &'a mut Book {
let book = unsafe {
book.as_mut().expect("Invalid Book pointer")
};
book
}
#[no_mangle]
pub extern fn storybook_get_body(book: *mut Book) -> *mut c_char {
let book = get_book_from_ref(book);
let body = book.get_current().body.clone();
let body = CString::new(body).expect("Body cannot be converted to CString");
body.into_raw()
}
#[repr(C)]
pub enum FooterType {
Ending,
Goto,
Choices,
}
#[no_mangle]
pub extern fn storybook_get_footer(book: *mut Book) -> FooterType {
let book = get_book_from_ref(book);
match book.get_current().footer {
Footer::Ending => FooterType::Ending,
Footer::Goto(_) => FooterType::Goto,
Footer::Choices(_) => FooterType::Choices,
}
}
#[no_mangle]
pub extern fn storybook_is_ended(book: *mut Book) -> bool {
let book = get_book_from_ref(book);
book.is_ended()
}
#[no_mangle]
pub extern fn storybook_advance_nooption(book: *mut Book) {
let book = get_book_from_ref(book);
book.advance_nooption();
}
#[no_mangle]
pub extern fn storybook_advance_option(book: *mut Book, option: u32) {
let book = get_book_from_ref(book);
book.advance_option(option);
} }
#[cfg(test)] #[cfg(test)]
@ -128,7 +276,7 @@ THE END
#[test] #[test]
fn storybook_parser_test() { fn storybook_parser_test() {
assert_eq!( assert_eq!(
parse_story(STORY), storybook_parser::story(STORY),
Ok(vec![ Ok(vec![
Page { Page {
id: String::from("START"), id: String::from("START"),
@ -138,15 +286,15 @@ THE END
Page { Page {
id: String::from("DIALOG"), id: String::from("DIALOG"),
body: String::from("Dane: 'Wow, that was a really big shit!'"), body: String::from("Dane: 'Wow, that was a really big shit!'"),
footer: Footer::Choices(vec![ footer: Footer::Choices(HashMap::from([
Choice { (1, Choice {
option: 1, option: 1,
flavor: String::from("Die"), flavor: String::from("Die"),
redirect: String::from("DIE"), redirect: String::from("DIE"),
stat_check: None, stat_check: None,
stat_change: None, stat_change: None,
}, }),
Choice { (2, Choice {
option: 2, option: 2,
flavor: String::from("Don't die"), flavor: String::from("Don't die"),
redirect: String::from("DONTDIE"), redirect: String::from("DONTDIE"),
@ -159,8 +307,8 @@ THE END
value: 100, value: 100,
rel: '+', rel: '+',
}) })
} }),
]) ]))
}, },
Page { Page {
id: String::from("DIE"), id: String::from("DIE"),
@ -170,4 +318,11 @@ THE END
]) ])
); );
} }
#[test]
fn test_book_from_pages() {
let pages = storybook_parser::story(STORY).unwrap();
let book = Book::from(pages);
assert_eq!(book.current_page, Some(String::from("START")));
}
} }