Compare commits

..

No commits in common. "08ae603106743119c511d7be7a3e79f78b5de7c2" and "7a2c19af66c45911353133b096f08f094e308670" have entirely different histories.

28 changed files with 318 additions and 513 deletions

View file

@ -1,32 +0,0 @@
@import ../template.nml
%<make_doc({"External Tools"}, "Graphviz", "Graphvis")>%
# Graphs from graphviz
[graph]
digraph {
bgcolor=transparent;
filelist [color=green, label="File List"];
doclist [color=green, label="Document List"];
iscached [shape=diamond, color=red, label="Cached?"];
parse [color=white, label=Parse];
compile [color=white, label=Compile];
cache [shape=box, color=blue, label=Cache];
edge [color=gray]
filelist -> iscached;
iscached -> cache[dir=both];
iscached -> doclist[label="+"];
iscached -> parse[label="No"];
parse -> compile;
compile -> cache[label="+"];
compile -> doclist[label="+"];
buildnav [color=white, label="Build Navigation"];
doclist -> buildnav;
output [color=white, label="Output"];
buildnav -> output;
}
[/graph]

View file

@ -1,5 +1,8 @@
@import ../template.nml @import ../template.nml
%<make_doc({"External Tools"}, "LaTeX", "LaTeX")>% @compiler.output = latex.html
@nav.title = LaTeX
@nav.category = External Tools
@html.page_title = Documentation | LaTeX
@LaTeX = $|[kind=inline, caption=LaTeX]\LaTeX|$ @LaTeX = $|[kind=inline, caption=LaTeX]\LaTeX|$

View file

@ -1,4 +1,6 @@
@import template.nml @import template.nml
%<make_doc({}, "Index", "Index")>% @compiler.output = index.html
@nav.title = Documentation
@html.page_title = Documentation | Index
# Welcome to the NML documentation! # Welcome to the NML documentation!

View file

@ -1,5 +1,8 @@
@import ../template.nml @import ../template.nml
%<make_doc({"Lua"}, "Lua", "Lua Basics")>% @compiler.output = lua.html
@nav.title = Lua
@nav.category = Lua
@html.page_title = Documentation | Lua
# Running lua code # Running lua code

View file

@ -1,5 +1,8 @@
@import ../template.nml @import ../template.nml
%<make_doc({"Styles"}, "Basic", "Basic Styles")>% @compiler.output = basic.html
@nav.title = Basic
@nav.category = Styles
@html.page_title = Documentation | Basic Styles
# Basic styles # Basic styles
## Bold ## Bold

View file

@ -1,4 +1,7 @@
@import ../template.nml @import ../template.nml
%<make_doc({"Styles"}, "User-Defined", "User-Defined Styles")>% @compiler.output = user-defined.html
@nav.title = User-Defined
@nav.category = Styles
@html.page_title = Documentation | User-Defined Styles
# TODO # TODO

View file

@ -6,22 +6,3 @@
\definecolor{__color1}{HTML}{d5d5d5} \\ \definecolor{__color1}{HTML}{d5d5d5} \\
\everymath{\color{__color1}\displaystyle} \everymath{\color{__color1}\displaystyle}
@tex.main.block_prepend = \color{__color1} @tex.main.block_prepend = \color{__color1}
@<
function make_doc(categories, title, page_title)
-- Navigation
nml.variable.insert("nav.title", title)
if categories[1] ~= nil
then
nml.variable.insert("nav.category", categories[1])
if categories[2] ~= nil
then
nml.variable.insert("nav.subcategory", categories[2])
end
end
-- HTML
nml.variable.insert("html.page_title", "NML | " .. page_title)
nml.variable.insert("compiler.output", page_title .. ".html")
end
>@

View file

