From 64bf1d45dc383412bc1702d0c5e600e5965f0c04 Mon Sep 17 00:00:00 2001 From: Dane Johnson Date: Fri, 11 Feb 2022 12:54:18 -0600 Subject: [PATCH] Begin location annotation and static analysis --- src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e05232d..34618d6 100644 --- a/src/lib.rs +++ b/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, +pub struct Book { + pages: HashMap>, current_page: Option, pub title: String, pub attribution: String, } -#[derive(Debug, PartialEq)] -pub struct Page { +#[derive(Debug)] +pub struct Page { id: String, pub body: String, pub footer: Footer, + pub loc: L, +} + +impl PartialEq> for Page { + fn eq(&self, other: &Page) -> 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 = 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 + = 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 { pub fn new (story: &str) -> Self { storybook_parser::story(story).unwrap() } +} + +impl Book { 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 { 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)> for Book { - fn from((title, attribution, v) : (String, String, Vec)) -> Book { +impl From<(String, String, Vec>)> for Book { + fn from((title, attribution, v) : (String, String, Vec>)) -> Book { 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)> for Book { } } +// Semantic errors +pub struct StorybookError { + pub message: String, + pub loc: Loc +} + +pub fn check(book: &Book) -> Vec { + let mut errors = Vec::new(); + check_start(book, &mut errors); + errors +} + +fn check_start(book: &Book, errors: &mut Vec) { + 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; + #[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 { 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(),