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;
#[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(),