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"
edition = "2021"
[lib]
crate-type = ["staticlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[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)]
pub struct Page {
id: String,
@ -9,7 +13,7 @@ pub struct Page {
pub enum Footer {
Ending,
Goto(String),
Choices(Vec<Choice>)
Choices(HashMap<u32, Choice>),
}
#[derive(Debug, PartialEq)]
@ -56,7 +60,7 @@ peg::parser! {
rule footer() -> Footer
= "THE END" __ { Footer::Ending } /
"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
= o:$(['0'..='9']+) ")" _ f:$((!redirect() [_])+) r:redirect() __ {
let (redirect, stat_change, stat_check) = r;
@ -94,10 +98,154 @@ peg::parser! {
}
}
pub fn parse_story(string: &str) -> Result<Vec<Page>,
peg::error::ParseError<
peg::str::LineCol>> {
storybook_parser::story(string)
pub struct Book {
pages: HashMap<String, Page>,
current_page: Option<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)]
@ -128,7 +276,7 @@ THE END
#[test]
fn storybook_parser_test() {
assert_eq!(
parse_story(STORY),
storybook_parser::story(STORY),
Ok(vec![
Page {
id: String::from("START"),
@ -138,15 +286,15 @@ THE END
Page {
id: String::from("DIALOG"),
body: String::from("Dane: 'Wow, that was a really big shit!'"),
footer: Footer::Choices(vec![
Choice {
footer: Footer::Choices(HashMap::from([
(1, Choice {
option: 1,
flavor: String::from("Die"),
redirect: String::from("DIE"),
stat_check: None,
stat_change: None,
},
Choice {
}),
(2, Choice {
option: 2,
flavor: String::from("Don't die"),
redirect: String::from("DONTDIE"),
@ -159,8 +307,8 @@ THE END
value: 100,
rel: '+',
})
}
])
}),
]))
},
Page {
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")));
}
}