@ -38,24 +38,6 @@ impl Compiler {
} }
} }
/// Sanitizes text for a [`Target`]
pub fn sanitize<S: AsRef<str>>(target: Target, str: S) -> String {
match target {
Target::HTML => str
.as_ref()
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;"),
_ => todo!("Sanitize not implemented"),
}
}
/// Gets a reference name
pub fn refname<S: AsRef<str>>(target: Target, str: S) -> String {
Self::sanitize(target, str).replace(' ', "_")
}
/// Inserts or get a reference id for the compiled document /// Inserts or get a reference id for the compiled document
/// ///
/// # Parameters /// # Parameters
@ -92,6 +74,18 @@ impl Compiler {
self.cache.as_ref().map(RefCell::borrow_mut) self.cache.as_ref().map(RefCell::borrow_mut)
} }
pub fn sanitize<S: AsRef<str>>(target: Target, str: S) -> String {
match target {
Target::HTML => str
.as_ref()
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;"),
_ => todo!("Sanitize not implemented"),
}
}
pub fn header(&self, document: &dyn Document) -> String { pub fn header(&self, document: &dyn Document) -> String {
pub fn get_variable_or_error( pub fn get_variable_or_error(
document: &dyn Document, document: &dyn Document,
@ -115,11 +109,8 @@ impl Compiler {
result += "<!DOCTYPE HTML><html><head>"; result += "<!DOCTYPE HTML><html><head>";
result += "<meta charset=\"UTF-8\">"; result += "<meta charset=\"UTF-8\">";
if let Some(page_title) = get_variable_or_error(document, "html.page_title") { if let Some(page_title) = get_variable_or_error(document, "html.page_title") {
result += format!( result += format!("<title>{}</title>", Compiler::sanitize(self.target(), page_title.to_string()))
"<title>{}</title>", .as_str();
Compiler::sanitize(self.target(), page_title.to_string())
)
.as_str();
} }
if let Some(css) = document.get_variable("html.css") { if let Some(css) = document.get_variable("html.css") {
@ -129,7 +120,7 @@ impl Compiler {
) )
.as_str(); .as_str();
} }
result += r#"</head><body><div class="layout">"#; result += r#"</head><body><div id="layout">"#;
// TODO: TOC // TODO: TOC
// TODO: Author, Date, Title, Div // TODO: Author, Date, Title, Div
@ -157,7 +148,7 @@ impl Compiler {
let header = self.header(document); let header = self.header(document);
// Body // Body
let mut body = r#"<div class="content">"#.to_string(); let mut body = r#"<div id="content">"#.to_string();
for i in 0..borrow.len() { for i in 0..borrow.len() {
let elem = &borrow[i]; let elem = &borrow[i];

View file

@ -23,7 +23,7 @@ impl NavEntry {
let mut result = String::new(); let mut result = String::new();
match target { match target {
Target::HTML => { Target::HTML => {
result += r#"<div class="navbar"><ul>"#; result += r#"<div id="navbar"><ul>"#;
fn process( fn process(
target: Target, target: Target,

View file

@ -544,7 +544,8 @@ impl RegexRule for CodeRule {
reports reports
} }
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { // TODO
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
let mut bindings = vec![]; let mut bindings = vec![];
bindings.push(( bindings.push((
"push_inline".to_string(), "push_inline".to_string(),
@ -646,7 +647,7 @@ impl RegexRule for CodeRule {
.unwrap(), .unwrap(),
)); ));
Some(bindings) bindings
} }
} }

View file

@ -80,5 +80,5 @@ impl RegexRule for CommentRule {
return reports; return reports;
} }
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -371,5 +371,5 @@ impl RegexRule for GraphRule {
} }
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -178,5 +178,5 @@ impl RegexRule for ImportRule {
return result; return result;
} }
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -171,5 +171,5 @@ impl RegexRule for LinkRule {
} }
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -337,5 +337,5 @@ impl Rule for ListRule
} }
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -7,8 +7,6 @@ use ariadne::Fmt;
use ariadne::Label; use ariadne::Label;
use ariadne::Report; use ariadne::Report;
use ariadne::ReportKind; use ariadne::ReportKind;
use mlua::Function;
use mlua::Lua;
use regex::Captures; use regex::Captures;
use regex::Match; use regex::Match;
use regex::Regex; use regex::Regex;
@ -520,7 +518,9 @@ impl RegexRule for MediaRule {
reports reports
} }
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua mlua::Lua) -> Vec<(String, mlua::Function<'lua>)> {
vec![]
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -150,5 +150,5 @@ impl Rule for ParagraphRule {
} }
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -231,7 +231,7 @@ impl RegexRule for RawRule {
reports reports
} }
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
let mut bindings = vec![]; let mut bindings = vec![];
bindings.push(( bindings.push((
@ -270,7 +270,7 @@ impl RegexRule for RawRule {
.unwrap(), .unwrap(),
)); ));
Some(bindings) bindings
} }
} }

