Add macro evaluator with %include implementation
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use codepage_437::CP437_WINGDINGS;
|
||||||
|
|
||||||
|
// Serialize multi-line content with CR-terminated lines
|
||||||
|
fn encode_multiline(input: &str) -> Result<Vec<u8>> {
|
||||||
|
input
|
||||||
|
.chars()
|
||||||
|
.map(|c| {
|
||||||
|
if c == '\n' {
|
||||||
|
Ok('\r' as u8)
|
||||||
|
} else {
|
||||||
|
CP437_WINGDINGS
|
||||||
|
.encode(c)
|
||||||
|
.ok_or(anyhow!("Couldn't encode char: {}", c))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_multiline(input: &[u8]) -> String {
|
||||||
|
input
|
||||||
|
.iter()
|
||||||
|
.map(|&x| {
|
||||||
|
if x == '\r' as u8 {
|
||||||
|
'\n'
|
||||||
|
} else {
|
||||||
|
CP437_WINGDINGS.decode(x)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use insta::assert_debug_snapshot;
|
||||||
|
|
||||||
|
use crate::encoding::{decode_multiline, encode_multiline};
|
||||||
|
|
||||||
|
fn serialize_code(input: &str) -> Vec<u8> {
|
||||||
|
encode_multiline(input).expect("Error in test")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn roundtrip_multiline() {
|
||||||
|
let bytes: Vec<u8> = (0..=255).collect();
|
||||||
|
assert_eq!(bytes, serialize_code(&decode_multiline(&bytes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn newlines() {
|
||||||
|
assert_debug_snapshot!(serialize_code("ABC\nDEF"), @r###"
|
||||||
|
[
|
||||||
|
65,
|
||||||
|
66,
|
||||||
|
67,
|
||||||
|
13,
|
||||||
|
68,
|
||||||
|
69,
|
||||||
|
70,
|
||||||
|
]
|
||||||
|
"###)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wingdings() {
|
||||||
|
assert_debug_snapshot!(decode_multiline(&(0..32).collect::<Vec<_>>()), @r###""\0☺☻♥♦♣♠•◘○◙♂♀\n♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼""###);
|
||||||
|
assert_debug_snapshot!(decode_multiline(&(112..144).collect::<Vec<_>>()), @r###""pqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅ""###);
|
||||||
|
assert_debug_snapshot!(decode_multiline(&(224..=255).collect::<Vec<_>>()), @r###""αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u{a0}""###);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
mod encoding;
|
||||||
mod preprocess;
|
mod preprocess;
|
||||||
mod world;
|
mod world;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
parse::{parse, Expr},
|
||||||
|
scan::scan,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Context {
|
||||||
|
file_loader: Box<dyn FileLoaderTrait>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait FileLoaderTrait {
|
||||||
|
fn load(&self, path: &Path) -> Result<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FileLoader;
|
||||||
|
struct MockFileLoader {
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileLoaderTrait for FileLoader {
|
||||||
|
fn load(&self, path: &Path) -> Result<String> {
|
||||||
|
fs::read_to_string(path).map_err(|e| anyhow!("Couldn't load file: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileLoaderTrait for MockFileLoader {
|
||||||
|
fn load(&self, _path: &Path) -> Result<String> {
|
||||||
|
Ok(self.content.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub fn eval_program(&self, input: &str) -> Result<String> {
|
||||||
|
let tokens = scan(input).0;
|
||||||
|
let exprs = parse(tokens)?;
|
||||||
|
let mut result: Vec<String> = vec![];
|
||||||
|
for expr in exprs {
|
||||||
|
match expr {
|
||||||
|
Expr::ZztOop(s) => result.push(s),
|
||||||
|
Expr::Macro(name, args) => match name.as_str() {
|
||||||
|
"include" => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
bail!("wrong number of args for %include");
|
||||||
|
}
|
||||||
|
let filename = if let Expr::String(s) = args[0].as_ref() {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
bail!("%include filename must be a string")
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut content = self.file_loader.load(Path::new(filename))?;
|
||||||
|
content = content.replace("\r\n", "\n");
|
||||||
|
if content.ends_with("\n") {
|
||||||
|
content.pop();
|
||||||
|
}
|
||||||
|
result.push(content)
|
||||||
|
}
|
||||||
|
_ => bail!("Unknown macro: {:?}", name),
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
bail!("Unexpected expr: {:?}", expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result.join(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use insta::assert_debug_snapshot;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn make_context(data: String) -> Context {
|
||||||
|
Context {
|
||||||
|
file_loader: Box::new(MockFileLoader { content: data }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn include() {
|
||||||
|
let program = format!("foo\n%include \"bb.txt\"\nquux");
|
||||||
|
let file = "bar\nbaz\n";
|
||||||
|
assert_debug_snapshot!(make_context(file.into()).eval_program(&program), @r###"
|
||||||
|
Ok(
|
||||||
|
"foo\nbar\nbaz\nquux",
|
||||||
|
)
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn include_windows() {
|
||||||
|
let program = format!("%include \"foo.txt\"");
|
||||||
|
let file = "foo\r\nbar";
|
||||||
|
assert_debug_snapshot!(make_context(file.into()).eval_program(&program), @r###"
|
||||||
|
Ok(
|
||||||
|
"foo\nbar",
|
||||||
|
)
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_macro() {
|
||||||
|
make_context("".into())
|
||||||
|
.eval_program("%foo")
|
||||||
|
.expect_err("Expected error: unknown macro");
|
||||||
|
assert_debug_snapshot!(make_context("".into())
|
||||||
|
.eval_program("%foo")
|
||||||
|
.expect_err("Expected error: unknown macro"), @r###""Unknown macro: \"foo\"""###);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
pub mod eval;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
pub mod scan;
|
pub mod scan;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ impl Parser {
|
|||||||
args.push(Box::new(Expr::String(val)));
|
args.push(Box::new(Expr::String(val)));
|
||||||
}
|
}
|
||||||
Token::Newline => {
|
Token::Newline => {
|
||||||
self.advance();
|
//self.advance();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => return Err(anyhow!("Unexpected token {:?}", token)),
|
_ => return Err(anyhow!("Unexpected token {:?}", token)),
|
||||||
@@ -163,7 +163,7 @@ mod tests {
|
|||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
ZztOop(
|
ZztOop(
|
||||||
"Baz.",
|
"\nBaz.",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
"###)
|
"###)
|
||||||
|
|||||||
Reference in New Issue
Block a user