Begin location annotation and static analysis
This commit is contained in:
parent
54c96ba3ad
commit
64bf1d45dc
91
src/lib.rs
91
src/lib.rs
@ -2,19 +2,34 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Loc {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Book {
|
||||
pages: HashMap<String, Page>,
|
||||
pub struct Book<L> {
|
||||
pages: HashMap<String, Page<L>>,
|
||||
current_page: Option<String>,
|
||||
pub title: String,
|
||||
pub attribution: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Page {
|
||||
#[derive(Debug)]
|
||||
pub struct Page<L> {
|
||||
id: String,
|
||||
pub body: String,
|
||||
pub footer: Footer,
|
||||
pub loc: L,
|
||||
}
|
||||
|
||||
impl<L, O> PartialEq<Page<O>> for Page<L> {
|
||||
fn eq(&self, other: &Page<O>) -> bool {
|
||||
self.id == other.id &&
|
||||
self.body == other.body &&
|
||||
self.footer == other.footer
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -48,18 +63,19 @@ pub struct StatChange {
|
||||
|
||||
peg::parser! {
|
||||
grammar storybook_parser() for str {
|
||||
pub rule story() -> Book
|
||||
pub rule story() -> Book<Loc>
|
||||
= blankline()* t:title() a:attribution() ps:page()+ eof() { Book::from((t, a, ps)) }
|
||||
rule title() -> String
|
||||
= t:$((!attribution() [_])+) &attribution() { String::from(t.trim()) }
|
||||
rule attribution() -> String
|
||||
= ("by" / "By" ) _ a:$((!__ [_])+) __ blankline()* { String::from(a.trim()) }
|
||||
rule page() -> Page
|
||||
= h:header() b:body() f:footer() blankline()* {
|
||||
rule page() -> Page<Loc>
|
||||
= start:position!() h:header() b:body() f:footer() end:position!() blankline()* {
|
||||
Page {
|
||||
id: h,
|
||||
body: b,
|
||||
footer: f
|
||||
footer: f,
|
||||
loc: Loc {start, end},
|
||||
}
|
||||
}
|
||||
rule header() -> String
|
||||
@ -109,10 +125,13 @@ peg::parser! {
|
||||
}
|
||||
}
|
||||
|
||||
impl Book {
|
||||
impl Book<Loc> {
|
||||
pub fn new (story: &str) -> Self {
|
||||
storybook_parser::story(story).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<L> Book<L> {
|
||||
pub fn find (&mut self, id: &str) {
|
||||
if self.pages.contains_key(id) {
|
||||
self.current_page = Some(String::from(id));
|
||||
@ -123,7 +142,7 @@ impl Book {
|
||||
pub fn is_ended(&self) -> bool {
|
||||
self.current_page == None
|
||||
}
|
||||
pub fn get_current(&self) -> &Page {
|
||||
pub fn get_current(&self) -> &Page<L> {
|
||||
match &self.current_page {
|
||||
None => panic!("Asked for current page, but current page is None!"),
|
||||
Some(s) => self.pages.get(s).unwrap(),
|
||||
@ -160,8 +179,8 @@ impl Book {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(String, String, Vec<Page>)> for Book {
|
||||
fn from((title, attribution, v) : (String, String, Vec<Page>)) -> Book {
|
||||
impl<L> From<(String, String, Vec<Page<L>>)> for Book<L> {
|
||||
fn from((title, attribution, v) : (String, String, Vec<Page<L>>)) -> Book<L> {
|
||||
let it = v.into_iter().map(|p| (p.id.clone(), p));
|
||||
let pages = HashMap::from_iter(it);
|
||||
let mut book = Book {
|
||||
@ -177,6 +196,27 @@ impl From<(String, String, Vec<Page>)> for Book {
|
||||
}
|
||||
}
|
||||
|
||||
// Semantic errors
|
||||
pub struct StorybookError {
|
||||
pub message: String,
|
||||
pub loc: Loc
|
||||
}
|
||||
|
||||
pub fn check(book: &Book<Loc>) -> Vec<StorybookError> {
|
||||
let mut errors = Vec::new();
|
||||
check_start(book, &mut errors);
|
||||
errors
|
||||
}
|
||||
|
||||
fn check_start(book: &Book<Loc>, errors: &mut Vec<StorybookError>) {
|
||||
if !book.pages.contains_key("START") {
|
||||
errors.push(StorybookError {
|
||||
message: "Story is missing a START page".to_string(),
|
||||
loc: Loc { start: 0, end: 0 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// C API
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -266,8 +306,10 @@ pub mod ffi {
|
||||
}
|
||||
}
|
||||
|
||||
type OpaqueBook = *mut Book<super::Loc>;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn storybook_make_book(string: *const c_char) -> *mut Book {
|
||||
pub extern fn storybook_make_book(string: *const c_char) -> OpaqueBook {
|
||||
let string = unsafe {
|
||||
CStr::from_ptr(string)
|
||||
};
|
||||
@ -278,7 +320,7 @@ pub mod ffi {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn storybook_free_book(book: *mut Book) {
|
||||
pub extern fn storybook_free_book(book: OpaqueBook) {
|
||||
unsafe {
|
||||
Box::from_raw(book);
|
||||
}
|
||||
@ -291,7 +333,7 @@ pub mod ffi {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_book_from_ref<'a> (book: *mut Book) -> &'a mut Book {
|
||||
fn get_book_from_ref<'a> (book: OpaqueBook) -> &'a mut Book<super::Loc> {
|
||||
let book = unsafe {
|
||||
book.as_mut().expect("Invalid Book pointer")
|
||||
};
|
||||
@ -299,7 +341,7 @@ pub mod ffi {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn storybook_get_body(book: *mut Book) -> *mut c_char {
|
||||
pub extern fn storybook_get_body(book: OpaqueBook) -> *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");
|
||||
@ -307,7 +349,7 @@ pub mod ffi {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn storybook_get_footer(book: *mut Book) -> Footer {
|
||||
pub extern fn storybook_get_footer(book: OpaqueBook) -> Footer {
|
||||
let book = get_book_from_ref(book);
|
||||
match book.get_current().footer {
|
||||
super::Footer::Ending => Footer::Ending,
|
||||
@ -317,26 +359,26 @@ pub mod ffi {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn storybook_is_ended(book: *mut Book) -> bool {
|
||||
pub extern fn storybook_is_ended(book: OpaqueBook) -> bool {
|
||||
let book = get_book_from_ref(book);
|
||||
book.is_ended()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn storybook_advance_nooption(book: *mut Book) {
|
||||
pub extern fn storybook_advance_nooption(book: OpaqueBook) {
|
||||
let book = get_book_from_ref(book);
|
||||
book.advance_nooption();
|
||||
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn storybook_advance_option(book: *mut Book, option: usize) {
|
||||
pub extern fn storybook_advance_option(book: OpaqueBook, option: usize) {
|
||||
let book = get_book_from_ref(book);
|
||||
book.advance_option(option);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn storybook_get_choices (book: *mut Book) -> *mut ChoiceList {
|
||||
pub extern fn storybook_get_choices (book: OpaqueBook) -> *mut ChoiceList {
|
||||
let book = get_book_from_ref(book);
|
||||
let footer = &book.get_current().footer;
|
||||
if let super::Footer::Choices(choices) = footer {
|
||||
@ -401,7 +443,8 @@ THE END
|
||||
("START".to_string(), Page {
|
||||
id: String::from("START"),
|
||||
body: String::from("One day, Dane took a really big shit."),
|
||||
footer: Footer::Goto(String::from("DIALOG"))
|
||||
footer: Footer::Goto(String::from("DIALOG")),
|
||||
loc: (),
|
||||
}),
|
||||
("DIALOG".to_string(), Page {
|
||||
id: String::from("DIALOG"),
|
||||
@ -428,12 +471,14 @@ THE END
|
||||
rel: '+',
|
||||
})
|
||||
}),
|
||||
]))
|
||||
])),
|
||||
loc: (),
|
||||
}),
|
||||
("DIE".to_string(), Page {
|
||||
id: String::from("DIE"),
|
||||
body: String::from("Then he died.\n\nFuck you, Dane!"),
|
||||
footer: Footer::Ending,
|
||||
loc: (),
|
||||
}),
|
||||
]),
|
||||
title: "Dane's Big Day".to_string(),
|
||||
|
Loading…
Reference in New Issue
Block a user