View file

@ -6,8 +6,6 @@ use ariadne::Fmt;
use ariadne::Label; use ariadne::Label;
use ariadne::Report; use ariadne::Report;
use ariadne::ReportKind; use ariadne::ReportKind;
use mlua::Function;
use mlua::Lua;
use regex::Captures; use regex::Captures;
use regex::Match; use regex::Match;
use regex::Regex; use regex::Regex;
@ -207,5 +205,7 @@ impl RegexRule for ReferenceRule {
reports reports
} }
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua mlua::Lua) -> Vec<(String, mlua::Function<'lua>)> {
vec![]
}
} }

View file

@ -275,5 +275,5 @@ impl RegexRule for ScriptRule {
} }
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -38,14 +38,11 @@ impl Element for Section {
fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) } fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(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 => Ok(format!(
Ok(format!( "<h{0}>{1}</h{0}>",
r#"<h{0} id="{1}">{2}</h{0}>"#,
self.depth, self.depth,
Compiler::refname(compiler.target(), self.title.as_str()),
Compiler::sanitize(compiler.target(), self.title.as_str()) Compiler::sanitize(compiler.target(), self.title.as_str())
)) )),
},
Target::LATEX => Err("Unimplemented compiler".to_string()), Target::LATEX => Err("Unimplemented compiler".to_string()),
} }
} }
@ -59,27 +56,11 @@ impl ReferenceableElement for Section {
fn compile_reference( fn compile_reference(
&self, &self,
compiler: &Compiler, compiler: &Compiler,
_document: &dyn Document, document: &dyn Document,
reference: &super::reference::Reference, reference: &super::reference::Reference,
_refid: usize, refid: usize,
) -> Result<String, String> { ) -> Result<String, String> {
match compiler.target() { todo!()
Target::HTML => {
let caption = reference.caption().map_or(
format!(
"({})",
Compiler::sanitize(compiler.target(), self.title.as_str())
),
|cap| cap.clone(),
);
Ok(format!(
"<a class=\"section-ref\" href=\"#{}\">{caption}</a>",
Compiler::refname(compiler.target(), self.title.as_str())
))
}
_ => todo!(""),
}
} }
} }
@ -254,7 +235,7 @@ impl RegexRule for SectionRule {
return result; return result;
} }
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
let mut bindings = vec![]; let mut bindings = vec![];
bindings.push(( bindings.push((
@ -299,6 +280,6 @@ impl RegexRule for SectionRule {
.unwrap(), .unwrap(),
)); ));
Some(bindings) bindings
} }
} }

View file

@ -183,5 +183,5 @@ impl RegexRule for StyleRule
} }
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -430,7 +430,7 @@ impl RegexRule for TexRule {
} }
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }
#[cfg(test)] #[cfg(test)]

View file

@ -61,7 +61,7 @@ impl Rule for TextRule {
panic!("Text cannot match"); panic!("Text cannot match");
} }
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
let mut bindings = vec![]; let mut bindings = vec![];
bindings.push(( bindings.push((
"push".to_string(), "push".to_string(),
@ -83,6 +83,6 @@ impl Rule for TextRule {
.unwrap(), .unwrap(),
)); ));
Some(bindings) bindings
} }
} }

View file

