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;
|
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(),
|
||||||
|
Loading…
Reference in New Issue
Block a user