Begin location annotation and static analysis

This commit is contained in:
Dane Johnson 2022-02-11 12:54:18 -06:00
parent 54c96ba3ad
commit 64bf1d45dc

View File

@ -2,19 +2,34 @@
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug)]
pub struct Loc {
pub start: usize,
pub end: usize,
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Book { pub struct Book<L> {
pages: HashMap<String, Page>, pages: HashMap<String, Page<L>>,
current_page: Option<String>, current_page: Option<String>,
pub title: String, pub title: String,
pub attribution: String, pub attribution: String,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Page { pub struct Page<L> {
id: String, id: String,
pub body: String, pub body: String,
pub footer: Footer, 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)] #[derive(Debug, PartialEq)]
@ -48,18 +63,19 @@ pub struct StatChange {
peg::parser! { peg::parser! {
grammar storybook_parser() for str { 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)) } = blankline()* t:title() a:attribution() ps:page()+ eof() { Book::from((t, a, ps)) }
rule title() -> String rule title() -> String
= t:$((!attribution() [_])+) &attribution() { String::from(t.trim()) } = t:$((!attribution() [_])+) &attribution() { String::from(t.trim()) }
rule attribution() -> String rule attribution() -> String
= ("by" / "By" ) _ a:$((!__ [_])+) __ blankline()* { String::from(a.trim()) } = ("by" / "By" ) _ a:$((!__ [_])+) __ blankline()* { String::from(a.trim()) }
rule page() -> Page rule page() -> Page<Loc>
= h:header() b:body() f:footer() blankline()* { = start:position!() h:header() b:body() f:footer() end:position!() blankline()* {
Page { Page {
id: h, id: h,
body: b, body: b,
footer: f footer: f,
loc: Loc {start, end},
} }
} }
rule header() -> String rule header() -> String
@ -109,10 +125,13 @@ peg::parser! {
} }
} }
impl Book { impl Book<Loc> {
pub fn new (story: &str) -> Self { pub fn new (story: &str) -> Self {
storybook_parser::story(story).unwrap() storybook_parser::story(story).unwrap()
} }
}
impl<L> Book<L> {
pub fn find (&mut self, id: &str) { pub fn find (&mut self, id: &str) {
if self.pages.contains_key(id) { if self.pages.contains_key(id) {
self.current_page = Some(String::from(id)); self.current_page = Some(String::from(id));
@ -123,7 +142,7 @@ impl Book {
pub fn is_ended(&self) -> bool { pub fn is_ended(&self) -> bool {
self.current_page == None self.current_page == None
} }
pub fn get_current(&self) -> &Page { pub fn get_current(&self) -> &Page<L> {
match &self.current_page { match &self.current_page {
None => panic!("Asked for current page, but current page is None!"), None => panic!("Asked for current page, but current page is None!"),
Some(s) => self.pages.get(s).unwrap(), Some(s) => self.pages.get(s).unwrap(),
@ -160,8 +179,8 @@ impl Book {
} }
} }
impl From<(String, String, Vec<Page>)> for Book { impl<L> From<(String, String, Vec<Page<L>>)> for Book<L> {
fn from((title, attribution, v) : (String, String, Vec<Page>)) -> Book { fn from((title, attribution, v) : (String, String, Vec<Page<L>>)) -> Book<L> {
let it = v.into_iter().map(|p| (p.id.clone(), p)); let it = v.into_iter().map(|p| (p.id.clone(), p));
let pages = HashMap::from_iter(it); let pages = HashMap::from_iter(it);
let mut book = Book { 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 // C API
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
@ -266,8 +306,10 @@ pub mod ffi {
} }
} }
type OpaqueBook = *mut Book<super::Loc>;
#[no_mangle] #[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 { let string = unsafe {
CStr::from_ptr(string) CStr::from_ptr(string)
}; };
@ -278,7 +320,7 @@ pub mod ffi {
} }
#[no_mangle] #[no_mangle]
pub extern fn storybook_free_book(book: *mut Book) { pub extern fn storybook_free_book(book: OpaqueBook) {
unsafe { unsafe {
Box::from_raw(book); 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 { let book = unsafe {
book.as_mut().expect("Invalid Book pointer") book.as_mut().expect("Invalid Book pointer")
}; };
@ -299,7 +341,7 @@ pub mod ffi {
} }
#[no_mangle] #[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 book = get_book_from_ref(book);
let body = book.get_current().body.clone(); let body = book.get_current().body.clone();
let body = CString::new(body).expect("Body cannot be converted to CString"); let body = CString::new(body).expect("Body cannot be converted to CString");
@ -307,7 +349,7 @@ pub mod ffi {
} }
#[no_mangle] #[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); let book = get_book_from_ref(book);
match book.get_current().footer { match book.get_current().footer {
super::Footer::Ending => Footer::Ending, super::Footer::Ending => Footer::Ending,
@ -317,26 +359,26 @@ pub mod ffi {
} }
#[no_mangle] #[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); let book = get_book_from_ref(book);
book.is_ended() book.is_ended()
} }
#[no_mangle] #[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); let book = get_book_from_ref(book);
book.advance_nooption(); book.advance_nooption();
} }
#[no_mangle] #[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); let book = get_book_from_ref(book);
book.advance_option(option); book.advance_option(option);
} }
#[no_mangle] #[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 book = get_book_from_ref(book);
let footer = &book.get_current().footer; let footer = &book.get_current().footer;
if let super::Footer::Choices(choices) = footer { if let super::Footer::Choices(choices) = footer {
@ -401,7 +443,8 @@ THE END
("START".to_string(), Page { ("START".to_string(), Page {
id: String::from("START"), id: String::from("START"),
body: String::from("One day, Dane took a really big shit."), 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 { ("DIALOG".to_string(), Page {
id: String::from("DIALOG"), id: String::from("DIALOG"),
@ -428,12 +471,14 @@ THE END
rel: '+', rel: '+',
}) })
}), }),
])) ])),
loc: (),
}), }),
("DIE".to_string(), Page { ("DIE".to_string(), Page {
id: String::from("DIE"), id: String::from("DIE"),
body: String::from("Then he died.\n\nFuck you, Dane!"), body: String::from("Then he died.\n\nFuck you, Dane!"),
footer: Footer::Ending, footer: Footer::Ending,
loc: (),
}), }),
]), ]),
title: "Dane's Big Day".to_string(), title: "Dane's Big Day".to_string(),