@ -1,109 +1,78 @@
use crate::document::document::Document; use mlua::{Function, Lua};
use crate::document::variable::BaseVariable;
use crate::document::variable::PathVariable;
use crate::document::variable::Variable;
use crate::lua::kernel::CTX;
use crate::parser::parser::Parser;
use crate::parser::parser::ReportColors;
use crate::parser::rule::RegexRule;
use crate::parser::source::Source;
use crate::parser::source::Token;
use ariadne::Fmt;
use ariadne::Label;
use ariadne::Report;
use ariadne::ReportKind;
use mlua::Function;
use mlua::Lua;
use regex::Regex; use regex::Regex;
use std::ops::Range; use crate::{document::document::Document, parser::{parser::{Parser, ReportColors}, rule::RegexRule, source::{Source, Token}}};
use std::rc::Rc; use ariadne::{Report, Fmt, Label, ReportKind};
use std::str::FromStr; use crate::document::variable::{BaseVariable, PathVariable, Variable};
use std::{ops::Range, rc::Rc};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum VariableKind {
Regular,
Path,
}
impl FromStr for VariableKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"regular" | "" => Ok(VariableKind::Regular),
"path" | "'" => Ok(VariableKind::Path),
_ => Err(format!("Uknnown variable kind: `{s}`")),
}
}
}
pub struct VariableRule { pub struct VariableRule {
re: [Regex; 1], re: [Regex; 1],
kinds: Vec<(String, String)>, kinds: Vec<(String, String)>,
} }
impl VariableRule { impl VariableRule {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
re: [Regex::new(r"(?:^|\n)@([^[:alpha:]])?(.*?)=((?:\\\n|.)*)").unwrap()], re: [Regex::new(r"(?:^|\n)@([^[:alpha:]])?(.*?)=((?:\\\n|.)*)").unwrap()],
kinds: vec![("".into(), "Regular".into()), ("'".into(), "Path".into())], kinds: vec![
} ("".into(), "Regular".into()),
("'".into(), "Path".into())
]
}
} }
pub fn make_variable( pub fn make_variable(&self, colors: &ReportColors, location: Token, kind: usize, name: String, value: String) -> Result<Rc<dyn Variable>, String>
&self, {
colors: &ReportColors, match self.kinds[kind].0.as_str()
location: Token, {
kind: usize, "" => {
name: String, Ok(Rc::new(BaseVariable::new(location, name, value)))
value: String, }
) -> Result<Rc<dyn Variable>, String> { "'" => {
match self.kinds[kind].0.as_str() { match std::fs::canonicalize(value.as_str()) // TODO: not canonicalize
"" => Ok(Rc::new(BaseVariable::new(location, name, value))),
"'" => {
match std::fs::canonicalize(value.as_str()) // TODO: not canonicalize
{ {
Ok(path) => Ok(Rc::new(PathVariable::new(location, name, path))), Ok(path) => Ok(Rc::new(PathVariable::new(location, name, path))),
Err(e) => Err(format!("Unable to canonicalize path `{}`: {}", Err(e) => Err(format!("Unable to canonicalize path `{}`: {}",
value.fg(colors.highlight), value.fg(colors.highlight),
e.to_string())) e.to_string()))
} }
} }
_ => panic!("Unhandled variable kind"), _ => panic!("Unhandled variable kind")
} }
} }
// Trim and check variable name for validity // Trim and check variable name for validity
pub fn validate_name<'a>( pub fn validate_name<'a>(colors: &ReportColors, original_name: &'a str) -> Result<&'a str, String>
colors: &ReportColors, {
original_name: &'a str, let name = original_name.trim_start().trim_end();
) -> Result<&'a str, String> { if name.contains("%")
let name = original_name.trim_start().trim_end(); {
if name.contains("%") { return Err(format!("Name cannot contain '{}'",
return Err(format!("Name cannot contain '{}'", "%".fg(colors.info))); "%".fg(colors.info)));
} }
return Ok(name); return Ok(name);
} }
pub fn validate_value(_colors: &ReportColors, original_value: &str) -> Result<String, String> { pub fn validate_value(_colors: &ReportColors, original_value: &str) -> Result<String, String>
{
let mut escaped = 0usize; let mut escaped = 0usize;
let mut result = String::new(); let mut result = String::new();
for c in original_value.trim_start().trim_end().chars() { for c in original_value.trim_start().trim_end().chars() {
if c == '\\' { if c == '\\' { escaped += 1 }
escaped += 1 else if c == '\n' {
} else if c == '\n' {
match escaped { match escaped {
0 => return Err("Unknown error wile capturing variable".to_string()), 0 => return Err("Unknown error wile capturing variable".to_string()),
// Remove '\n' // Remove '\n'
1 => {} 1 => {},
// Insert '\n' // Insert '\n'
_ => { _ => {
result.push(c); result.push(c);
(0..escaped - 2).for_each(|_| result.push('\\')); (0..escaped-2).for_each(|_| result.push('\\'));
} }
} }
escaped = 0; escaped = 0;
} else { }
else {
(0..escaped).for_each(|_| result.push('\\')); (0..escaped).for_each(|_| result.push('\\'));
escaped = 0; escaped = 0;
result.push(c); result.push(c);
@ -112,7 +81,7 @@ impl VariableRule {
(0..escaped).for_each(|_| result.push('\\')); (0..escaped).for_each(|_| result.push('\\'));
Ok(result) Ok(result)
} }
} }
impl RegexRule for VariableRule { impl RegexRule for VariableRule {
@ -120,306 +89,233 @@ impl RegexRule for VariableRule {
fn regexes(&self) -> &[Regex] { &self.re } fn regexes(&self) -> &[Regex] { &self.re }
fn on_regex_match<'a>( fn on_regex_match<'a>(&self, _: usize, parser: &dyn Parser, document: &'a dyn Document, token: Token, matches: regex::Captures) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>
&self, {
_: usize,
parser: &dyn Parser,
document: &'a dyn Document,
token: Token,
matches: regex::Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
let mut result = vec![]; let mut result = vec![];
// [Optional] variable kind // [Optional] variable kind
let var_kind = match matches.get(1) { let var_kind = match matches.get(1)
{
Some(kind) => { Some(kind) => {
// Find kind // Find kind
let r = self let r = self.kinds.iter().enumerate().find(|(_i, (ref char, ref _name))| {
.kinds char == kind.as_str() });
.iter()
.enumerate()
.find(|(_i, (ref char, ref _name))| char == kind.as_str());
// Unknown kind specified // Unknown kind specified
if r.is_none() { if r.is_none()
result.push( {
Report::build(ReportKind::Error, token.source(), kind.start()) result.push(
.with_message("Unknown variable kind") Report::build(ReportKind::Error, token.source(), kind.start())
.with_label( .with_message("Unknown variable kind")
Label::new((token.source(), kind.range())) .with_label(
.with_message(format!( Label::new((token.source(), kind.range()))
"Variable kind `{}` is unknown", .with_message(format!("Variable kind `{}` is unknown",
kind.as_str().fg(parser.colors().highlight) kind.as_str().fg(parser.colors().highlight)))
)) .with_color(parser.colors().error))
.with_color(parser.colors().error), .with_help(format!("Leave empty for regular variables. Available variable kinds:{}",
) self.kinds.iter().skip(1).fold("".to_string(), |acc, (char, name)| {
.with_help(format!( acc + format!("\n - `{}` : {}",
"Leave empty for regular variables. Available variable kinds:{}", char.fg(parser.colors().highlight),
self.kinds.iter().skip(1).fold( name.fg(parser.colors().info)).as_str()
"".to_string(), })))
|acc, (char, name)| { .finish());
acc + format!(
"\n - `{}` : {}",
char.fg(parser.colors().highlight),
name.fg(parser.colors().info)
)
.as_str()
}
)
))
.finish(),
);
return result; return result;
} }
r.unwrap().0 r.unwrap().0
} }
None => 0, None => 0,
}; };
let var_name = match matches.get(2) { let var_name = match matches.get(2)
Some(name) => match VariableRule::validate_name(&parser.colors(), name.as_str()) { {
Ok(var_name) => var_name, Some(name) => {
Err(msg) => { match VariableRule::validate_name(&parser.colors(), name.as_str())
result.push( {
Report::build(ReportKind::Error, token.source(), name.start()) Ok(var_name) => var_name,
.with_message("Invalid variable name") Err(msg) => {
.with_label( result.push(
Label::new((token.source(), name.range())) Report::build(ReportKind::Error, token.source(), name.start())
.with_message(format!( .with_message("Invalid variable name")
"Variable name `{}` is not allowed. {msg}", .with_label(
name.as_str().fg(parser.colors().highlight) Label::new((token.source(), name.range()))
)) .with_message(format!("Variable name `{}` is not allowed. {msg}",
.with_color(parser.colors().error), name.as_str().fg(parser.colors().highlight)))
) .with_color(parser.colors().error))
.finish(), .finish());
);
return result; return result;
} },
}, }
_ => panic!("Unknown variable name"), },
}; _ => panic!("Unknown variable name")
};
let var_value = match matches.get(3) { let var_value = match matches.get(3)
Some(value) => match VariableRule::validate_value(&parser.colors(), value.as_str()) { {
Ok(var_value) => var_value, Some(value) => {
Err(msg) => { match VariableRule::validate_value(&parser.colors(), value.as_str())
result.push( {
Report::build(ReportKind::Error, token.source(), value.start()) Ok(var_value) => var_value,
.with_message("Invalid variable value") Err(msg ) => {
.with_label( result.push(
Label::new((token.source(), value.range())) Report::build(ReportKind::Error, token.source(), value.start())
.with_message(format!( .with_message("Invalid variable value")
"Variable value `{}` is not allowed. {msg}", .with_label(
value.as_str().fg(parser.colors().highlight) Label::new((token.source(), value.range()))
)) .with_message(format!("Variable value `{}` is not allowed. {msg}",
.with_color(parser.colors().error), value.as_str().fg(parser.colors().highlight)))
) .with_color(parser.colors().error))
.finish(), .finish());
);
return result; return result;
} }
}, }
_ => panic!("Invalid variable value"), }
}; _ => panic!("Invalid variable value")
};
match self.make_variable( match self.make_variable(&parser.colors(), token.clone(), var_kind, var_name.to_string(), var_value)
&parser.colors(), {
token.clone(), Ok(variable) => document.add_variable(variable),
var_kind, Err(msg) => {
var_name.to_string(), let m = matches.get(0).unwrap();
var_value, result.push(
) { Report::build(ReportKind::Error, token.source(), m.start())
Ok(variable) => document.add_variable(variable), .with_message("Unable to create variable")
Err(msg) => { .with_label(
let m = matches.get(0).unwrap(); Label::new((token.source(), m.start()+1 .. m.end() ))
result.push( .with_message(format!("Unable to create variable `{}`. {}",
Report::build(ReportKind::Error, token.source(), m.start()) var_name.fg(parser.colors().highlight),
.with_message("Unable to create variable") msg))
.with_label( .with_color(parser.colors().error))
Label::new((token.source(), m.start() + 1..m.end())) .finish());
.with_message(format!(
"Unable to create variable `{}`. {}",
var_name.fg(parser.colors().highlight),
msg
))
.with_color(parser.colors().error),
)
.finish(),
);
return result; return result;
} }
} }
return result; return result;
} }
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { // TODO
let mut bindings = vec![]; fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
bindings.push((
"insert".to_string(),
lua.create_function(|_, (name, value): (String, String)| {
CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| {
let var = Rc::new(BaseVariable::new(ctx.location.clone(), name, value));
ctx.document.add_variable(var);
})
});
Ok(())
})
.unwrap(),
));
bindings.push((
"get".to_string(),
lua.create_function(|_, name: String| {
let mut value: Option<String> = None;
CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| {
if let Some(var) = ctx.document.get_variable(name.as_str())
{
value = Some(var.to_string());
}
})
});
Ok(value)
})
.unwrap(),
));
Some(bindings)
}
} }
pub struct VariableSubstitutionRule { pub struct VariableSubstitutionRule
{
re: [Regex; 1], re: [Regex; 1],
} }
impl VariableSubstitutionRule { impl VariableSubstitutionRule {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
re: [Regex::new(r"%(.*?)%").unwrap()], re: [Regex::new(r"%(.*?)%").unwrap()],
} }
} }
} }
impl RegexRule for VariableSubstitutionRule { impl RegexRule for VariableSubstitutionRule
fn name(&self) -> &'static str { "Variable Substitution" } {
fn name(&self) -> &'static str { "Variable Substitution" }
fn regexes(&self) -> &[regex::Regex] { &self.re } fn regexes(&self) -> &[regex::Regex] { &self.re }
fn on_regex_match<'a>( fn on_regex_match<'a>(&self, _index: usize, parser: &dyn Parser, document: &'a dyn Document<'a>, token: Token, matches: regex::Captures) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
&self,
_index: usize,
parser: &dyn Parser,
document: &'a dyn Document<'a>,
token: Token,
matches: regex::Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
let mut result = vec![]; let mut result = vec![];
let variable = match matches.get(1) { let variable = match matches.get(1)
Some(name) => { {
// Empty name Some(name) => {
if name.as_str().is_empty() { // Empty name
result.push( if name.as_str().is_empty()
Report::build(ReportKind::Error, token.source(), name.start()) {
.with_message("Empty variable name") result.push(
.with_label( Report::build(ReportKind::Error, token.source(), name.start())
Label::new((token.source(), matches.get(0).unwrap().range())) .with_message("Empty variable name")
.with_message(format!("Missing variable name for substitution")) .with_label(
.with_color(parser.colors().error), Label::new((token.source(), matches.get(0).unwrap().range()))
) .with_message(format!("Missing variable name for substitution"))
.finish(), .with_color(parser.colors().error))
); .finish());
return result; return result;
} }
// Leading spaces // Leading spaces
else if name.as_str().trim_start() != name.as_str() { else if name.as_str().trim_start() != name.as_str()
result.push( {
Report::build(ReportKind::Error, token.source(), name.start()) result.push(
.with_message("Invalid variable name") Report::build(ReportKind::Error, token.source(), name.start())
.with_label( .with_message("Invalid variable name")
Label::new((token.source(), name.range())) .with_label(
.with_message(format!("Variable names contains leading spaces")) Label::new((token.source(), name.range()))
.with_color(parser.colors().error), .with_message(format!("Variable names contains leading spaces"))
) .with_color(parser.colors().error))
.with_help("Remove leading spaces") .with_help("Remove leading spaces")
.finish(), .finish());
);
return result; return result;
} }
// Trailing spaces // Trailing spaces
else if name.as_str().trim_end() != name.as_str() { else if name.as_str().trim_end() != name.as_str()
result.push( {
Report::build(ReportKind::Error, token.source(), name.start()) result.push(
.with_message("Invalid variable name") Report::build(ReportKind::Error, token.source(), name.start())
.with_label( .with_message("Invalid variable name")
Label::new((token.source(), name.range())) .with_label(
.with_message(format!( Label::new((token.source(), name.range()))
"Variable names contains trailing spaces" .with_message(format!("Variable names contains trailing spaces"))
)) .with_color(parser.colors().error))
.with_color(parser.colors().error), .with_help("Remove trailing spaces")
) .finish());
.with_help("Remove trailing spaces")
.finish(),
);
return result; return result;
} }
// Invalid name // Invalid name
match VariableRule::validate_name(&parser.colors(), name.as_str()) { match VariableRule::validate_name(&parser.colors(), name.as_str())
Err(msg) => { {
Err(msg) =>
{
result.push( result.push(
Report::build(ReportKind::Error, token.source(), name.start()) Report::build(ReportKind::Error, token.source(), name.start())
.with_message("Invalid variable name") .with_message("Invalid variable name")
.with_label( .with_label(
Label::new((token.source(), name.range())) Label::new((token.source(), name.range()))
.with_message(msg) .with_message(msg)
.with_color(parser.colors().error), .with_color(parser.colors().error))
) .finish());
.finish(),
);
return result; return result;
} }
_ => {} _ => {},
} }
// Get variable // Get variable
match document.get_variable(name.as_str()) { match document.get_variable(name.as_str())
None => { {
result.push( None => {
Report::build(ReportKind::Error, token.source(), name.start()) result.push(
.with_message("Unknown variable name") Report::build(ReportKind::Error, token.source(), name.start())
.with_label( .with_message("Unknown variable name")
Label::new((token.source(), name.range())) .with_label(
.with_message(format!( Label::new((token.source(), name.range()))
"Unable to find variable with name: `{}`", .with_message(format!("Unable to find variable with name: `{}`",
name.as_str().fg(parser.colors().highlight) name.as_str().fg(parser.colors().highlight)))
)) .with_color(parser.colors().error))
.with_color(parser.colors().error), .finish());
) return result;
.finish(), }
); Some(var) => var,
return result; }
} },
Some(var) => var, _ => panic!("Unknown error")
} };
}
_ => panic!("Unknown error"),
};
variable.parse(token, parser, document); variable.parse(token, parser, document);
return result; return result;
} }
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
} }

