Add graph
This commit is contained in:
parent
12a4e956a9
commit
b8f4671657
14 changed files with 1000 additions and 754 deletions
|
@ -1,263 +1,337 @@
|
||||||
use std::{io::{Read, Write}, ops::Range, process::{Command, Stdio}, rc::Rc, sync::Once};
|
use std::collections::HashMap;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::process::Stdio;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
use ariadne::{Fmt, Label, Report, ReportKind};
|
use crate::parser::util::Property;
|
||||||
use crypto::{digest::Digest, sha2::Sha512};
|
use crate::parser::util::PropertyMapError;
|
||||||
use mlua::{Function, Lua};
|
use crate::parser::util::PropertyParser;
|
||||||
use regex::{Captures, Regex};
|
use ariadne::Fmt;
|
||||||
|
use ariadne::Label;
|
||||||
|
use ariadne::Report;
|
||||||
|
use ariadne::ReportKind;
|
||||||
|
use crypto::digest::Digest;
|
||||||
|
use crypto::sha2::Sha512;
|
||||||
|
use graphviz_rust::cmd::Format;
|
||||||
|
use graphviz_rust::cmd::Layout;
|
||||||
|
use graphviz_rust::exec;
|
||||||
|
use graphviz_rust::exec_dot;
|
||||||
|
use graphviz_rust::parse;
|
||||||
|
use graphviz_rust::printer::PrinterContext;
|
||||||
|
use mlua::Function;
|
||||||
|
use mlua::Lua;
|
||||||
|
use regex::Captures;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::{cache::cache::{Cached, CachedError}, compiler::compiler::{Compiler, Target}, document::{document::Document, element::{ElemKind, Element}}, parser::{parser::Parser, rule::RegexRule, source::{Source, Token}, util}};
|
use crate::cache::cache::Cached;
|
||||||
|
use crate::cache::cache::CachedError;
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
use crate::compiler::compiler::Compiler;
|
||||||
enum TexKind
|
use crate::compiler::compiler::Target;
|
||||||
{
|
use crate::document::document::Document;
|
||||||
Block,
|
use crate::document::element::ElemKind;
|
||||||
Inline,
|
use crate::document::element::Element;
|
||||||
}
|
use crate::parser::parser::Parser;
|
||||||
|
use crate::parser::rule::RegexRule;
|
||||||
impl From<&TexKind> for ElemKind
|
use crate::parser::source::Source;
|
||||||
{
|
use crate::parser::source::Token;
|
||||||
fn from(value: &TexKind) -> Self {
|
use crate::parser::util;
|
||||||
match value {
|
|
||||||
TexKind::Inline => ElemKind::Inline,
|
|
||||||
_ => ElemKind::Block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Tex
|
struct Graphviz {
|
||||||
{
|
pub location: Token,
|
||||||
location: Token,
|
pub dot: String,
|
||||||
block: TexKind,
|
pub layout: Layout,
|
||||||
env: String,
|
pub caption: Option<String>,
|
||||||
tex: String,
|
|
||||||
caption: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tex {
|
fn layout_from_str(value: &str) -> Result<Layout, String> {
|
||||||
fn new(location: Token, block: TexKind, env: String, tex: String, caption: Option<String>) -> Self {
|
match value {
|
||||||
Self { location, block, env, tex, caption }
|
"dot" => Ok(Layout::Dot),
|
||||||
}
|
"neato" => Ok(Layout::Neato),
|
||||||
|
"fdp" => Ok(Layout::Fdp),
|
||||||
fn format_latex(fontsize: &String, preamble: &String, tex: &String) -> FormattedTex
|
"sfdp" => Ok(Layout::Sfdp),
|
||||||
{
|
"circo" => Ok(Layout::Circo),
|
||||||
FormattedTex(format!(r"\documentclass[{}pt,preview]{{standalone}}
|
"twopi" => Ok(Layout::Twopi),
|
||||||
{}
|
"osage" => Ok(Layout::Asage), // typo in graphviz_rust ?
|
||||||
\begin{{document}}
|
"patchwork" => Ok(Layout::Patchwork),
|
||||||
\begin{{preview}}
|
_ => Err(format!("Unknown layout: {value}")),
|
||||||
{}
|
|
||||||
\end{{preview}}
|
|
||||||
\end{{document}}",
|
|
||||||
fontsize, preamble, tex))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FormattedTex(String);
|
impl Graphviz {
|
||||||
|
/// Renders dot to svg
|
||||||
|
fn dot_to_svg(&self) -> Result<String, String> {
|
||||||
|
print!("Rendering Graphviz `{}`... ", self.dot);
|
||||||
|
|
||||||
impl FormattedTex
|
let svg = match exec_dot(
|
||||||
{
|
self.dot.clone(),
|
||||||
/// Renders latex to svg
|
vec![self.layout.into(), Format::Svg.into()],
|
||||||
fn latex_to_svg(&self, exec: &String, fontsize: &String) -> Result<String, String>
|
) {
|
||||||
{
|
Ok(svg) => {
|
||||||
print!("Rendering LaTex `{}`... ", self.0);
|
let out = String::from_utf8_lossy(svg.as_slice());
|
||||||
let process = match Command::new(exec)
|
let split_at = out.find("<!-- Generated").unwrap(); // Remove svg header
|
||||||
.arg("--fontsize").arg(fontsize)
|
|
||||||
.stdout(Stdio::piped())
|
out.split_at(split_at).1.to_string()
|
||||||
.stdin(Stdio::piped())
|
}
|
||||||
.spawn()
|
Err(e) => return Err(format!("Unable to execute dot: {e}")),
|
||||||
{
|
|
||||||
Err(e) => return Err(format!("Could not spawn `{exec}`: {}", e)),
|
|
||||||
Ok(process) => process
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = process.stdin.unwrap().write_all(self.0.as_bytes())
|
|
||||||
{
|
|
||||||
panic!("Unable to write to `latex2svg`'s stdin: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = String::new();
|
|
||||||
match process.stdout.unwrap().read_to_string(&mut result)
|
|
||||||
{
|
|
||||||
Err(e) => panic!("Unable to read `latex2svg` stdout: {}", e),
|
|
||||||
Ok(_) => {}
|
|
||||||
}
|
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
|
|
||||||
Ok(result)
|
Ok(svg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cached for FormattedTex
|
impl Cached for Graphviz {
|
||||||
{
|
|
||||||
type Key = String;
|
type Key = String;
|
||||||
type Value = String;
|
type Value = String;
|
||||||
|
|
||||||
fn sql_table() -> &'static str {
|
fn sql_table() -> &'static str {
|
||||||
"CREATE TABLE IF NOT EXISTS cached_tex (
|
"CREATE TABLE IF NOT EXISTS cached_dot (
|
||||||
digest TEXT PRIMARY KEY,
|
digest TEXT PRIMARY KEY,
|
||||||
svg BLOB NOT NULL);"
|
svg BLOB NOT NULL);"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sql_get_query() -> &'static str {
|
fn sql_get_query() -> &'static str { "SELECT svg FROM cached_dot WHERE digest = (?1)" }
|
||||||
"SELECT svg FROM cached_tex WHERE digest = (?1)"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sql_insert_query() -> &'static str {
|
fn sql_insert_query() -> &'static str { "INSERT INTO cached_dot (digest, svg) VALUES (?1, ?2)" }
|
||||||
"INSERT INTO cached_tex (digest, svg) VALUES (?1, ?2)"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key(&self) -> <Self as Cached>::Key {
|
fn key(&self) -> <Self as Cached>::Key {
|
||||||
let mut hasher = Sha512::new();
|
let mut hasher = Sha512::new();
|
||||||
hasher.input(self.0.as_bytes());
|
hasher.input((self.layout as usize).to_be_bytes().as_slice());
|
||||||
|
hasher.input(self.dot.as_bytes());
|
||||||
|
|
||||||
hasher.result_str()
|
hasher.result_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Tex {
|
impl Element for Graphviz {
|
||||||
fn location(&self) -> &Token { &self.location }
|
fn location(&self) -> &Token { &self.location }
|
||||||
|
|
||||||
fn kind(&self) -> ElemKind { (&self.block).into() }
|
fn kind(&self) -> ElemKind { ElemKind::Block }
|
||||||
|
|
||||||
fn element_name(&self) -> &'static str { "LaTeX" }
|
fn element_name(&self) -> &'static str { "Graphviz" }
|
||||||
|
|
||||||
fn to_string(&self) -> String { format!("{self:#?}") }
|
fn to_string(&self) -> String { format!("{self:#?}") }
|
||||||
|
|
||||||
fn compile(&self, compiler: &Compiler, document: &dyn Document)
|
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> {
|
||||||
-> Result<String, String> {
|
|
||||||
|
|
||||||
match compiler.target() {
|
match compiler.target() {
|
||||||
Target::HTML => {
|
Target::HTML => {
|
||||||
static CACHE_INIT : Once = Once::new();
|
static CACHE_INIT: Once = Once::new();
|
||||||
CACHE_INIT.call_once(|| if let Some(mut con) = compiler.cache() {
|
CACHE_INIT.call_once(|| {
|
||||||
if let Err(e) = FormattedTex::init(&mut con)
|
if let Some(mut con) = compiler.cache() {
|
||||||
{
|
if let Err(e) = Graphviz::init(&mut con) {
|
||||||
eprintln!("Unable to create cache table: {e}");
|
eprintln!("Unable to create cache table: {e}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let exec = document.get_variable(format!("tex.{}.exec", self.env).as_str())
|
if let Some(mut con) = compiler.cache() {
|
||||||
.map_or("latex2svg".to_string(), |var| var.to_string());
|
match self.cached(&mut con, |s| s.dot_to_svg()) {
|
||||||
// FIXME: Because fontsize is passed as an arg, verify that it cannot be used to execute python/shell code
|
|
||||||
let fontsize = document.get_variable(format!("tex.{}.fontsize", self.env).as_str())
|
|
||||||
.map_or("12".to_string(), |var| var.to_string());
|
|
||||||
let preamble = document.get_variable(format!("tex.{}.preamble", self.env).as_str())
|
|
||||||
.map_or("".to_string(), |var| var.to_string());
|
|
||||||
let prepend = if self.block == TexKind::Inline { "".to_string() }
|
|
||||||
else
|
|
||||||
{
|
|
||||||
document.get_variable(format!("tex.{}.block_prepend", self.env).as_str())
|
|
||||||
.map_or("".to_string(), |var| var.to_string()+"\n")
|
|
||||||
};
|
|
||||||
|
|
||||||
let latex = match self.block
|
|
||||||
{
|
|
||||||
TexKind::Inline => Tex::format_latex(
|
|
||||||
&fontsize,
|
|
||||||
&preamble,
|
|
||||||
&format!("${{{}}}$", self.tex)),
|
|
||||||
_ => Tex::format_latex(
|
|
||||||
&fontsize,
|
|
||||||
&preamble,
|
|
||||||
&format!("{prepend}{}", self.tex))
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(mut con) = compiler.cache()
|
|
||||||
{
|
|
||||||
match latex.cached(&mut con, |s| s.latex_to_svg(&exec, &fontsize))
|
|
||||||
{
|
|
||||||
Ok(s) => Ok(s),
|
Ok(s) => Ok(s),
|
||||||
Err(e) => match e
|
Err(e) => match e {
|
||||||
{
|
CachedError::SqlErr(e) => {
|
||||||
CachedError::SqlErr(e) => Err(format!("Querying the cache failed: {e}")),
|
Err(format!("Querying the cache failed: {e}"))
|
||||||
CachedError::GenErr(e) => Err(e)
|
}
|
||||||
|
CachedError::GenErr(e) => Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.dot_to_svg() {
|
||||||
|
Ok(svg) => Ok(svg),
|
||||||
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
_ => todo!("Unimplemented"),
|
||||||
{
|
|
||||||
latex.latex_to_svg(&exec, &fontsize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => todo!("Unimplemented")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TexRule {
|
pub struct GraphRule {
|
||||||
re: [Regex; 2],
|
re: [Regex; 1],
|
||||||
|
properties: PropertyParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TexRule {
|
impl GraphRule {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let mut props = HashMap::new();
|
||||||
|
props.insert(
|
||||||
|
"layout".to_string(),
|
||||||
|
Property::new(
|
||||||
|
true,
|
||||||
|
"Graphviz layout engine see <https://graphviz.org/docs/layouts/>".to_string(),
|
||||||
|
Some("dot".to_string()),
|
||||||
|
),
|
||||||
|
);
|
||||||
Self {
|
Self {
|
||||||
re: [
|
re: [Regex::new(
|
||||||
Regex::new(r"\$\|(?:\[(.*)\])?(?:((?:\\.|[^\\\\])*?)\|\$)?").unwrap(),
|
r"\[graph\](?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\[/graph\])?",
|
||||||
Regex::new(r"\$(?:\[(.*)\])?(?:((?:\\.|[^\\\\])*?)\$)?").unwrap(),
|
)
|
||||||
],
|
.unwrap()],
|
||||||
|
properties: PropertyParser::new(props),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegexRule for TexRule
|
impl RegexRule for GraphRule {
|
||||||
{
|
fn name(&self) -> &'static str { "Graph" }
|
||||||
fn name(&self) -> &'static str { "Tex" }
|
|
||||||
|
|
||||||
fn regexes(&self) -> &[regex::Regex] { &self.re }
|
fn regexes(&self) -> &[regex::Regex] { &self.re }
|
||||||
|
|
||||||
fn on_regex_match(&self, index: usize, parser: &dyn Parser, document: &dyn Document, token: Token, matches: Captures)
|
fn on_regex_match(
|
||||||
-> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
&self,
|
||||||
|
_: usize,
|
||||||
|
parser: &dyn Parser,
|
||||||
|
document: &dyn Document,
|
||||||
|
token: Token,
|
||||||
|
matches: Captures,
|
||||||
|
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
||||||
let mut reports = vec![];
|
let mut reports = vec![];
|
||||||
|
|
||||||
let tex_env = matches.get(1)
|
let graph_content = match matches.get(2) {
|
||||||
.and_then(|env| Some(env.as_str().trim_start().trim_end()))
|
// Unterminated `[graph]`
|
||||||
.and_then(|env| (!env.is_empty()).then_some(env))
|
|
||||||
.unwrap_or("main");
|
|
||||||
|
|
||||||
let tex_content = match matches.get(2)
|
|
||||||
{
|
|
||||||
// Unterminated `$`
|
|
||||||
None => {
|
None => {
|
||||||
reports.push(
|
reports.push(
|
||||||
Report::build(ReportKind::Error, token.source(), token.start())
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
.with_message("Unterminated Tex Code")
|
.with_message("Unterminated Graph Code")
|
||||||
.with_label(
|
.with_label(
|
||||||
Label::new((token.source().clone(), token.range.clone()))
|
Label::new((token.source().clone(), token.range.clone()))
|
||||||
.with_message(format!("Missing terminating `{}` after first `{}`",
|
.with_message(format!(
|
||||||
["|$", "$"][index].fg(parser.colors().info),
|
"Missing terminating `{}` after first `{}`",
|
||||||
["$|", "$"][index].fg(parser.colors().info)))
|
"[/graph]".fg(parser.colors().info),
|
||||||
.with_color(parser.colors().error))
|
"[graph]".fg(parser.colors().info)
|
||||||
.finish());
|
))
|
||||||
|
.with_color(parser.colors().error),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
return reports;
|
return reports;
|
||||||
}
|
}
|
||||||
Some(content) => {
|
Some(content) => {
|
||||||
let processed = util::process_escaped('\\', ["|$", "$"][index],
|
let processed = util::process_escaped(
|
||||||
content.as_str().trim_start().trim_end());
|
'\\',
|
||||||
|
"[/graph]",
|
||||||
|
content.as_str().trim_start().trim_end(),
|
||||||
|
);
|
||||||
|
|
||||||
if processed.is_empty()
|
if processed.is_empty() {
|
||||||
{
|
|
||||||
reports.push(
|
reports.push(
|
||||||
Report::build(ReportKind::Warning, token.source(), content.start())
|
Report::build(ReportKind::Error, token.source(), content.start())
|
||||||
.with_message("Empty Tex Code")
|
.with_message("Empty Graph Code")
|
||||||
.with_label(
|
.with_label(
|
||||||
Label::new((token.source().clone(), content.range()))
|
Label::new((token.source().clone(), content.range()))
|
||||||
.with_message("Tex code is empty")
|
.with_message("Graph code is empty")
|
||||||
.with_color(parser.colors().warning))
|
.with_color(parser.colors().error),
|
||||||
.finish());
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
return reports;
|
||||||
}
|
}
|
||||||
processed
|
processed
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
let properties = match matches.get(1) {
|
||||||
|
None => match self.properties.default() {
|
||||||
|
Ok(properties) => properties,
|
||||||
|
Err(e) => {
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
|
.with_message("Invalid Graph")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), token.range.clone()))
|
||||||
|
.with_message(format!("Graph is missing property: {e}"))
|
||||||
|
.with_color(parser.colors().error),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(props) => {
|
||||||
|
let processed =
|
||||||
|
util::process_escaped('\\', "]", props.as_str().trim_start().trim_end());
|
||||||
|
match self.properties.parse(processed.as_str()) {
|
||||||
|
Err(e) => {
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Error, token.source(), props.start())
|
||||||
|
.with_message("Invalid Graph Properties")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), props.range()))
|
||||||
|
.with_message(e)
|
||||||
|
.with_color(parser.colors().error),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
Ok(properties) => properties,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Property "layout"
|
||||||
|
let graph_layout = match properties.get("layout", |prop, value| {
|
||||||
|
layout_from_str(value.as_str()).map_err(|e| (prop, e))
|
||||||
|
}) {
|
||||||
|
Ok((_prop, kind)) => kind,
|
||||||
|
Err(e) => match e {
|
||||||
|
PropertyMapError::ParseError((prop, err)) => {
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
|
.with_message("Invalid Graph Property")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), token.range.clone()))
|
||||||
|
.with_message(format!(
|
||||||
|
"Property `layout: {}` cannot be converted: {}",
|
||||||
|
prop.fg(parser.colors().info),
|
||||||
|
err.fg(parser.colors().error)
|
||||||
|
))
|
||||||
|
.with_color(parser.colors().warning),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
PropertyMapError::NotFoundError(err) => {
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
|
.with_message("Invalid Graph Property")
|
||||||
|
.with_label(
|
||||||
|
Label::new((
|
||||||
|
token.source().clone(),
|
||||||
|
token.start() + 1..token.end(),
|
||||||
|
))
|
||||||
|
.with_message(format!(
|
||||||
|
"Property `{}` is missing",
|
||||||
|
err.fg(parser.colors().info)
|
||||||
|
))
|
||||||
|
.with_color(parser.colors().warning),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Caption
|
// TODO: Caption
|
||||||
|
|
||||||
parser.push(document, Box::new(Tex::new(
|
parser.push(
|
||||||
token,
|
document,
|
||||||
if index == 1 { TexKind::Inline } else { TexKind::Block },
|
Box::new(Graphviz {
|
||||||
tex_env.to_string(),
|
location: token,
|
||||||
tex_content,
|
dot: graph_content,
|
||||||
None,
|
layout: graph_layout,
|
||||||
)));
|
caption: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
reports
|
reports
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,4 +11,5 @@ pub mod section;
|
||||||
pub mod link;
|
pub mod link;
|
||||||
pub mod code;
|
pub mod code;
|
||||||
pub mod tex;
|
pub mod tex;
|
||||||
|
pub mod graphviz;
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
|
|
|
@ -158,7 +158,7 @@ impl RegexRule for RawRule
|
||||||
.with_message("Invalid Code Property")
|
.with_message("Invalid Code Property")
|
||||||
.with_label(
|
.with_label(
|
||||||
Label::new((token.source().clone(), token.start()+1..token.end()))
|
Label::new((token.source().clone(), token.start()+1..token.end()))
|
||||||
.with_message(format!("Property `{}` doesn't exist",
|
.with_message(format!("Property `{}` is missing",
|
||||||
err.fg(parser.colors().info)))
|
err.fg(parser.colors().info)))
|
||||||
.with_color(parser.colors().warning))
|
.with_color(parser.colors().warning))
|
||||||
.finish());
|
.finish());
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
use crate::parser::parser::Parser;
|
use crate::parser::parser::Parser;
|
||||||
|
|
||||||
use super::{code::CodeRule, comment::CommentRule, import::ImportRule, link::LinkRule, list::ListRule, paragraph::ParagraphRule, raw::RawRule, script::ScriptRule, section::SectionRule, style::StyleRule, tex::TexRule, text::TextRule, variable::{VariableRule, VariableSubstitutionRule}};
|
use super::code::CodeRule;
|
||||||
|
use super::comment::CommentRule;
|
||||||
|
use super::graphviz::GraphRule;
|
||||||
|
use super::import::ImportRule;
|
||||||
|
use super::link::LinkRule;
|
||||||
|
use super::list::ListRule;
|
||||||
|
use super::paragraph::ParagraphRule;
|
||||||
|
use super::raw::RawRule;
|
||||||
|
use super::script::ScriptRule;
|
||||||
|
use super::section::SectionRule;
|
||||||
|
use super::style::StyleRule;
|
||||||
|
use super::tex::TexRule;
|
||||||
|
use super::text::TextRule;
|
||||||
|
use super::variable::VariableRule;
|
||||||
|
use super::variable::VariableSubstitutionRule;
|
||||||
|
|
||||||
|
pub fn register<P: Parser>(parser: &mut P) {
|
||||||
pub fn register<P: Parser>(parser: &mut P)
|
|
||||||
{
|
|
||||||
parser.add_rule(Box::new(CommentRule::new()), None);
|
parser.add_rule(Box::new(CommentRule::new()), None);
|
||||||
parser.add_rule(Box::new(ParagraphRule::new()), None);
|
parser.add_rule(Box::new(ParagraphRule::new()), None);
|
||||||
parser.add_rule(Box::new(ImportRule::new()), None);
|
parser.add_rule(Box::new(ImportRule::new()), None);
|
||||||
|
@ -15,6 +27,7 @@ pub fn register<P: Parser>(parser: &mut P)
|
||||||
parser.add_rule(Box::new(ListRule::new()), None);
|
parser.add_rule(Box::new(ListRule::new()), None);
|
||||||
parser.add_rule(Box::new(CodeRule::new()), None);
|
parser.add_rule(Box::new(CodeRule::new()), None);
|
||||||
parser.add_rule(Box::new(TexRule::new()), None);
|
parser.add_rule(Box::new(TexRule::new()), None);
|
||||||
|
parser.add_rule(Box::new(GraphRule::new()), None);
|
||||||
|
|
||||||
parser.add_rule(Box::new(StyleRule::new()), None);
|
parser.add_rule(Box::new(StyleRule::new()), None);
|
||||||
parser.add_rule(Box::new(SectionRule::new()), None);
|
parser.add_rule(Box::new(SectionRule::new()), None);
|
||||||
|
|
|
@ -1,32 +1,52 @@
|
||||||
use std::{io::{Read, Write}, ops::Range, process::{Command, Stdio}, rc::Rc, sync::Once};
|
use std::io::Read;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::process::Stdio;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
use ariadne::{Fmt, Label, Report, ReportKind};
|
use ariadne::Fmt;
|
||||||
use crypto::{digest::Digest, sha2::Sha512};
|
use ariadne::Label;
|
||||||
use mlua::{Function, Lua};
|
use ariadne::Report;
|
||||||
use regex::{Captures, Regex};
|
use ariadne::ReportKind;
|
||||||
|
use crypto::digest::Digest;
|
||||||
|
use crypto::sha2::Sha512;
|
||||||
|
use mlua::Function;
|
||||||
|
use mlua::Lua;
|
||||||
|
use regex::Captures;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::{cache::cache::{Cached, CachedError}, compiler::compiler::{Compiler, Target}, document::{document::Document, element::{ElemKind, Element}}, parser::{parser::Parser, rule::RegexRule, source::{Source, Token}, util}};
|
use crate::cache::cache::Cached;
|
||||||
|
use crate::cache::cache::CachedError;
|
||||||
|
use crate::compiler::compiler::Compiler;
|
||||||
|
use crate::compiler::compiler::Target;
|
||||||
|
use crate::document::document::Document;
|
||||||
|
use crate::document::element::ElemKind;
|
||||||
|
use crate::document::element::Element;
|
||||||
|
use crate::parser::parser::Parser;
|
||||||
|
use crate::parser::rule::RegexRule;
|
||||||
|
use crate::parser::source::Source;
|
||||||
|
use crate::parser::source::Token;
|
||||||
|
use crate::parser::util;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
enum TexKind
|
enum TexKind {
|
||||||
{
|
|
||||||
Block,
|
Block,
|
||||||
Inline,
|
Inline,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&TexKind> for ElemKind
|
impl From<&TexKind> for ElemKind {
|
||||||
{
|
|
||||||
fn from(value: &TexKind) -> Self {
|
fn from(value: &TexKind) -> Self {
|
||||||
match value {
|
match value {
|
||||||
TexKind::Inline => ElemKind::Inline,
|
TexKind::Inline => ElemKind::Inline,
|
||||||
_ => ElemKind::Block
|
_ => ElemKind::Block,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Tex
|
struct Tex {
|
||||||
{
|
|
||||||
location: Token,
|
location: Token,
|
||||||
block: TexKind,
|
block: TexKind,
|
||||||
env: String,
|
env: String,
|
||||||
|
@ -35,49 +55,59 @@ struct Tex
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tex {
|
impl Tex {
|
||||||
fn new(location: Token, block: TexKind, env: String, tex: String, caption: Option<String>) -> Self {
|
fn new(
|
||||||
Self { location, block, env, tex, caption }
|
location: Token,
|
||||||
|
block: TexKind,
|
||||||
|
env: String,
|
||||||
|
tex: String,
|
||||||
|
caption: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
location,
|
||||||
|
block,
|
||||||
|
env,
|
||||||
|
tex,
|
||||||
|
caption,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_latex(fontsize: &String, preamble: &String, tex: &String) -> FormattedTex
|
fn format_latex(fontsize: &String, preamble: &String, tex: &String) -> FormattedTex {
|
||||||
{
|
FormattedTex(format!(
|
||||||
FormattedTex(format!(r"\documentclass[{}pt,preview]{{standalone}}
|
r"\documentclass[{}pt,preview]{{standalone}}
|
||||||
{}
|
{}
|
||||||
\begin{{document}}
|
\begin{{document}}
|
||||||
\begin{{preview}}
|
\begin{{preview}}
|
||||||
{}
|
{}
|
||||||
\end{{preview}}
|
\end{{preview}}
|
||||||
\end{{document}}",
|
\end{{document}}",
|
||||||
fontsize, preamble, tex))
|
fontsize, preamble, tex
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FormattedTex(String);
|
struct FormattedTex(String);
|
||||||
|
|
||||||
impl FormattedTex
|
impl FormattedTex {
|
||||||
{
|
|
||||||
/// Renders latex to svg
|
/// Renders latex to svg
|
||||||
fn latex_to_svg(&self, exec: &String, fontsize: &String) -> Result<String, String>
|
fn latex_to_svg(&self, exec: &String, fontsize: &String) -> Result<String, String> {
|
||||||
{
|
|
||||||
print!("Rendering LaTex `{}`... ", self.0);
|
print!("Rendering LaTex `{}`... ", self.0);
|
||||||
let process = match Command::new(exec)
|
let process = match Command::new(exec)
|
||||||
.arg("--fontsize").arg(fontsize)
|
.arg("--fontsize")
|
||||||
|
.arg(fontsize)
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
Err(e) => return Err(format!("Could not spawn `{exec}`: {}", e)),
|
Err(e) => return Err(format!("Could not spawn `{exec}`: {}", e)),
|
||||||
Ok(process) => process
|
Ok(process) => process,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = process.stdin.unwrap().write_all(self.0.as_bytes())
|
if let Err(e) = process.stdin.unwrap().write_all(self.0.as_bytes()) {
|
||||||
{
|
|
||||||
panic!("Unable to write to `latex2svg`'s stdin: {}", e);
|
panic!("Unable to write to `latex2svg`'s stdin: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
match process.stdout.unwrap().read_to_string(&mut result)
|
match process.stdout.unwrap().read_to_string(&mut result) {
|
||||||
{
|
|
||||||
Err(e) => panic!("Unable to read `latex2svg` stdout: {}", e),
|
Err(e) => panic!("Unable to read `latex2svg` stdout: {}", e),
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
}
|
}
|
||||||
|
@ -87,8 +117,7 @@ impl FormattedTex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cached for FormattedTex
|
impl Cached for FormattedTex {
|
||||||
{
|
|
||||||
type Key = String;
|
type Key = String;
|
||||||
type Value = String;
|
type Value = String;
|
||||||
|
|
||||||
|
@ -115,71 +144,74 @@ impl Cached for FormattedTex
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Tex {
|
impl Element for Tex {
|
||||||
fn location(&self) -> &Token { &self.location }
|
fn location(&self) -> &Token {
|
||||||
|
&self.location
|
||||||
|
}
|
||||||
|
|
||||||
fn kind(&self) -> ElemKind { (&self.block).into() }
|
fn kind(&self) -> ElemKind {
|
||||||
|
(&self.block).into()
|
||||||
|
}
|
||||||
|
|
||||||
fn element_name(&self) -> &'static str { "LaTeX" }
|
fn element_name(&self) -> &'static str {
|
||||||
|
"LaTeX"
|
||||||
|
}
|
||||||
|
|
||||||
fn to_string(&self) -> String { format!("{self:#?}") }
|
fn to_string(&self) -> String {
|
||||||
|
format!("{self:#?}")
|
||||||
fn compile(&self, compiler: &Compiler, document: &dyn Document)
|
}
|
||||||
-> Result<String, String> {
|
|
||||||
|
|
||||||
|
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
|
||||||
match compiler.target() {
|
match compiler.target() {
|
||||||
Target::HTML => {
|
Target::HTML => {
|
||||||
static CACHE_INIT : Once = Once::new();
|
static CACHE_INIT: Once = Once::new();
|
||||||
CACHE_INIT.call_once(|| if let Some(mut con) = compiler.cache() {
|
CACHE_INIT.call_once(|| {
|
||||||
if let Err(e) = FormattedTex::init(&mut con)
|
if let Some(mut con) = compiler.cache() {
|
||||||
{
|
if let Err(e) = FormattedTex::init(&mut con) {
|
||||||
eprintln!("Unable to create cache table: {e}");
|
eprintln!("Unable to create cache table: {e}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let exec = document.get_variable(format!("tex.{}.exec", self.env).as_str())
|
let exec = document
|
||||||
|
.get_variable(format!("tex.{}.exec", self.env).as_str())
|
||||||
.map_or("latex2svg".to_string(), |var| var.to_string());
|
.map_or("latex2svg".to_string(), |var| var.to_string());
|
||||||
// FIXME: Because fontsize is passed as an arg, verify that it cannot be used to execute python/shell code
|
// FIXME: Because fontsize is passed as an arg, verify that it cannot be used to execute python/shell code
|
||||||
let fontsize = document.get_variable(format!("tex.{}.fontsize", self.env).as_str())
|
let fontsize = document
|
||||||
|
.get_variable(format!("tex.{}.fontsize", self.env).as_str())
|
||||||
.map_or("12".to_string(), |var| var.to_string());
|
.map_or("12".to_string(), |var| var.to_string());
|
||||||
let preamble = document.get_variable(format!("tex.{}.preamble", self.env).as_str())
|
let preamble = document
|
||||||
|
.get_variable(format!("tex.{}.preamble", self.env).as_str())
|
||||||
.map_or("".to_string(), |var| var.to_string());
|
.map_or("".to_string(), |var| var.to_string());
|
||||||
let prepend = if self.block == TexKind::Inline { "".to_string() }
|
let prepend = if self.block == TexKind::Inline {
|
||||||
else
|
"".to_string()
|
||||||
{
|
} else {
|
||||||
document.get_variable(format!("tex.{}.block_prepend", self.env).as_str())
|
document
|
||||||
.map_or("".to_string(), |var| var.to_string()+"\n")
|
.get_variable(format!("tex.{}.block_prepend", self.env).as_str())
|
||||||
|
.map_or("".to_string(), |var| var.to_string() + "\n")
|
||||||
};
|
};
|
||||||
|
|
||||||
let latex = match self.block
|
let latex = match self.block {
|
||||||
{
|
TexKind::Inline => {
|
||||||
TexKind::Inline => Tex::format_latex(
|
Tex::format_latex(&fontsize, &preamble, &format!("${{{}}}$", self.tex))
|
||||||
&fontsize,
|
}
|
||||||
&preamble,
|
_ => Tex::format_latex(&fontsize, &preamble, &format!("{prepend}{}", self.tex)),
|
||||||
&format!("${{{}}}$", self.tex)),
|
|
||||||
_ => Tex::format_latex(
|
|
||||||
&fontsize,
|
|
||||||
&preamble,
|
|
||||||
&format!("{prepend}{}", self.tex))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(mut con) = compiler.cache()
|
if let Some(mut con) = compiler.cache() {
|
||||||
{
|
match latex.cached(&mut con, |s| s.latex_to_svg(&exec, &fontsize)) {
|
||||||
match latex.cached(&mut con, |s| s.latex_to_svg(&exec, &fontsize))
|
|
||||||
{
|
|
||||||
Ok(s) => Ok(s),
|
Ok(s) => Ok(s),
|
||||||
Err(e) => match e
|
Err(e) => match e {
|
||||||
{
|
CachedError::SqlErr(e) => {
|
||||||
CachedError::SqlErr(e) => Err(format!("Querying the cache failed: {e}")),
|
Err(format!("Querying the cache failed: {e}"))
|
||||||
CachedError::GenErr(e) => Err(e)
|
|
||||||
}
|
}
|
||||||
|
CachedError::GenErr(e) => Err(e),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
latex.latex_to_svg(&exec, &fontsize)
|
latex.latex_to_svg(&exec, &fontsize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => todo!("Unimplemented")
|
_ => todo!("Unimplemented"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,23 +231,32 @@ impl TexRule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegexRule for TexRule
|
impl RegexRule for TexRule {
|
||||||
{
|
fn name(&self) -> &'static str {
|
||||||
fn name(&self) -> &'static str { "Tex" }
|
"Tex"
|
||||||
|
}
|
||||||
|
|
||||||
fn regexes(&self) -> &[regex::Regex] { &self.re }
|
fn regexes(&self) -> &[regex::Regex] {
|
||||||
|
&self.re
|
||||||
|
}
|
||||||
|
|
||||||
fn on_regex_match(&self, index: usize, parser: &dyn Parser, document: &dyn Document, token: Token, matches: Captures)
|
fn on_regex_match(
|
||||||
-> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
&self,
|
||||||
|
index: usize,
|
||||||
|
parser: &dyn Parser,
|
||||||
|
document: &dyn Document,
|
||||||
|
token: Token,
|
||||||
|
matches: Captures,
|
||||||
|
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
||||||
let mut reports = vec![];
|
let mut reports = vec![];
|
||||||
|
|
||||||
let tex_env = matches.get(1)
|
let tex_env = matches
|
||||||
|
.get(1)
|
||||||
.and_then(|env| Some(env.as_str().trim_start().trim_end()))
|
.and_then(|env| Some(env.as_str().trim_start().trim_end()))
|
||||||
.and_then(|env| (!env.is_empty()).then_some(env))
|
.and_then(|env| (!env.is_empty()).then_some(env))
|
||||||
.unwrap_or("main");
|
.unwrap_or("main");
|
||||||
|
|
||||||
let tex_content = match matches.get(2)
|
let tex_content = match matches.get(2) {
|
||||||
{
|
|
||||||
// Unterminated `$`
|
// Unterminated `$`
|
||||||
None => {
|
None => {
|
||||||
reports.push(
|
reports.push(
|
||||||
|
@ -223,27 +264,35 @@ impl RegexRule for TexRule
|
||||||
.with_message("Unterminated Tex Code")
|
.with_message("Unterminated Tex Code")
|
||||||
.with_label(
|
.with_label(
|
||||||
Label::new((token.source().clone(), token.range.clone()))
|
Label::new((token.source().clone(), token.range.clone()))
|
||||||
.with_message(format!("Missing terminating `{}` after first `{}`",
|
.with_message(format!(
|
||||||
|
"Missing terminating `{}` after first `{}`",
|
||||||
["|$", "$"][index].fg(parser.colors().info),
|
["|$", "$"][index].fg(parser.colors().info),
|
||||||
["$|", "$"][index].fg(parser.colors().info)))
|
["$|", "$"][index].fg(parser.colors().info)
|
||||||
.with_color(parser.colors().error))
|
))
|
||||||
.finish());
|
.with_color(parser.colors().error),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
return reports;
|
return reports;
|
||||||
}
|
}
|
||||||
Some(content) => {
|
Some(content) => {
|
||||||
let processed = util::process_escaped('\\', ["|$", "$"][index],
|
let processed = util::process_escaped(
|
||||||
content.as_str().trim_start().trim_end());
|
'\\',
|
||||||
|
["|$", "$"][index],
|
||||||
|
content.as_str().trim_start().trim_end(),
|
||||||
|
);
|
||||||
|
|
||||||
if processed.is_empty()
|
if processed.is_empty() {
|
||||||
{
|
|
||||||
reports.push(
|
reports.push(
|
||||||
Report::build(ReportKind::Warning, token.source(), content.start())
|
Report::build(ReportKind::Warning, token.source(), content.start())
|
||||||
.with_message("Empty Tex Code")
|
.with_message("Empty Tex Code")
|
||||||
.with_label(
|
.with_label(
|
||||||
Label::new((token.source().clone(), content.range()))
|
Label::new((token.source().clone(), content.range()))
|
||||||
.with_message("Tex code is empty")
|
.with_message("Tex code is empty")
|
||||||
.with_color(parser.colors().warning))
|
.with_color(parser.colors().warning),
|
||||||
.finish());
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
processed
|
processed
|
||||||
}
|
}
|
||||||
|
@ -251,17 +300,26 @@ impl RegexRule for TexRule
|
||||||
|
|
||||||
// TODO: Caption
|
// TODO: Caption
|
||||||
|
|
||||||
parser.push(document, Box::new(Tex::new(
|
parser.push(
|
||||||
|
document,
|
||||||
|
Box::new(Tex::new(
|
||||||
token,
|
token,
|
||||||
if index == 1 { TexKind::Inline } else { TexKind::Block },
|
if index == 1 {
|
||||||
|
TexKind::Inline
|
||||||
|
} else {
|
||||||
|
TexKind::Block
|
||||||
|
},
|
||||||
tex_env.to_string(),
|
tex_env.to_string(),
|
||||||
tex_content,
|
tex_content,
|
||||||
None,
|
None,
|
||||||
)));
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
reports
|
reports
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
|
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,8 @@ impl Parser for LsParser
|
||||||
fn state(&self) -> std::cell::Ref<'_, StateHolder> { self.state.borrow() }
|
fn state(&self) -> std::cell::Ref<'_, StateHolder> { self.state.borrow() }
|
||||||
fn state_mut(&self) -> std::cell::RefMut<'_, StateHolder> { self.state.borrow_mut() }
|
fn state_mut(&self) -> std::cell::RefMut<'_, StateHolder> { self.state.borrow_mut() }
|
||||||
|
|
||||||
|
fn has_error(&self) -> bool { true }
|
||||||
|
|
||||||
fn push<'a>(&self, doc: &dyn Document, elem: Box<dyn Element>) {
|
fn push<'a>(&self, doc: &dyn Document, elem: Box<dyn Element>) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
49
src/main.rs
49
src/main.rs
|
@ -1,16 +1,18 @@
|
||||||
#![feature(char_indices_offset)]
|
#![feature(char_indices_offset)]
|
||||||
mod document;
|
mod cache;
|
||||||
mod compiler;
|
mod compiler;
|
||||||
mod parser;
|
mod document;
|
||||||
mod elements;
|
mod elements;
|
||||||
mod lua;
|
mod lua;
|
||||||
mod cache;
|
mod parser;
|
||||||
|
|
||||||
use std::{env, rc::Rc};
|
use std::env;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use compiler::compiler::Compiler;
|
use compiler::compiler::Compiler;
|
||||||
use getopts::Options;
|
use getopts::Options;
|
||||||
use parser::{langparser::LangParser, parser::Parser};
|
use parser::langparser::LangParser;
|
||||||
|
use parser::parser::Parser;
|
||||||
|
|
||||||
use crate::parser::source::SourceFile;
|
use crate::parser::source::SourceFile;
|
||||||
extern crate getopts;
|
extern crate getopts;
|
||||||
|
@ -20,9 +22,9 @@ fn print_usage(program: &str, opts: Options) {
|
||||||
print!("{}", opts.usage(&brief));
|
print!("{}", opts.usage(&brief));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_version()
|
fn print_version() {
|
||||||
{
|
print!(
|
||||||
print!("NML -- Not a Markup Language
|
"NML -- Not a Markup Language
|
||||||
Copyright (c) 2024
|
Copyright (c) 2024
|
||||||
NML is licensed under the GNU Affero General Public License version 3 (AGPLv3),
|
NML is licensed under the GNU Affero General Public License version 3 (AGPLv3),
|
||||||
under the terms of the Free Software Foundation <https://www.gnu.org/licenses/agpl-3.0.en.html>.
|
under the terms of the Free Software Foundation <https://www.gnu.org/licenses/agpl-3.0.en.html>.
|
||||||
|
@ -30,7 +32,8 @@ under the terms of the Free Software Foundation <https://www.gnu.org/licenses/ag
|
||||||
This program is free software; you may modify and redistribute it.
|
This program is free software; you may modify and redistribute it.
|
||||||
There is NO WARRANTY, to the extent permitted by law.
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
|
||||||
NML version: 0.4\n");
|
NML version: 0.4\n"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -45,11 +48,12 @@ fn main() {
|
||||||
opts.optflag("v", "version", "Print program version and licenses");
|
opts.optflag("v", "version", "Print program version and licenses");
|
||||||
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
Ok(m) => { m }
|
Ok(m) => m,
|
||||||
Err(f) => { panic!("{}", f.to_string()) }
|
Err(f) => {
|
||||||
|
panic!("{}", f.to_string())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if matches.opt_present("v")
|
if matches.opt_present("v") {
|
||||||
{
|
|
||||||
print_version();
|
print_version();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -72,16 +76,15 @@ fn main() {
|
||||||
let source = SourceFile::new(input.to_string(), None).unwrap();
|
let source = SourceFile::new(input.to_string(), None).unwrap();
|
||||||
let doc = parser.parse(Rc::new(source), None);
|
let doc = parser.parse(Rc::new(source), None);
|
||||||
|
|
||||||
if debug_opts.contains(&"ast".to_string())
|
if debug_opts.contains(&"ast".to_string()) {
|
||||||
{
|
|
||||||
println!("-- BEGIN AST DEBUGGING --");
|
println!("-- BEGIN AST DEBUGGING --");
|
||||||
doc.content().borrow().iter().for_each(|elem| {
|
doc.content()
|
||||||
println!("{}", (elem).to_string())
|
.borrow()
|
||||||
});
|
.iter()
|
||||||
|
.for_each(|elem| println!("{}", (elem).to_string()));
|
||||||
println!("-- END AST DEBUGGING --");
|
println!("-- END AST DEBUGGING --");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
//if debug_opts.contains(&"ref".to_string())
|
//if debug_opts.contains(&"ref".to_string())
|
||||||
//{
|
//{
|
||||||
|
@ -92,8 +95,7 @@ fn main() {
|
||||||
// });
|
// });
|
||||||
// println!("-- END REFERENCES DEBUGGING --");
|
// println!("-- END REFERENCES DEBUGGING --");
|
||||||
//}
|
//}
|
||||||
if debug_opts.contains(&"var".to_string())
|
if debug_opts.contains(&"var".to_string()) {
|
||||||
{
|
|
||||||
println!("-- BEGIN VARIABLES DEBUGGING --");
|
println!("-- BEGIN VARIABLES DEBUGGING --");
|
||||||
let sc = doc.scope().borrow();
|
let sc = doc.scope().borrow();
|
||||||
sc.variables.iter().for_each(|(_name, var)| {
|
sc.variables.iter().for_each(|(_name, var)| {
|
||||||
|
@ -102,10 +104,13 @@ fn main() {
|
||||||
println!("-- END VARIABLES DEBUGGING --");
|
println!("-- END VARIABLES DEBUGGING --");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parser.has_error() {
|
||||||
|
println!("Compilation aborted due to errors while parsing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let compiler = Compiler::new(compiler::compiler::Target::HTML, db_path);
|
let compiler = Compiler::new(compiler::compiler::Target::HTML, db_path);
|
||||||
let out = compiler.compile(doc.as_ref());
|
let out = compiler.compile(doc.as_ref());
|
||||||
|
|
||||||
std::fs::write("a.html", out).unwrap();
|
std::fs::write("a.html", out).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,38 @@
|
||||||
use std::{cell::{Ref, RefCell, RefMut}, collections::{HashMap, HashSet}, ops::Range, rc::Rc};
|
use std::cell::RefCell;
|
||||||
|
use std::cell::RefMut;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use ariadne::{Label, Report};
|
use ariadne::Label;
|
||||||
|
use ariadne::Report;
|
||||||
|
|
||||||
use crate::{document::{document::{DocumentAccessors, Document}, element::{ElemKind, Element}, langdocument::LangDocument}, elements::{paragraph::Paragraph, registrar::register, text::Text}, lua::kernel::{Kernel, KernelHolder}, parser::source::{SourceFile, VirtualSource}};
|
use crate::document::document::Document;
|
||||||
|
use crate::document::document::DocumentAccessors;
|
||||||
|
use crate::document::element::ElemKind;
|
||||||
|
use crate::document::element::Element;
|
||||||
|
use crate::document::langdocument::LangDocument;
|
||||||
|
use crate::elements::paragraph::Paragraph;
|
||||||
|
use crate::elements::registrar::register;
|
||||||
|
use crate::elements::text::Text;
|
||||||
|
use crate::lua::kernel::Kernel;
|
||||||
|
use crate::lua::kernel::KernelHolder;
|
||||||
|
use crate::parser::source::SourceFile;
|
||||||
|
use crate::parser::source::VirtualSource;
|
||||||
|
|
||||||
use super::{parser::{Parser, ReportColors}, rule::Rule, source::{Cursor, Source, Token}, state::StateHolder, util};
|
use super::parser::Parser;
|
||||||
|
use super::parser::ReportColors;
|
||||||
|
use super::rule::Rule;
|
||||||
|
use super::source::Cursor;
|
||||||
|
use super::source::Source;
|
||||||
|
use super::source::Token;
|
||||||
|
use super::state::StateHolder;
|
||||||
|
use super::util;
|
||||||
|
|
||||||
/// Parser for the language
|
/// Parser for the language
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LangParser
|
pub struct LangParser {
|
||||||
{
|
|
||||||
rules: Vec<Box<dyn Rule>>,
|
rules: Vec<Box<dyn Rule>>,
|
||||||
colors: ReportColors,
|
colors: ReportColors,
|
||||||
|
|
||||||
|
@ -19,10 +42,8 @@ pub struct LangParser
|
||||||
pub kernels: RefCell<HashMap<String, Kernel>>,
|
pub kernels: RefCell<HashMap<String, Kernel>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LangParser
|
impl LangParser {
|
||||||
{
|
pub fn default() -> Self {
|
||||||
pub fn default() -> Self
|
|
||||||
{
|
|
||||||
let mut s = Self {
|
let mut s = Self {
|
||||||
rules: vec![],
|
rules: vec![],
|
||||||
colors: ReportColors::with_colors(),
|
colors: ReportColors::with_colors(),
|
||||||
|
@ -32,24 +53,25 @@ impl LangParser
|
||||||
};
|
};
|
||||||
register(&mut s);
|
register(&mut s);
|
||||||
|
|
||||||
s.kernels.borrow_mut()
|
s.kernels
|
||||||
|
.borrow_mut()
|
||||||
.insert("main".to_string(), Kernel::new(&s));
|
.insert("main".to_string(), Kernel::new(&s));
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_reports<'a>(&self, _source: Rc<dyn Source>, reports: Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>>)
|
fn handle_reports<'a>(
|
||||||
{
|
&self,
|
||||||
for mut report in reports
|
_source: Rc<dyn Source>,
|
||||||
{
|
reports: Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>>,
|
||||||
|
) {
|
||||||
|
for mut report in reports {
|
||||||
let mut sources: HashSet<Rc<dyn Source>> = HashSet::new();
|
let mut sources: HashSet<Rc<dyn Source>> = HashSet::new();
|
||||||
fn recurse_source(sources: &mut HashSet<Rc<dyn Source>>, source: Rc<dyn Source>) {
|
fn recurse_source(sources: &mut HashSet<Rc<dyn Source>>, source: Rc<dyn Source>) {
|
||||||
sources.insert(source.clone());
|
sources.insert(source.clone());
|
||||||
match source.location()
|
match source.location() {
|
||||||
{
|
|
||||||
Some(parent) => {
|
Some(parent) => {
|
||||||
let parent_source = parent.source();
|
let parent_source = parent.source();
|
||||||
if sources.get(&parent_source).is_none()
|
if sources.get(&parent_source).is_none() {
|
||||||
{
|
|
||||||
recurse_source(sources, parent_source);
|
recurse_source(sources, parent_source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,32 +83,31 @@ impl LangParser
|
||||||
recurse_source(&mut sources, label.span.0.clone());
|
recurse_source(&mut sources, label.span.0.clone());
|
||||||
});
|
});
|
||||||
|
|
||||||
let cache = sources.iter()
|
let cache = sources
|
||||||
|
.iter()
|
||||||
.map(|source| (source.clone(), source.content().clone()))
|
.map(|source| (source.clone(), source.content().clone()))
|
||||||
.collect::<Vec<(Rc<dyn Source>, String)>>();
|
.collect::<Vec<(Rc<dyn Source>, String)>>();
|
||||||
|
|
||||||
cache.iter()
|
cache.iter().for_each(|(source, _)| {
|
||||||
.for_each(|(source, _)| {
|
if let Some(location) = source.location() {
|
||||||
if let Some (location) = source.location()
|
if let Some(_s) = source.downcast_ref::<SourceFile>() {
|
||||||
{
|
|
||||||
if let Some(_s) = source.downcast_ref::<SourceFile>()
|
|
||||||
{
|
|
||||||
report.labels.push(
|
report.labels.push(
|
||||||
Label::new((location.source(), location.start()+1 .. location.end()))
|
Label::new((location.source(), location.start() + 1..location.end()))
|
||||||
.with_message("In file included from here")
|
.with_message("In file included from here")
|
||||||
.with_order(-1)
|
.with_order(-1),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(_s) = source.downcast_ref::<VirtualSource>()
|
if let Some(_s) = source.downcast_ref::<VirtualSource>() {
|
||||||
{
|
let start = location.start()
|
||||||
let start = location.start() + (location.source().content().as_bytes()[location.start()] == '\n' as u8)
|
+ (location.source().content().as_bytes()[location.start()]
|
||||||
|
== '\n' as u8)
|
||||||
.then_some(1)
|
.then_some(1)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
report.labels.push(
|
report.labels.push(
|
||||||
Label::new((location.source(), start .. location.end()))
|
Label::new((location.source(), start..location.end()))
|
||||||
.with_message("In evaluation of")
|
.with_message("In evaluation of")
|
||||||
.with_order(-1)
|
.with_order(-1),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -96,45 +117,58 @@ impl LangParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser for LangParser
|
impl Parser for LangParser {
|
||||||
{
|
fn colors(&self) -> &ReportColors {
|
||||||
fn colors(&self) -> &ReportColors { &self.colors }
|
&self.colors
|
||||||
|
}
|
||||||
|
|
||||||
fn rules(&self) -> &Vec<Box<dyn Rule>> { &self.rules }
|
fn rules(&self) -> &Vec<Box<dyn Rule>> {
|
||||||
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>> { &mut self.rules }
|
&self.rules
|
||||||
|
}
|
||||||
|
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>> {
|
||||||
|
&mut self.rules
|
||||||
|
}
|
||||||
|
|
||||||
fn state(&self) -> std::cell::Ref<'_, StateHolder> { self.state.borrow() }
|
fn state(&self) -> std::cell::Ref<'_, StateHolder> {
|
||||||
fn state_mut(&self) -> std::cell::RefMut<'_, StateHolder> { self.state.borrow_mut() }
|
self.state.borrow()
|
||||||
|
}
|
||||||
|
fn state_mut(&self) -> std::cell::RefMut<'_, StateHolder> {
|
||||||
|
self.state.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_error(&self) -> bool { *self.err_flag.borrow() }
|
||||||
|
|
||||||
/// Add an [`Element`] to the [`Document`]
|
/// Add an [`Element`] to the [`Document`]
|
||||||
fn push<'a>(&self, doc: &dyn Document, elem: Box<dyn Element>)
|
fn push<'a>(&self, doc: &dyn Document, elem: Box<dyn Element>) {
|
||||||
{
|
if elem.kind() == ElemKind::Inline || elem.kind() == ElemKind::Invisible {
|
||||||
if elem.kind() == ElemKind::Inline || elem.kind() == ElemKind::Invisible
|
let mut paragraph = doc
|
||||||
{
|
.last_element_mut::<Paragraph>()
|
||||||
let mut paragraph = doc.last_element_mut::<Paragraph>()
|
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
doc.push(Box::new(Paragraph::new(elem.location().clone())));
|
doc.push(Box::new(Paragraph::new(elem.location().clone())));
|
||||||
doc.last_element_mut::<Paragraph>()
|
doc.last_element_mut::<Paragraph>()
|
||||||
}).unwrap();
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
paragraph.push(elem);
|
paragraph.push(elem);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Process paragraph events
|
// Process paragraph events
|
||||||
if doc.last_element::<Paragraph>()
|
if doc.last_element::<Paragraph>().is_some_and(|_| true) {
|
||||||
.is_some_and(|_| true)
|
self.handle_reports(
|
||||||
{
|
doc.source(),
|
||||||
self.handle_reports(doc.source(),
|
self.state_mut()
|
||||||
self.state_mut().on_scope_end(self, doc, super::state::Scope::PARAGRAPH));
|
.on_scope_end(self, doc, super::state::Scope::PARAGRAPH),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.push(elem);
|
doc.push(elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse<'a>(&self, source: Rc<dyn Source>, parent: Option<&'a dyn Document<'a>>) -> Box<dyn Document<'a>+'a>
|
fn parse<'a>(
|
||||||
{
|
&self,
|
||||||
|
source: Rc<dyn Source>,
|
||||||
|
parent: Option<&'a dyn Document<'a>>,
|
||||||
|
) -> Box<dyn Document<'a> + 'a> {
|
||||||
let doc = LangDocument::new(source.clone(), parent);
|
let doc = LangDocument::new(source.clone(), parent);
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
for _ in 0..self.rules.len() {
|
for _ in 0..self.rules.len() {
|
||||||
|
@ -144,53 +178,59 @@ impl Parser for LangParser
|
||||||
let content = source.content();
|
let content = source.content();
|
||||||
let mut cursor = Cursor::new(0usize, doc.source()); // Cursor in file
|
let mut cursor = Cursor::new(0usize, doc.source()); // Cursor in file
|
||||||
|
|
||||||
if let Some(parent) = parent // Terminate parent's paragraph state
|
if let Some(parent) = parent
|
||||||
|
// Terminate parent's paragraph state
|
||||||
{
|
{
|
||||||
self.handle_reports(parent.source(),
|
self.handle_reports(
|
||||||
self.state_mut().on_scope_end(self, parent, super::state::Scope::PARAGRAPH));
|
parent.source(),
|
||||||
|
self.state_mut()
|
||||||
|
.on_scope_end(self, parent, super::state::Scope::PARAGRAPH),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loop
|
loop {
|
||||||
{
|
|
||||||
let (rule_pos, rule, match_data) = self.update_matches(&cursor, &mut matches);
|
let (rule_pos, rule, match_data) = self.update_matches(&cursor, &mut matches);
|
||||||
|
|
||||||
// Unmatched content
|
// Unmatched content
|
||||||
let text_content = util::process_text(&doc, &content.as_str()[cursor.pos..rule_pos.pos]);
|
let text_content =
|
||||||
if !text_content.is_empty()
|
util::process_text(&doc, &content.as_str()[cursor.pos..rule_pos.pos]);
|
||||||
{
|
if !text_content.is_empty() {
|
||||||
self.push(&doc, Box::new(Text::new(
|
self.push(
|
||||||
|
&doc,
|
||||||
|
Box::new(Text::new(
|
||||||
Token::new(cursor.pos..rule_pos.pos, source.clone()),
|
Token::new(cursor.pos..rule_pos.pos, source.clone()),
|
||||||
text_content
|
text_content,
|
||||||
)));
|
)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rule) = rule
|
if let Some(rule) = rule {
|
||||||
{
|
|
||||||
|
|
||||||
// Rule callback
|
// Rule callback
|
||||||
let dd: &'a dyn Document = unsafe {std::mem::transmute(&doc as &dyn Document)};
|
let dd: &'a dyn Document = unsafe { std::mem::transmute(&doc as &dyn Document) };
|
||||||
let (new_cursor, reports) = rule.on_match(self, dd, rule_pos, match_data);
|
let (new_cursor, reports) = rule.on_match(self, dd, rule_pos, match_data);
|
||||||
|
|
||||||
self.handle_reports(doc.source(), reports);
|
self.handle_reports(doc.source(), reports);
|
||||||
|
|
||||||
// Advance
|
// Advance
|
||||||
cursor = new_cursor;
|
cursor = new_cursor;
|
||||||
}
|
} else
|
||||||
else // No rules left
|
// No rules left
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// State
|
// State
|
||||||
self.handle_reports(doc.source(),
|
self.handle_reports(
|
||||||
self.state_mut().on_scope_end(self, &doc, super::state::Scope::DOCUMENT));
|
doc.source(),
|
||||||
|
self.state_mut()
|
||||||
|
.on_scope_end(self, &doc, super::state::Scope::DOCUMENT),
|
||||||
|
);
|
||||||
|
|
||||||
return Box::new(doc);
|
return Box::new(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_into<'a>(&self, source: Rc<dyn Source>, document: &'a dyn Document<'a>)
|
fn parse_into<'a>(&self, source: Rc<dyn Source>, document: &'a dyn Document<'a>) {
|
||||||
{
|
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
for _ in 0..self.rules.len() {
|
for _ in 0..self.rules.len() {
|
||||||
matches.push((0usize, None));
|
matches.push((0usize, None));
|
||||||
|
@ -199,22 +239,23 @@ impl Parser for LangParser
|
||||||
let content = source.content();
|
let content = source.content();
|
||||||
let mut cursor = Cursor::new(0usize, source.clone());
|
let mut cursor = Cursor::new(0usize, source.clone());
|
||||||
|
|
||||||
loop
|
loop {
|
||||||
{
|
|
||||||
let (rule_pos, rule, match_data) = self.update_matches(&cursor, &mut matches);
|
let (rule_pos, rule, match_data) = self.update_matches(&cursor, &mut matches);
|
||||||
|
|
||||||
// Unmatched content
|
// Unmatched content
|
||||||
let text_content = util::process_text(document, &content.as_str()[cursor.pos..rule_pos.pos]);
|
let text_content =
|
||||||
if !text_content.is_empty()
|
util::process_text(document, &content.as_str()[cursor.pos..rule_pos.pos]);
|
||||||
{
|
if !text_content.is_empty() {
|
||||||
self.push(document, Box::new(Text::new(
|
self.push(
|
||||||
|
document,
|
||||||
|
Box::new(Text::new(
|
||||||
Token::new(cursor.pos..rule_pos.pos, source.clone()),
|
Token::new(cursor.pos..rule_pos.pos, source.clone()),
|
||||||
text_content
|
text_content,
|
||||||
)));
|
)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rule) = rule
|
if let Some(rule) = rule {
|
||||||
{
|
|
||||||
// Rule callback
|
// Rule callback
|
||||||
let (new_cursor, reports) = (*rule).on_match(self, document, rule_pos, match_data);
|
let (new_cursor, reports) = (*rule).on_match(self, document, rule_pos, match_data);
|
||||||
|
|
||||||
|
@ -222,8 +263,8 @@ impl Parser for LangParser
|
||||||
|
|
||||||
// Advance
|
// Advance
|
||||||
cursor = new_cursor;
|
cursor = new_cursor;
|
||||||
}
|
} else
|
||||||
else // No rules left
|
// No rules left
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -237,19 +278,14 @@ impl Parser for LangParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KernelHolder for LangParser
|
impl KernelHolder for LangParser {
|
||||||
{
|
fn get_kernel(&self, name: &str) -> Option<RefMut<'_, Kernel>> {
|
||||||
fn get_kernel(&self, name: &str)
|
RefMut::filter_map(self.kernels.borrow_mut(), |map| map.get_mut(name)).ok()
|
||||||
-> Option<RefMut<'_, Kernel>> {
|
|
||||||
RefMut::filter_map(self.kernels.borrow_mut(),
|
|
||||||
|map| map.get_mut(name)).ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_kernel(&self, name: String, kernel: Kernel)
|
fn insert_kernel(&self, name: String, kernel: Kernel) -> RefMut<'_, Kernel> {
|
||||||
-> RefMut<'_, Kernel> {
|
|
||||||
//TODO do not get
|
//TODO do not get
|
||||||
self.kernels.borrow_mut()
|
self.kernels.borrow_mut().insert(name.clone(), kernel);
|
||||||
.insert(name.clone(), kernel);
|
|
||||||
self.get_kernel(name.as_str()).unwrap()
|
self.get_kernel(name.as_str()).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub mod source;
|
|
||||||
pub mod parser;
|
|
||||||
pub mod langparser;
|
pub mod langparser;
|
||||||
|
pub mod parser;
|
||||||
pub mod rule;
|
pub mod rule;
|
||||||
|
pub mod source;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::cell::{Ref, RefMut};
|
use std::cell::Ref;
|
||||||
|
use std::cell::RefMut;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::rule::Rule;
|
use super::rule::Rule;
|
||||||
use super::source::{Cursor, Source};
|
use super::source::Cursor;
|
||||||
|
use super::source::Source;
|
||||||
use super::state::StateHolder;
|
use super::state::StateHolder;
|
||||||
use crate::document::document::Document;
|
use crate::document::document::Document;
|
||||||
use crate::document::element::Element;
|
use crate::document::element::Element;
|
||||||
use ariadne::Color;
|
|
||||||
use crate::lua::kernel::KernelHolder;
|
use crate::lua::kernel::KernelHolder;
|
||||||
|
use ariadne::Color;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ReportColors
|
pub struct ReportColors {
|
||||||
{
|
|
||||||
pub error: Color,
|
pub error: Color,
|
||||||
pub warning: Color,
|
pub warning: Color,
|
||||||
pub info: Color,
|
pub info: Color,
|
||||||
|
@ -40,8 +41,7 @@ impl ReportColors {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Parser: KernelHolder
|
pub trait Parser: KernelHolder {
|
||||||
{
|
|
||||||
/// Gets the colors for formatting errors
|
/// Gets the colors for formatting errors
|
||||||
///
|
///
|
||||||
/// When colors are disabled, all colors should resolve to empty string
|
/// When colors are disabled, all colors should resolve to empty string
|
||||||
|
@ -50,33 +50,40 @@ pub trait Parser: KernelHolder
|
||||||
fn rules(&self) -> &Vec<Box<dyn Rule>>;
|
fn rules(&self) -> &Vec<Box<dyn Rule>>;
|
||||||
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>>;
|
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>>;
|
||||||
|
|
||||||
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>) -> Result<(), String>
|
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>) -> Result<(), String> {
|
||||||
{
|
|
||||||
// Error on duplicate rule
|
// Error on duplicate rule
|
||||||
let rule_name = (*rule).name();
|
let rule_name = (*rule).name();
|
||||||
if let Err(e) = self.rules().iter().try_for_each(|rule| {
|
if let Err(e) = self.rules().iter().try_for_each(|rule| {
|
||||||
if (*rule).name() != rule_name { return Ok(()); }
|
if (*rule).name() != rule_name {
|
||||||
|
return Ok(());
|
||||||
return Err(format!("Attempted to introduce duplicate rule: `{rule_name}`"));
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return Err(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match after
|
return Err(format!(
|
||||||
{
|
"Attempted to introduce duplicate rule: `{rule_name}`"
|
||||||
|
));
|
||||||
|
}) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
match after {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let before = self.rules().iter()
|
let before = self
|
||||||
|
.rules()
|
||||||
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_pos, r)| (r).name() == name);
|
.find(|(_pos, r)| (r).name() == name);
|
||||||
|
|
||||||
match before
|
match before {
|
||||||
{
|
Some((pos, _)) => self.rules_mut().insert(pos + 1, rule),
|
||||||
Some((pos, _)) => self.rules_mut().insert(pos+1, rule),
|
_ => {
|
||||||
_ => return Err(format!("Unable to find rule named `{name}`, to insert rule `{}` after it", rule.name()))
|
return Err(format!(
|
||||||
|
"Unable to find rule named `{name}`, to insert rule `{}` after it",
|
||||||
|
rule.name()
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => self.rules_mut().push(rule)
|
}
|
||||||
|
_ => self.rules_mut().push(rule),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -85,43 +92,51 @@ pub trait Parser: KernelHolder
|
||||||
fn state(&self) -> Ref<'_, StateHolder>;
|
fn state(&self) -> Ref<'_, StateHolder>;
|
||||||
fn state_mut(&self) -> RefMut<'_, StateHolder>;
|
fn state_mut(&self) -> RefMut<'_, StateHolder>;
|
||||||
|
|
||||||
|
fn has_error(&self) -> bool;
|
||||||
|
|
||||||
// Update [`matches`] and returns the position of the next matched rule.
|
// Update [`matches`] and returns the position of the next matched rule.
|
||||||
// If rule is empty, it means that there are no rules left to parse (i.e
|
// If rule is empty, it means that there are no rules left to parse (i.e
|
||||||
// end of document).
|
// end of document).
|
||||||
fn update_matches(&self, cursor: &Cursor, matches: &mut Vec<(usize, Option<Box<dyn Any>>)>)
|
fn update_matches(
|
||||||
-> (Cursor, Option<&Box<dyn Rule>>, Option<Box<dyn Any>>)
|
&self,
|
||||||
{
|
cursor: &Cursor,
|
||||||
|
matches: &mut Vec<(usize, Option<Box<dyn Any>>)>,
|
||||||
|
) -> (Cursor, Option<&Box<dyn Rule>>, Option<Box<dyn Any>>) {
|
||||||
// Update matches
|
// Update matches
|
||||||
// TODO: Trivially parellalizable
|
// TODO: Trivially parellalizable
|
||||||
self.rules().iter().zip(matches.iter_mut()).for_each(
|
self.rules()
|
||||||
|(rule, (matched_at, match_data))| {
|
.iter()
|
||||||
|
.zip(matches.iter_mut())
|
||||||
|
.for_each(|(rule, (matched_at, match_data))| {
|
||||||
// Don't upate if not stepped over yet
|
// Don't upate if not stepped over yet
|
||||||
if *matched_at > cursor.pos { return }
|
if *matched_at > cursor.pos {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
(*matched_at, *match_data) = match rule.next_match(cursor)
|
(*matched_at, *match_data) = match rule.next_match(cursor) {
|
||||||
{
|
|
||||||
None => (usize::MAX, None),
|
None => (usize::MAX, None),
|
||||||
Some((mut pos, mut data)) =>
|
Some((mut pos, mut data)) => {
|
||||||
{
|
|
||||||
// Check if escaped
|
// Check if escaped
|
||||||
while pos != usize::MAX
|
while pos != usize::MAX {
|
||||||
{
|
|
||||||
let content = cursor.source.content().as_str();
|
let content = cursor.source.content().as_str();
|
||||||
let mut graphemes = content[0 .. pos].graphemes(true);
|
let mut graphemes = content[0..pos].graphemes(true);
|
||||||
let mut escaped = false;
|
let mut escaped = false;
|
||||||
'inner: loop
|
'inner: loop {
|
||||||
{
|
|
||||||
let g = graphemes.next_back();
|
let g = graphemes.next_back();
|
||||||
if !g.is_some() || g.unwrap() != "\\" { break 'inner; }
|
if !g.is_some() || g.unwrap() != "\\" {
|
||||||
|
break 'inner;
|
||||||
|
}
|
||||||
|
|
||||||
escaped = !escaped;
|
escaped = !escaped;
|
||||||
}
|
}
|
||||||
if !escaped { break; }
|
if !escaped {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Find next potential match
|
// Find next potential match
|
||||||
(pos, data) = match rule.next_match(&cursor.at(pos+1)) {
|
(pos, data) = match rule.next_match(&cursor.at(pos + 1)) {
|
||||||
Some((new_pos, new_data)) => (new_pos, new_data),
|
Some((new_pos, new_data)) => (new_pos, new_data),
|
||||||
None => (usize::MAX, data) // Stop iterating
|
None => (usize::MAX, data), // Stop iterating
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,26 +146,35 @@ pub trait Parser: KernelHolder
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get winning match
|
// Get winning match
|
||||||
let (winner, (next_pos, _match_data)) = matches.iter()
|
let (winner, (next_pos, _match_data)) = matches
|
||||||
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.min_by_key(|(_, (pos, _match_data))| pos).unwrap();
|
.min_by_key(|(_, (pos, _match_data))| pos)
|
||||||
if *next_pos == usize::MAX // No rule has matched
|
.unwrap();
|
||||||
|
if *next_pos == usize::MAX
|
||||||
|
// No rule has matched
|
||||||
{
|
{
|
||||||
let content = cursor.source.content();
|
let content = cursor.source.content();
|
||||||
// No winners, i.e no matches left
|
// No winners, i.e no matches left
|
||||||
return (cursor.at(content.len()), None, None);
|
return (cursor.at(content.len()), None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
(cursor.at(*next_pos),
|
(
|
||||||
|
cursor.at(*next_pos),
|
||||||
Some(&self.rules()[winner]),
|
Some(&self.rules()[winner]),
|
||||||
std::mem::replace(&mut matches[winner].1, None))
|
std::mem::replace(&mut matches[winner].1, None),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an [`Element`] to the [`Document`]
|
/// Add an [`Element`] to the [`Document`]
|
||||||
fn push<'a>(&self, doc: &dyn Document, elem: Box<dyn Element>);
|
fn push<'a>(&self, doc: &dyn Document, elem: Box<dyn Element>);
|
||||||
|
|
||||||
/// Parse [`Source`] into a new [`Document`]
|
/// Parse [`Source`] into a new [`Document`]
|
||||||
fn parse<'a>(&self, source: Rc<dyn Source>, parent: Option<&'a dyn Document<'a>>) -> Box<dyn Document<'a>+'a>;
|
fn parse<'a>(
|
||||||
|
&self,
|
||||||
|
source: Rc<dyn Source>,
|
||||||
|
parent: Option<&'a dyn Document<'a>>,
|
||||||
|
) -> Box<dyn Document<'a> + 'a>;
|
||||||
|
|
||||||
/// Parse [`Source`] into an already existing [`Document`]
|
/// Parse [`Source`] into an already existing [`Document`]
|
||||||
fn parse_into<'a>(&self, source: Rc<dyn Source>, document: &'a dyn Document<'a>);
|
fn parse_into<'a>(&self, source: Rc<dyn Source>, document: &'a dyn Document<'a>);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use super::parser::Parser;
|
use super::parser::Parser;
|
||||||
use super::source::{Cursor, Source, Token};
|
use super::source::Cursor;
|
||||||
use ariadne::Report;
|
use super::source::Source;
|
||||||
use mlua::{Function, Lua};
|
use super::source::Token;
|
||||||
use crate::document::document::Document;
|
use crate::document::document::Document;
|
||||||
|
use ariadne::Report;
|
||||||
|
use mlua::Function;
|
||||||
|
use mlua::Lua;
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
@ -14,13 +17,18 @@ pub trait Rule {
|
||||||
/// Finds the next match starting from [`cursor`]
|
/// Finds the next match starting from [`cursor`]
|
||||||
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)>;
|
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)>;
|
||||||
/// Callback when rule matches
|
/// Callback when rule matches
|
||||||
fn on_match<'a>(&self, parser: &dyn Parser, document: &'a (dyn Document<'a>+'a), cursor: Cursor, match_data: Option<Box<dyn Any>>) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>);
|
fn on_match<'a>(
|
||||||
|
&self,
|
||||||
|
parser: &dyn Parser,
|
||||||
|
document: &'a (dyn Document<'a> + 'a),
|
||||||
|
cursor: Cursor,
|
||||||
|
match_data: Option<Box<dyn Any>>,
|
||||||
|
) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>);
|
||||||
/// Export bindings to lua
|
/// Export bindings to lua
|
||||||
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)>;
|
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for dyn Rule
|
impl core::fmt::Debug for dyn Rule {
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "Rule{{{}}}", self.name())
|
write!(f, "Rule{{{}}}", self.name())
|
||||||
}
|
}
|
||||||
|
@ -65,53 +73,78 @@ impl<T: RegexRule> Rule for T {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub trait RegexRule
|
pub trait RegexRule {
|
||||||
{
|
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
/// Returns the rule's regexes
|
/// Returns the rule's regexes
|
||||||
fn regexes(&self) -> &[regex::Regex];
|
fn regexes(&self) -> &[regex::Regex];
|
||||||
|
|
||||||
/// Callback on regex rule match
|
/// Callback on regex rule match
|
||||||
fn on_regex_match<'a>(&self, index: usize, parser: &dyn Parser, document: &'a (dyn Document<'a>+'a), token: Token, matches: regex::Captures) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>;
|
fn on_regex_match<'a>(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
parser: &dyn Parser,
|
||||||
|
document: &'a (dyn Document<'a> + 'a),
|
||||||
|
token: Token,
|
||||||
|
matches: regex::Captures,
|
||||||
|
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>;
|
||||||
|
|
||||||
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)>;
|
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: RegexRule> Rule for T {
|
impl<T: RegexRule> Rule for T {
|
||||||
fn name(&self) -> &'static str { RegexRule::name(self) }
|
fn name(&self) -> &'static str {
|
||||||
|
RegexRule::name(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Finds the next match starting from [`cursor`]
|
/// Finds the next match starting from [`cursor`]
|
||||||
fn next_match(&self, cursor: &Cursor)
|
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
|
||||||
-> Option<(usize, Box<dyn Any>)> {
|
|
||||||
let content = cursor.source.content();
|
let content = cursor.source.content();
|
||||||
let mut found: Option<(usize, usize)> = None;
|
let mut found: Option<(usize, usize)> = None;
|
||||||
self.regexes().iter().enumerate().for_each(|(id, re)| {
|
self.regexes().iter().enumerate().for_each(|(id, re)| {
|
||||||
if let Some(m) = re.find_at(content.as_str(), cursor.pos)
|
if let Some(m) = re.find_at(content.as_str(), cursor.pos) {
|
||||||
{
|
|
||||||
found = found
|
found = found
|
||||||
.and_then(|(f_pos, f_id)|
|
.and_then(|(f_pos, f_id)| {
|
||||||
if f_pos > m.start() { Some((m.start(), id)) } else { Some((f_pos, f_id)) } )
|
if f_pos > m.start() {
|
||||||
|
Some((m.start(), id))
|
||||||
|
} else {
|
||||||
|
Some((f_pos, f_id))
|
||||||
|
}
|
||||||
|
})
|
||||||
.or(Some((m.start(), id)));
|
.or(Some((m.start(), id)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return found.map(|(pos, id)|
|
return found.map(|(pos, id)| (pos, Box::new(id) as Box<dyn Any>));
|
||||||
(pos, Box::new(id) as Box<dyn Any>));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_match<'a>(&self, parser: &dyn Parser, document: &'a (dyn Document<'a>+'a), cursor: Cursor, match_data: Option<Box<dyn Any>>)
|
fn on_match<'a>(
|
||||||
-> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
|
&self,
|
||||||
|
parser: &dyn Parser,
|
||||||
|
document: &'a (dyn Document<'a> + 'a),
|
||||||
|
cursor: Cursor,
|
||||||
|
match_data: Option<Box<dyn Any>>,
|
||||||
|
) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
|
||||||
let content = cursor.source.content();
|
let content = cursor.source.content();
|
||||||
let index = unsafe { match_data.unwrap_unchecked().downcast::<usize>().unwrap_unchecked() };
|
let index = unsafe {
|
||||||
|
match_data
|
||||||
|
.unwrap_unchecked()
|
||||||
|
.downcast::<usize>()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
};
|
||||||
let re = &self.regexes()[*index];
|
let re = &self.regexes()[*index];
|
||||||
|
|
||||||
let captures = re.captures_at(content.as_str(), cursor.pos).unwrap();
|
let captures = re.captures_at(content.as_str(), cursor.pos).unwrap();
|
||||||
let token = Token::new(captures.get(0).unwrap().range(), cursor.source.clone());
|
let token = Token::new(captures.get(0).unwrap().range(), cursor.source.clone());
|
||||||
|
|
||||||
let token_end = token.end();
|
let token_end = token.end();
|
||||||
return (cursor.at(token_end), self.on_regex_match(*index, parser, document, token, captures));
|
return (
|
||||||
|
cursor.at(token_end),
|
||||||
|
self.on_regex_match(*index, parser, document, token, captures),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { self.lua_bindings(lua) }
|
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
|
||||||
|
self.lua_bindings(lua)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use std::{fs, ops::Range, rc::Rc};
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
use std::fs;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use downcast_rs::{impl_downcast, Downcast};
|
use downcast_rs::impl_downcast;
|
||||||
use serde::{Deserialize, Serialize};
|
use downcast_rs::Downcast;
|
||||||
|
|
||||||
/// Trait for source content
|
/// Trait for source content
|
||||||
pub trait Source: Downcast
|
pub trait Source: Downcast {
|
||||||
{
|
|
||||||
/// Gets the source's location
|
/// Gets the source's location
|
||||||
fn location(&self) -> Option<&Token>;
|
fn location(&self) -> Option<&Token>;
|
||||||
/// Gets the source's name
|
/// Gets the source's name
|
||||||
|
@ -16,23 +17,20 @@ pub trait Source: Downcast
|
||||||
}
|
}
|
||||||
impl_downcast!(Source);
|
impl_downcast!(Source);
|
||||||
|
|
||||||
impl core::fmt::Display for dyn Source
|
impl core::fmt::Display for dyn Source {
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.name())
|
write!(f, "{}", self.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for dyn Source
|
impl core::fmt::Debug for dyn Source {
|
||||||
{
|
|
||||||
// TODO
|
// TODO
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "Source{{{}}}", self.name())
|
write!(f, "Source{{{}}}", self.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::PartialEq for dyn Source
|
impl std::cmp::PartialEq for dyn Source {
|
||||||
{
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.name() == other.name()
|
self.name() == other.name()
|
||||||
}
|
}
|
||||||
|
@ -40,30 +38,29 @@ impl std::cmp::PartialEq for dyn Source
|
||||||
|
|
||||||
impl std::cmp::Eq for dyn Source {}
|
impl std::cmp::Eq for dyn Source {}
|
||||||
|
|
||||||
impl std::hash::Hash for dyn Source
|
impl std::hash::Hash for dyn Source {
|
||||||
{
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.name().hash(state)
|
self.name().hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SourceFile
|
pub struct SourceFile {
|
||||||
{
|
|
||||||
location: Option<Token>,
|
location: Option<Token>,
|
||||||
path: String,
|
path: String,
|
||||||
content: String,
|
content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SourceFile {
|
||||||
impl SourceFile
|
|
||||||
{
|
|
||||||
// TODO: Create a SourceFileRegistry holding already loaded files to avoid reloading them
|
// TODO: Create a SourceFileRegistry holding already loaded files to avoid reloading them
|
||||||
pub fn new(path: String, location: Option<Token>) -> Result<Self, String>
|
pub fn new(path: String, location: Option<Token>) -> Result<Self, String> {
|
||||||
{
|
match fs::read_to_string(&path) {
|
||||||
match fs::read_to_string(&path)
|
Err(_) => {
|
||||||
{
|
return Err(String::from(format!(
|
||||||
Err(_) => return Err(String::from(format!("Unable to read file content: `{}`", path))),
|
"Unable to read file content: `{}`",
|
||||||
Ok(content) => Ok(Self{
|
path
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
Ok(content) => Ok(Self {
|
||||||
location,
|
location,
|
||||||
path,
|
path,
|
||||||
content,
|
content,
|
||||||
|
@ -71,8 +68,7 @@ impl SourceFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_content(path: String, content: String, location: Option<Token>) -> Self
|
pub fn with_content(path: String, content: String, location: Option<Token>) -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
location: location,
|
location: location,
|
||||||
path: path,
|
path: path,
|
||||||
|
@ -81,38 +77,48 @@ impl SourceFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source for SourceFile
|
impl Source for SourceFile {
|
||||||
{
|
fn location(&self) -> Option<&Token> {
|
||||||
fn location(&self) -> Option<&Token> { self.location.as_ref() }
|
self.location.as_ref()
|
||||||
fn name(&self) -> &String { &self.path }
|
}
|
||||||
fn content(&self) -> &String { &self.content }
|
fn name(&self) -> &String {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
fn content(&self) -> &String {
|
||||||
|
&self.content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VirtualSource
|
pub struct VirtualSource {
|
||||||
{
|
|
||||||
location: Token,
|
location: Token,
|
||||||
name: String,
|
name: String,
|
||||||
content: String,
|
content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualSource
|
impl VirtualSource {
|
||||||
{
|
pub fn new(location: Token, name: String, content: String) -> Self {
|
||||||
pub fn new(location: Token, name: String, content: String) -> Self
|
Self {
|
||||||
{
|
location,
|
||||||
Self { location, name, content }
|
name,
|
||||||
|
content,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source for VirtualSource
|
impl Source for VirtualSource {
|
||||||
{
|
fn location(&self) -> Option<&Token> {
|
||||||
fn location(&self) -> Option<&Token> { Some(&self.location) }
|
Some(&self.location)
|
||||||
fn name(&self) -> &String { &self.name }
|
}
|
||||||
fn content(&self) -> &String { &self.content }
|
fn name(&self) -> &String {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
fn content(&self) -> &String {
|
||||||
|
&self.content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cursor
|
pub struct Cursor {
|
||||||
{
|
|
||||||
pub pos: usize,
|
pub pos: usize,
|
||||||
pub source: Rc<dyn Source>,
|
pub source: Rc<dyn Source>,
|
||||||
}
|
}
|
||||||
|
@ -123,8 +129,7 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates [`cursor`] at [`new_pos`] in the same [`file`]
|
/// Creates [`cursor`] at [`new_pos`] in the same [`file`]
|
||||||
pub fn at(&self, new_pos: usize) -> Self
|
pub fn at(&self, new_pos: usize) -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
pos: new_pos,
|
pos: new_pos,
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
|
@ -132,8 +137,7 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Cursor
|
impl Clone for Cursor {
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pos: self.pos,
|
pos: self.pos,
|
||||||
|
@ -147,41 +151,35 @@ impl Clone for Cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Token
|
pub struct Token {
|
||||||
{
|
|
||||||
pub range: Range<usize>,
|
pub range: Range<usize>,
|
||||||
source: Rc<dyn Source>,
|
source: Rc<dyn Source>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Token
|
impl Token {
|
||||||
{
|
|
||||||
pub fn new(range: Range<usize>, source: Rc<dyn Source>) -> Self {
|
pub fn new(range: Range<usize>, source: Rc<dyn Source>) -> Self {
|
||||||
Self { range, source }
|
Self { range, source }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source(&self) -> Rc<dyn Source>
|
pub fn source(&self) -> Rc<dyn Source> {
|
||||||
{
|
return self.source.clone();
|
||||||
return self.source.clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct Token from a range
|
/// Construct Token from a range
|
||||||
pub fn from(start: &Cursor, end: &Cursor) -> Self
|
pub fn from(start: &Cursor, end: &Cursor) -> Self {
|
||||||
{
|
|
||||||
assert!(Rc::ptr_eq(&start.source, &end.source));
|
assert!(Rc::ptr_eq(&start.source, &end.source));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
range: start.pos .. end.pos,
|
range: start.pos..end.pos,
|
||||||
source: start.source.clone()
|
source: start.source.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&self) -> usize
|
pub fn start(&self) -> usize {
|
||||||
{
|
|
||||||
return self.range.start;
|
return self.range.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end(&self) -> usize
|
pub fn end(&self) -> usize {
|
||||||
{
|
|
||||||
return self.range.end;
|
return self.range.end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
use std::{cell::RefCell, collections::HashMap, ops::Range, rc::Rc};
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use ariadne::Report;
|
use ariadne::Report;
|
||||||
use downcast_rs::{impl_downcast, Downcast};
|
use downcast_rs::impl_downcast;
|
||||||
|
use downcast_rs::Downcast;
|
||||||
|
|
||||||
use crate::document::document::Document;
|
use crate::document::document::Document;
|
||||||
|
|
||||||
use super::{parser::Parser, source::Source};
|
use super::parser::Parser;
|
||||||
|
use super::source::Source;
|
||||||
|
|
||||||
/// Scope for state objects
|
/// Scope for state objects
|
||||||
#[derive(PartialEq, PartialOrd, Debug)]
|
#[derive(PartialEq, PartialOrd, Debug)]
|
||||||
pub enum Scope
|
pub enum Scope {
|
||||||
{
|
|
||||||
/// Global state
|
/// Global state
|
||||||
GLOBAL = 0,
|
GLOBAL = 0,
|
||||||
/// Document-local state
|
/// Document-local state
|
||||||
|
@ -21,18 +25,20 @@ pub enum Scope
|
||||||
PARAGRAPH = 2,
|
PARAGRAPH = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait State: Downcast
|
pub trait State: Downcast {
|
||||||
{
|
|
||||||
/// Returns the state's [`Scope`]
|
/// Returns the state's [`Scope`]
|
||||||
fn scope(&self) -> Scope;
|
fn scope(&self) -> Scope;
|
||||||
|
|
||||||
/// Callback called when state goes out of scope
|
/// Callback called when state goes out of scope
|
||||||
fn on_remove<'a>(&self, parser: &dyn Parser, document: &dyn Document) -> Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>>;
|
fn on_remove<'a>(
|
||||||
|
&self,
|
||||||
|
parser: &dyn Parser,
|
||||||
|
document: &dyn Document,
|
||||||
|
) -> Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>>;
|
||||||
}
|
}
|
||||||
impl_downcast!(State);
|
impl_downcast!(State);
|
||||||
|
|
||||||
impl core::fmt::Debug for dyn State
|
impl core::fmt::Debug for dyn State {
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "State{{Scope: {:#?}}}", self.scope())
|
write!(f, "State{{Scope: {:#?}}}", self.scope())
|
||||||
}
|
}
|
||||||
|
@ -40,13 +46,11 @@ impl core::fmt::Debug for dyn State
|
||||||
|
|
||||||
/// Object owning all the states
|
/// Object owning all the states
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StateHolder
|
pub struct StateHolder {
|
||||||
{
|
data: HashMap<String, Rc<RefCell<dyn State>>>,
|
||||||
data: HashMap<String, Rc<RefCell<dyn State>>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateHolder
|
impl StateHolder {
|
||||||
{
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: HashMap::new(),
|
data: HashMap::new(),
|
||||||
|
@ -54,38 +58,38 @@ impl StateHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempts to push [`state`]. On collision, returns an error with the already present state
|
// Attempts to push [`state`]. On collision, returns an error with the already present state
|
||||||
pub fn insert(&mut self, name: String, state: Rc<RefCell<dyn State>>) -> Result<Rc<RefCell<dyn State>>, Rc<RefCell<dyn State>>>
|
pub fn insert(
|
||||||
{
|
&mut self,
|
||||||
match self.data.insert(name, state.clone())
|
name: String,
|
||||||
{
|
state: Rc<RefCell<dyn State>>,
|
||||||
|
) -> Result<Rc<RefCell<dyn State>>, Rc<RefCell<dyn State>>> {
|
||||||
|
match self.data.insert(name, state.clone()) {
|
||||||
Some(state) => Err(state),
|
Some(state) => Err(state),
|
||||||
_ => Ok(state)
|
_ => Ok(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query(&self, name: &String) -> Option<Rc<RefCell<dyn State>>>
|
pub fn query(&self, name: &String) -> Option<Rc<RefCell<dyn State>>> {
|
||||||
{
|
self.data.get(name).map_or(None, |st| Some(st.clone()))
|
||||||
self.data
|
|
||||||
.get(name)
|
|
||||||
.map_or(None, |st| Some(st.clone()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_scope_end(&mut self, parser: &dyn Parser, document: &dyn Document, scope: Scope) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>
|
pub fn on_scope_end(
|
||||||
{
|
&mut self,
|
||||||
|
parser: &dyn Parser,
|
||||||
|
document: &dyn Document,
|
||||||
|
scope: Scope,
|
||||||
|
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
|
|
||||||
self.data
|
self.data.retain(|_name, state| {
|
||||||
.retain(|_name, state|
|
if state.borrow().scope() >= scope {
|
||||||
{
|
state
|
||||||
if state.borrow().scope() >= scope
|
.borrow()
|
||||||
{
|
.on_remove(parser, document)
|
||||||
state.borrow().on_remove(parser, document)
|
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.for_each(|report| result.push(report));
|
.for_each(|report| result.push(report));
|
||||||
false
|
false
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,13 +2,10 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{
|
use crate::document::document::Document;
|
||||||
document::{
|
use crate::document::document::DocumentAccessors;
|
||||||
document::{Document, DocumentAccessors},
|
use crate::document::element::ElemKind;
|
||||||
element::ElemKind,
|
use crate::elements::paragraph::Paragraph;
|
||||||
},
|
|
||||||
elements::paragraph::Paragraph,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Processes text for escape characters and paragraphing
|
/// Processes text for escape characters and paragraphing
|
||||||
pub fn process_text(document: &dyn Document, content: &str) -> String {
|
pub fn process_text(document: &dyn Document, content: &str) -> String {
|
||||||
|
@ -360,11 +357,12 @@ impl PropertyParser {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::document::langdocument::LangDocument;
|
||||||
document::langdocument::LangDocument,
|
use crate::elements::comment::Comment;
|
||||||
elements::{comment::Comment, style::Style, text::Text},
|
use crate::elements::style::Style;
|
||||||
parser::source::{SourceFile, Token},
|
use crate::elements::text::Text;
|
||||||
};
|
use crate::parser::source::SourceFile;
|
||||||
|
use crate::parser::source::Token;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -387,6 +385,13 @@ mod tests {
|
||||||
let tok = Token::new(0..0, source);
|
let tok = Token::new(0..0, source);
|
||||||
doc.push(Box::new(Paragraph::new(tok.clone())));
|
doc.push(Box::new(Paragraph::new(tok.clone())));
|
||||||
|
|
||||||
|
// Comments are ignored (kind => Invisible)
|
||||||
|
(&doc as &dyn Document)
|
||||||
|
.last_element_mut::<Paragraph>()
|
||||||
|
.unwrap()
|
||||||
|
.push(Box::new(Comment::new(tok.clone(), "COMMENT".to_string())));
|
||||||
|
assert_eq!(process_text(&doc, "\na"), "a");
|
||||||
|
|
||||||
// A space is appended as previous element is inline
|
// A space is appended as previous element is inline
|
||||||
(&doc as &dyn Document)
|
(&doc as &dyn Document)
|
||||||
.last_element_mut::<Paragraph>()
|
.last_element_mut::<Paragraph>()
|
||||||
|
@ -399,13 +404,6 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push(Box::new(Style::new(tok.clone(), 0, false)));
|
.push(Box::new(Style::new(tok.clone(), 0, false)));
|
||||||
assert_eq!(process_text(&doc, "\na"), " a");
|
assert_eq!(process_text(&doc, "\na"), " a");
|
||||||
|
|
||||||
// Comments are ignored (kind => Invisible)
|
|
||||||
(&doc as &dyn Document)
|
|
||||||
.last_element_mut::<Paragraph>()
|
|
||||||
.unwrap()
|
|
||||||
.push(Box::new(Comment::new(tok.clone(), "COMMENT".to_string())));
|
|
||||||
assert_eq!(process_text(&doc, "\na"), " a");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue