Compare commits

..

2 commits

Author SHA1 Message Date
08ae603106 Section References & Minor style adjustment 2024-07-30 16:40:14 +02:00
b814c57355 Added bindings 2024-07-30 11:01:22 +02:00
28 changed files with 515 additions and 320 deletions

32
docs/external/graphviz.nml vendored Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,3 +6,22 @@
\definecolor{__color1}{HTML}{d5d5d5} \\
\everymath{\color{__color1}\displaystyle}
@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,6 +38,24 @@ 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
///
/// # Parameters
@ -74,18 +92,6 @@ impl Compiler {
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 get_variable_or_error(
document: &dyn Document,
@ -109,7 +115,10 @@ impl Compiler {
result += "<!DOCTYPE HTML><html><head>";
result += "<meta charset=\"UTF-8\">";
if let Some(page_title) = get_variable_or_error(document, "html.page_title") {
result += format!("<title>{}</title>", Compiler::sanitize(self.target(), page_title.to_string()))
result += format!(
"<title>{}</title>",
Compiler::sanitize(self.target(), page_title.to_string())
)
.as_str();
}
@ -120,7 +129,7 @@ impl Compiler {
)
.as_str();
}
result += r#"</head><body><div id="layout">"#;
result += r#"</head><body><div class="layout">"#;
// TODO: TOC
// TODO: Author, Date, Title, Div
@ -148,7 +157,7 @@ impl Compiler {
let header = self.header(document);
// Body
let mut body = r#"<div id="content">"#.to_string();
let mut body = r#"<div class="content">"#.to_string();
for i in 0..borrow.len() {
let elem = &borrow[i];

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,41 @@
use mlua::{Function, Lua};
use crate::document::document::Document;
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 crate::{document::document::Document, parser::{parser::{Parser, ReportColors}, rule::RegexRule, source::{Source, Token}}};
use ariadne::{Report, Fmt, Label, ReportKind};
use crate::document::variable::{BaseVariable, PathVariable, Variable};
use std::{ops::Range, rc::Rc};
use std::ops::Range;
use std::rc::Rc;
use std::str::FromStr;
#[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 {
re: [Regex; 1],
@ -14,20 +46,20 @@ impl VariableRule {
pub fn new() -> Self {
Self {
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(&self, colors: &ReportColors, location: Token, kind: usize, name: String, value: String) -> Result<Rc<dyn Variable>, String>
{
match self.kinds[kind].0.as_str()
{
"" => {
Ok(Rc::new(BaseVariable::new(location, name, value)))
}
pub fn make_variable(
&self,
colors: &ReportColors,
location: Token,
kind: usize,
name: String,
value: String,
) -> Result<Rc<dyn Variable>, String> {
match self.kinds[kind].0.as_str() {
"" => Ok(Rc::new(BaseVariable::new(location, name, value))),
"'" => {
match std::fs::canonicalize(value.as_str()) // TODO: not canonicalize
{
@ -37,42 +69,41 @@ impl VariableRule {
e.to_string()))
}
}
_ => panic!("Unhandled variable kind")
_ => panic!("Unhandled variable kind"),
}
}
// Trim and check variable name for validity
pub fn validate_name<'a>(colors: &ReportColors, original_name: &'a str) -> Result<&'a str, String>
{
pub fn validate_name<'a>(
colors: &ReportColors,
original_name: &'a str,
) -> Result<&'a str, String> {
let name = original_name.trim_start().trim_end();
if name.contains("%")
{
return Err(format!("Name cannot contain '{}'",
"%".fg(colors.info)));
if name.contains("%") {
return Err(format!("Name cannot contain '{}'", "%".fg(colors.info)));
}
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 result = String::new();
for c in original_value.trim_start().trim_end().chars() {
if c == '\\' { escaped += 1 }
else if c == '\n' {
if c == '\\' {
escaped += 1
} else if c == '\n' {
match escaped {
0 => return Err("Unknown error wile capturing variable".to_string()),
// Remove '\n'
1 => {},
1 => {}
// Insert '\n'
_ => {
result.push(c);
(0..escaped-2).for_each(|_| result.push('\\'));
(0..escaped - 2).for_each(|_| result.push('\\'));
}
}
escaped = 0;
}
else {
} else {
(0..escaped).for_each(|_| result.push('\\'));
escaped = 0;
result.push(c);
@ -89,35 +120,54 @@ impl RegexRule for VariableRule {
fn regexes(&self) -> &[Regex] { &self.re }
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>)>>
{
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>)>> {
let mut result = vec![];
// [Optional] variable kind
let var_kind = match matches.get(1)
{
let var_kind = match matches.get(1) {
Some(kind) => {
// Find kind
let r = self.kinds.iter().enumerate().find(|(_i, (ref char, ref _name))| {
char == kind.as_str() });
let r = self
.kinds
.iter()
.enumerate()
.find(|(_i, (ref char, ref _name))| char == kind.as_str());
// Unknown kind specified
if r.is_none()
{
if r.is_none() {
result.push(
Report::build(ReportKind::Error, token.source(), kind.start())
.with_message("Unknown variable kind")
.with_label(
Label::new((token.source(), kind.range()))
.with_message(format!("Variable kind `{}` is unknown",
kind.as_str().fg(parser.colors().highlight)))
.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)| {
acc + format!("\n - `{}` : {}",
.with_message(format!(
"Variable kind `{}` is unknown",
kind.as_str().fg(parser.colors().highlight)
))
.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)| {
acc + format!(
"\n - `{}` : {}",
char.fg(parser.colors().highlight),
name.fg(parser.colors().info)).as_str()
})))
.finish());
name.fg(parser.colors().info)
)
.as_str()
}
)
))
.finish(),
);
return result;
}
@ -127,11 +177,8 @@ impl RegexRule for VariableRule {
None => 0,
};
let var_name = match matches.get(2)
{
Some(name) => {
match VariableRule::validate_name(&parser.colors(), name.as_str())
{
let var_name = match matches.get(2) {
Some(name) => match VariableRule::validate_name(&parser.colors(), name.as_str()) {
Ok(var_name) => var_name,
Err(msg) => {
result.push(
@ -139,44 +186,52 @@ impl RegexRule for VariableRule {
.with_message("Invalid variable name")
.with_label(
Label::new((token.source(), name.range()))
.with_message(format!("Variable name `{}` is not allowed. {msg}",
name.as_str().fg(parser.colors().highlight)))
.with_color(parser.colors().error))
.finish());
.with_message(format!(
"Variable name `{}` is not allowed. {msg}",
name.as_str().fg(parser.colors().highlight)
))
.with_color(parser.colors().error),
)
.finish(),
);
return result;
},
}
},
_ => panic!("Unknown variable name")
_ => panic!("Unknown variable name"),
};
let var_value = match matches.get(3)
{
Some(value) => {
match VariableRule::validate_value(&parser.colors(), value.as_str())
{
let var_value = match matches.get(3) {
Some(value) => match VariableRule::validate_value(&parser.colors(), value.as_str()) {
Ok(var_value) => var_value,
Err(msg ) => {
Err(msg) => {
result.push(
Report::build(ReportKind::Error, token.source(), value.start())
.with_message("Invalid variable value")
.with_label(
Label::new((token.source(), value.range()))
.with_message(format!("Variable value `{}` is not allowed. {msg}",
value.as_str().fg(parser.colors().highlight)))
.with_color(parser.colors().error))
.finish());
.with_message(format!(
"Variable value `{}` is not allowed. {msg}",
value.as_str().fg(parser.colors().highlight)
))
.with_color(parser.colors().error),
)
.finish(),
);
return result;
}
}
}
_ => panic!("Invalid variable value")
},
_ => panic!("Invalid variable value"),
};
match self.make_variable(&parser.colors(), token.clone(), var_kind, var_name.to_string(), var_value)
{
match self.make_variable(
&parser.colors(),
token.clone(),
var_kind,
var_name.to_string(),
var_value,
) {
Ok(variable) => document.add_variable(variable),
Err(msg) => {
let m = matches.get(0).unwrap();
@ -184,12 +239,16 @@ impl RegexRule for VariableRule {
Report::build(ReportKind::Error, token.source(), m.start())
.with_message("Unable to create variable")
.with_label(
Label::new((token.source(), m.start()+1 .. m.end() ))
.with_message(format!("Unable to create variable `{}`. {}",
Label::new((token.source(), m.start() + 1..m.end()))
.with_message(format!(
"Unable to create variable `{}`. {}",
var_name.fg(parser.colors().highlight),
msg))
.with_color(parser.colors().error))
.finish());
msg
))
.with_color(parser.colors().error),
)
.finish(),
);
return result;
}
@ -198,12 +257,45 @@ impl RegexRule for VariableRule {
return result;
}
// TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> {
let mut bindings = 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],
}
@ -215,100 +307,113 @@ impl VariableSubstitutionRule {
}
}
impl RegexRule for VariableSubstitutionRule
{
impl RegexRule for VariableSubstitutionRule {
fn name(&self) -> &'static str { "Variable Substitution" }
fn regexes(&self) -> &[regex::Regex] { &self.re }
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>)>> {
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>)>> {
let mut result = vec![];
let variable = match matches.get(1)
{
let variable = match matches.get(1) {
Some(name) => {
// Empty name
if name.as_str().is_empty()
{
if name.as_str().is_empty() {
result.push(
Report::build(ReportKind::Error, token.source(), name.start())
.with_message("Empty variable name")
.with_label(
Label::new((token.source(), matches.get(0).unwrap().range()))
.with_message(format!("Missing variable name for substitution"))
.with_color(parser.colors().error))
.finish());
.with_color(parser.colors().error),
)
.finish(),
);
return result;
}
// 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())
.with_message("Invalid variable name")
.with_label(
Label::new((token.source(), name.range()))
.with_message(format!("Variable names contains leading spaces"))
.with_color(parser.colors().error))
.with_color(parser.colors().error),
)
.with_help("Remove leading spaces")
.finish());
.finish(),
);
return result;
}
// 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())
.with_message("Invalid variable name")
.with_label(
Label::new((token.source(), name.range()))
.with_message(format!("Variable names contains trailing spaces"))
.with_color(parser.colors().error))
.with_message(format!(
"Variable names contains trailing spaces"
))
.with_color(parser.colors().error),
)
.with_help("Remove trailing spaces")
.finish());
.finish(),
);
return result;
}
// Invalid name
match VariableRule::validate_name(&parser.colors(), name.as_str())
{
Err(msg) =>
{
match VariableRule::validate_name(&parser.colors(), name.as_str()) {
Err(msg) => {
result.push(
Report::build(ReportKind::Error, token.source(), name.start())
.with_message("Invalid variable name")
.with_label(
Label::new((token.source(), name.range()))
.with_message(msg)
.with_color(parser.colors().error))
.finish());
.with_color(parser.colors().error),
)
.finish(),
);
return result;
}
_ => {},
_ => {}
}
// Get variable
match document.get_variable(name.as_str())
{
match document.get_variable(name.as_str()) {
None => {
result.push(
Report::build(ReportKind::Error, token.source(), name.start())
.with_message("Unknown variable name")
.with_label(
Label::new((token.source(), name.range()))
.with_message(format!("Unable to find variable with name: `{}`",
name.as_str().fg(parser.colors().highlight)))
.with_color(parser.colors().error))
.finish());
.with_message(format!(
"Unable to find variable with name: `{}`",
name.as_str().fg(parser.colors().highlight)
))
.with_color(parser.colors().error),
)
.finish(),
);
return result;
}
Some(var) => var,
}
},
_ => panic!("Unknown error")
}
_ => panic!("Unknown error"),
};
variable.parse(token, parser, document);
@ -316,6 +421,5 @@ impl RegexRule for VariableSubstitutionRule
return result;
}
// TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
}

View file

@ -33,17 +33,20 @@ impl Kernel {
let nml_table = lua.create_table().unwrap();
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)
for (fun_name, fun) in bindings
{
table.set(fun_name, fun).unwrap();
}
nml_table.set(name, table).unwrap();
}
}
lua.globals().set("nml", nml_table).unwrap();
}

View file

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

View file

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