View file

@ -34,18 +34,15 @@ impl Kernel {
for rule in parser.rules() for rule in parser.rules()
{ {
if let Some(bindings) = rule.lua_bindings(&lua) let table = lua.create_table().unwrap();
let name = rule.name().to_lowercase();
for (fun_name, fun) in rule.lua_bindings(&lua)
{ {
let table = lua.create_table().unwrap(); table.set(fun_name, fun).unwrap();
let name = rule.name().to_lowercase();
for (fun_name, fun) in bindings
{
table.set(fun_name, fun).unwrap();
}
nml_table.set(name, table).unwrap();
} }
nml_table.set(name, table).unwrap();
} }
lua.globals().set("nml", nml_table).unwrap(); lua.globals().set("nml", nml_table).unwrap();
} }

View file

@ -25,7 +25,7 @@ pub trait Rule {
match_data: Option<Box<dyn Any>>, match_data: Option<Box<dyn Any>>,
) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>); ) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>);
/// Export bindings to lua /// Export bindings to lua
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<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 {
@ -89,7 +89,7 @@ pub trait RegexRule {
matches: regex::Captures, matches: regex::Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>; ) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>;
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<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 {
@ -144,7 +144,7 @@ impl<T: RegexRule> Rule for T {
); );
} }
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
self.lua_bindings(lua) self.lua_bindings(lua)
} }
} }

