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"
|
||||
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"
|
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)]
|
||||
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")));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user