Get the bodies, footers working in the c-api
This commit is contained in:
parent
9fcb145b55
commit
c1eb29275a
2
rust-ffi-demo/.gitignore
vendored
Normal file
2
rust-ffi-demo/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
demo
|
||||||
|
demo.h
|
9
rust-ffi-demo/Makefile
Normal file
9
rust-ffi-demo/Makefile
Normal 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
47
rust-ffi-demo/demo.c
Normal 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;
|
||||||
|
}
|
@ -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"
|
8
src/rust-base/cbindgen.toml
Normal file
8
src/rust-base/cbindgen.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
language = "C"
|
||||||
|
|
||||||
|
[export.rename]
|
||||||
|
|
||||||
|
"FooterType" = "footer"
|
||||||
|
|
||||||
|
[enum]
|
||||||
|
rename_variants = "QualifiedScreamingSnakeCase"
|
@ -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"
|
|
@ -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")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user