View file

@ -2,19 +2,9 @@ body {
background-color: #1b1b1d; background-color: #1b1b1d;
color: #c5c5c5; color: #c5c5c5;
font-family: sans-serif; font-family: sans-serif;
margin: 0;
padding: 0;
}
.layout { max-width: 90ch;
display: flex;
}
.content {
max-width: 99ch;
margin: 0 auto; margin: 0 auto;
padding: 0;
width: 100%;
} }
/* Styles */ /* Styles */
@ -39,18 +29,13 @@ a.inline-code {
} }
/* Navbar */ /* Navbar */
.navbar { #navbar {
display: none;
left: 0; left: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
width: max(calc((100vw - 99ch) / 2 - 15vw), 24ch); width: max(16vw, 20ch);
height: 100vh;
position: fixed;
margin-right: 1em;
overflow-y: auto; overflow-y: auto;
position: absolute;
box-sizing: border-box; box-sizing: border-box;
overscroll-behavior-y: contain; overscroll-behavior-y: contain;
@ -61,53 +46,44 @@ a.inline-code {
font-weight: bold; font-weight: bold;
} }
@media (min-width: 130ch) { #navbar a {
.navbar {
display: block;
}
.container {
flex-direction: row;
}
}
.navbar a {
color: #ffb454; color: #ffb454;
text-decoration: none; text-decoration: none;
font-weight: normal; font-weight: normal;
} }
.navbar li { #navbar li {
display: block; display: block;
position: relative; position: relative;
padding-left: 1em; padding-left: 1em;
margin-left: 0em; margin-left: 0em;
} }
.navbar ul { #navbar ul {
margin-left: 0em; margin-left: 0em;
padding-left: 0; padding-left: 0;
} }
.navbar summary{ #navbar summary{
display: block; display: block;
cursor: pointer; cursor: pointer;
} }
.navbar summary::marker, #navbar summary::marker,
.navbar summary::-webkit-details-marker{ #navbar summary::-webkit-details-marker{
display: none; display: none;
} }
.navbar summary:focus{ #navbar summary:focus{
outline: none; outline: none;
} }
.navbar summary:focus-visible{ #navbar summary:focus-visible{
outline: 1px dotted #000; outline: 1px dotted #000;
} }
.navbar summary:before { #navbar summary:before {
content: "+"; content: "+";
color: #ffb454; color: #ffb454;
float: left; float: left;
@ -115,7 +91,7 @@ a.inline-code {
width: 1em; width: 1em;
} }
.navbar details[open] > summary:before { #navbar details[open] > summary:before {
content: ""; content: "";
} }