mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Refactor parser 🏞
This commit is contained in:
parent
f8770d2b2a
commit
3533268b1f
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||||||
authors = ["The Typst Project Developers"]
|
authors = ["The Typst Project Developers"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
bench = false
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["main"]
|
members = ["main"]
|
||||||
|
|
||||||
|
@ -15,12 +15,10 @@ use crate::syntax::{Ident, Span, SpanWith, Spanned, SynNode, SynTree};
|
|||||||
use crate::{DynFuture, Feedback, Pass};
|
use crate::{DynFuture, Feedback, Pass};
|
||||||
|
|
||||||
/// A computational value.
|
/// A computational value.
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
/// An identifier: `ident`.
|
/// An identifier: `ident`.
|
||||||
Ident(Ident),
|
Ident(Ident),
|
||||||
/// A string: `"string"`.
|
|
||||||
Str(String),
|
|
||||||
/// A boolean: `true, false`.
|
/// A boolean: `true, false`.
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
/// A number: `1.2, 200%`.
|
/// A number: `1.2, 200%`.
|
||||||
@ -29,6 +27,8 @@ pub enum Value {
|
|||||||
Length(Length),
|
Length(Length),
|
||||||
/// A color value with alpha channel: `#f79143ff`.
|
/// A color value with alpha channel: `#f79143ff`.
|
||||||
Color(RgbaColor),
|
Color(RgbaColor),
|
||||||
|
/// A string: `"string"`.
|
||||||
|
Str(String),
|
||||||
/// A dictionary value: `(false, 12cm, greeting="hi")`.
|
/// A dictionary value: `(false, 12cm, greeting="hi")`.
|
||||||
Dict(DictValue),
|
Dict(DictValue),
|
||||||
/// A syntax tree containing typesetting content.
|
/// A syntax tree containing typesetting content.
|
||||||
@ -45,11 +45,11 @@ impl Value {
|
|||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Ident(_) => "identifier",
|
Self::Ident(_) => "identifier",
|
||||||
Self::Str(_) => "string",
|
|
||||||
Self::Bool(_) => "bool",
|
Self::Bool(_) => "bool",
|
||||||
Self::Number(_) => "number",
|
Self::Number(_) => "number",
|
||||||
Self::Length(_) => "length",
|
Self::Length(_) => "length",
|
||||||
Self::Color(_) => "color",
|
Self::Color(_) => "color",
|
||||||
|
Self::Str(_) => "string",
|
||||||
Self::Dict(_) => "dict",
|
Self::Dict(_) => "dict",
|
||||||
Self::Tree(_) => "syntax tree",
|
Self::Tree(_) => "syntax tree",
|
||||||
Self::Func(_) => "function",
|
Self::Func(_) => "function",
|
||||||
@ -65,9 +65,6 @@ impl Spanned<Value> {
|
|||||||
/// the value is represented as layoutable content in a reasonable way.
|
/// the value is represented as layoutable content in a reasonable way.
|
||||||
pub fn into_commands(self) -> Commands {
|
pub fn into_commands(self) -> Commands {
|
||||||
match self.v {
|
match self.v {
|
||||||
Value::Commands(commands) => commands,
|
|
||||||
Value::Tree(tree) => vec![Command::LayoutSyntaxTree(tree)],
|
|
||||||
|
|
||||||
// Forward to each entry, separated with spaces.
|
// Forward to each entry, separated with spaces.
|
||||||
Value::Dict(dict) => {
|
Value::Dict(dict) => {
|
||||||
let mut commands = vec![];
|
let mut commands = vec![];
|
||||||
@ -75,7 +72,7 @@ impl Spanned<Value> {
|
|||||||
for entry in dict.into_values() {
|
for entry in dict.into_values() {
|
||||||
if let Some(last_end) = end {
|
if let Some(last_end) = end {
|
||||||
let span = Span::new(last_end, entry.key.start);
|
let span = Span::new(last_end, entry.key.start);
|
||||||
let tree = vec![SynNode::Spacing.span_with(span)];
|
let tree = vec![SynNode::Space.span_with(span)];
|
||||||
commands.push(Command::LayoutSyntaxTree(tree));
|
commands.push(Command::LayoutSyntaxTree(tree));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +82,9 @@ impl Spanned<Value> {
|
|||||||
commands
|
commands
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value::Tree(tree) => vec![Command::LayoutSyntaxTree(tree)],
|
||||||
|
Value::Commands(commands) => commands,
|
||||||
|
|
||||||
// Format with debug.
|
// Format with debug.
|
||||||
val => {
|
val => {
|
||||||
let fmt = format!("{:?}", val);
|
let fmt = format!("{:?}", val);
|
||||||
@ -99,37 +99,19 @@ impl Debug for Value {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Ident(i) => i.fmt(f),
|
Self::Ident(i) => i.fmt(f),
|
||||||
Self::Str(s) => s.fmt(f),
|
|
||||||
Self::Bool(b) => b.fmt(f),
|
Self::Bool(b) => b.fmt(f),
|
||||||
Self::Number(n) => n.fmt(f),
|
Self::Number(n) => n.fmt(f),
|
||||||
Self::Length(s) => s.fmt(f),
|
Self::Length(s) => s.fmt(f),
|
||||||
Self::Color(c) => c.fmt(f),
|
Self::Color(c) => c.fmt(f),
|
||||||
|
Self::Str(s) => s.fmt(f),
|
||||||
Self::Dict(t) => t.fmt(f),
|
Self::Dict(t) => t.fmt(f),
|
||||||
Self::Tree(t) => t.fmt(f),
|
Self::Tree(t) => t.fmt(f),
|
||||||
Self::Func(_) => f.pad("<function>"),
|
Self::Func(c) => c.fmt(f),
|
||||||
Self::Commands(c) => c.fmt(f),
|
Self::Commands(c) => c.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Value {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
match (self, other) {
|
|
||||||
(Self::Ident(a), Self::Ident(b)) => a == b,
|
|
||||||
(Self::Str(a), Self::Str(b)) => a == b,
|
|
||||||
(Self::Bool(a), Self::Bool(b)) => a == b,
|
|
||||||
(Self::Number(a), Self::Number(b)) => a == b,
|
|
||||||
(Self::Length(a), Self::Length(b)) => a == b,
|
|
||||||
(Self::Color(a), Self::Color(b)) => a == b,
|
|
||||||
(Self::Dict(a), Self::Dict(b)) => a == b,
|
|
||||||
(Self::Tree(a), Self::Tree(b)) => a == b,
|
|
||||||
(Self::Func(a), Self::Func(b)) => Rc::ptr_eq(a, b),
|
|
||||||
(Self::Commands(a), Self::Commands(b)) => a == b,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An executable function value.
|
/// An executable function value.
|
||||||
///
|
///
|
||||||
/// The first argument is a dictionary containing the arguments passed to the
|
/// The first argument is a dictionary containing the arguments passed to the
|
||||||
@ -140,8 +122,45 @@ impl PartialEq for Value {
|
|||||||
/// layouting engine to do what the function pleases.
|
/// layouting engine to do what the function pleases.
|
||||||
///
|
///
|
||||||
/// The dynamic function object is wrapped in an `Rc` to keep `Value` clonable.
|
/// The dynamic function object is wrapped in an `Rc` to keep `Value` clonable.
|
||||||
pub type FuncValue =
|
#[derive(Clone)]
|
||||||
Rc<dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>>;
|
pub struct FuncValue(pub Rc<FuncType>);
|
||||||
|
|
||||||
|
/// The dynamic function type backtick [`FuncValue`].
|
||||||
|
///
|
||||||
|
/// [`FuncValue`]: struct.FuncValue.html
|
||||||
|
pub type FuncType = dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>;
|
||||||
|
|
||||||
|
impl FuncValue {
|
||||||
|
/// Create a new function value from a rust function or closure.
|
||||||
|
pub fn new<F: 'static>(f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>,
|
||||||
|
{
|
||||||
|
Self(Rc::new(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for FuncValue {}
|
||||||
|
|
||||||
|
impl PartialEq for FuncValue {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
Rc::ptr_eq(&self.0, &other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for FuncValue {
|
||||||
|
type Target = FuncType;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for FuncValue {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad("<function>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A dictionary of values.
|
/// A dictionary of values.
|
||||||
///
|
///
|
||||||
@ -262,8 +281,7 @@ impl DictValue {
|
|||||||
/// Generated `"unexpected argument"` errors for all remaining entries.
|
/// Generated `"unexpected argument"` errors for all remaining entries.
|
||||||
pub fn unexpected(&self, f: &mut Feedback) {
|
pub fn unexpected(&self, f: &mut Feedback) {
|
||||||
for entry in self.values() {
|
for entry in self.values() {
|
||||||
let span = Span::merge(entry.key, entry.val.span);
|
error!(@f, entry.key.join(entry.val.span), "unexpected argument");
|
||||||
error!(@f, span, "unexpected argument");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,19 +58,7 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match &node.v {
|
match &node.v {
|
||||||
SynNode::Spacing => self.layout_space(),
|
SynNode::Space => self.layout_space(),
|
||||||
SynNode::Linebreak => self.layouter.finish_line(),
|
|
||||||
SynNode::Parbreak => self.layout_parbreak(),
|
|
||||||
|
|
||||||
SynNode::ToggleItalic => {
|
|
||||||
self.style.text.italic = !self.style.text.italic;
|
|
||||||
decorate(self, Decoration::Italic);
|
|
||||||
}
|
|
||||||
SynNode::ToggleBolder => {
|
|
||||||
self.style.text.bolder = !self.style.text.bolder;
|
|
||||||
decorate(self, Decoration::Bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
SynNode::Text(text) => {
|
SynNode::Text(text) => {
|
||||||
if self.style.text.italic {
|
if self.style.text.italic {
|
||||||
decorate(self, Decoration::Italic);
|
decorate(self, Decoration::Italic);
|
||||||
@ -81,8 +69,19 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
self.layout_text(text).await;
|
self.layout_text(text).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
SynNode::Raw(raw) => self.layout_raw(raw).await,
|
SynNode::Linebreak => self.layouter.finish_line(),
|
||||||
|
SynNode::Parbreak => self.layout_parbreak(),
|
||||||
|
SynNode::ToggleItalic => {
|
||||||
|
self.style.text.italic = !self.style.text.italic;
|
||||||
|
decorate(self, Decoration::Italic);
|
||||||
|
}
|
||||||
|
SynNode::ToggleBolder => {
|
||||||
|
self.style.text.bolder = !self.style.text.bolder;
|
||||||
|
decorate(self, Decoration::Bold);
|
||||||
|
}
|
||||||
|
|
||||||
SynNode::Heading(heading) => self.layout_heading(heading).await,
|
SynNode::Heading(heading) => self.layout_heading(heading).await,
|
||||||
|
SynNode::Raw(raw) => self.layout_raw(raw).await,
|
||||||
|
|
||||||
SynNode::Expr(expr) => {
|
SynNode::Expr(expr) => {
|
||||||
self.layout_expr(expr.span_with(node.span)).await;
|
self.layout_expr(expr.span_with(node.span)).await;
|
||||||
@ -116,7 +115,7 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
|
|
||||||
async fn layout_heading(&mut self, heading: &NodeHeading) {
|
async fn layout_heading(&mut self, heading: &NodeHeading) {
|
||||||
let style = self.style.text.clone();
|
let style = self.style.text.clone();
|
||||||
self.style.text.font_scale *= 1.5 - 0.1 * heading.level.v.min(5) as f64;
|
self.style.text.font_scale *= 1.5 - 0.1 * heading.level.v as f64;
|
||||||
self.style.text.bolder = true;
|
self.style.text.bolder = true;
|
||||||
|
|
||||||
self.layout_parbreak();
|
self.layout_parbreak();
|
||||||
|
@ -13,7 +13,7 @@ pub async fn boxed(
|
|||||||
) -> Pass<Value> {
|
) -> Pass<Value> {
|
||||||
let mut f = Feedback::new();
|
let mut f = Feedback::new();
|
||||||
|
|
||||||
let content = args.take::<SynTree>().unwrap_or(SynTree::new());
|
let content = args.take::<SynTree>().unwrap_or_default();
|
||||||
|
|
||||||
ctx.base = ctx.spaces[0].size;
|
ctx.base = ctx.spaces[0].size;
|
||||||
ctx.spaces.truncate(1);
|
ctx.spaces.truncate(1);
|
||||||
|
@ -14,9 +14,7 @@ pub use font::*;
|
|||||||
pub use page::*;
|
pub use page::*;
|
||||||
pub use spacing::*;
|
pub use spacing::*;
|
||||||
|
|
||||||
use std::rc::Rc;
|
use crate::eval::{FuncValue, Scope};
|
||||||
|
|
||||||
use crate::eval::Scope;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
macro_rules! std {
|
macro_rules! std {
|
||||||
@ -32,7 +30,7 @@ macro_rules! std {
|
|||||||
|
|
||||||
macro_rules! wrap {
|
macro_rules! wrap {
|
||||||
($func:expr) => {
|
($func:expr) => {
|
||||||
Rc::new(|name, args, ctx| Box::pin($func(name, args, ctx)))
|
FuncValue::new(|name, args, ctx| Box::pin($func(name, args, ctx)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1009
src/parse/mod.rs
1009
src/parse/mod.rs
File diff suppressed because it is too large
Load Diff
292
src/parse/parser.rs
Normal file
292
src/parse/parser.rs
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
|
use super::{Scanner, TokenMode, Tokens};
|
||||||
|
use crate::diagnostic::Diagnostic;
|
||||||
|
use crate::syntax::{Decoration, Pos, Span, SpanWith, Spanned, Token};
|
||||||
|
use crate::Feedback;
|
||||||
|
|
||||||
|
/// A convenient token-based parser.
|
||||||
|
pub struct Parser<'s> {
|
||||||
|
tokens: Tokens<'s>,
|
||||||
|
modes: Vec<TokenMode>,
|
||||||
|
groups: Vec<(Pos, Group)>,
|
||||||
|
f: Feedback,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Parser<'s> {
|
||||||
|
/// Create a new parser for the source string.
|
||||||
|
pub fn new(src: &'s str) -> Self {
|
||||||
|
Self {
|
||||||
|
tokens: Tokens::new(src, TokenMode::Body),
|
||||||
|
modes: vec![],
|
||||||
|
groups: vec![],
|
||||||
|
f: Feedback::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish parsing and return the accumulated feedback.
|
||||||
|
pub fn finish(self) -> Feedback {
|
||||||
|
self.f
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a diagnostic to the feedback.
|
||||||
|
pub fn diag(&mut self, diag: Spanned<Diagnostic>) {
|
||||||
|
self.f.diagnostics.push(diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Eat the next token and add a diagnostic that it was not expected thing.
|
||||||
|
pub fn diag_expected(&mut self, thing: &str) {
|
||||||
|
if let Some(found) = self.eat() {
|
||||||
|
self.diag(error!(
|
||||||
|
found.span,
|
||||||
|
"expected {}, found {}",
|
||||||
|
thing,
|
||||||
|
found.v.name(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
self.diag_expected_at(thing, self.pos());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a diagnostic that the thing was expected at the given position.
|
||||||
|
pub fn diag_expected_at(&mut self, thing: &str, pos: Pos) {
|
||||||
|
self.diag(error!(pos, "expected {}", thing));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a diagnostic that the given token was unexpected.
|
||||||
|
pub fn diag_unexpected(&mut self, token: Spanned<Token>) {
|
||||||
|
self.diag(error!(token.span, "unexpected {}", token.v.name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a decoration to the feedback.
|
||||||
|
pub fn deco(&mut self, deco: Spanned<Decoration>) {
|
||||||
|
self.f.decorations.push(deco);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the token mode and push the previous mode onto a stack.
|
||||||
|
pub fn push_mode(&mut self, mode: TokenMode) {
|
||||||
|
self.modes.push(self.tokens.mode());
|
||||||
|
self.tokens.set_mode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop the topmost token mode from the stack.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This panics if there is no mode on the stack.
|
||||||
|
pub fn pop_mode(&mut self) {
|
||||||
|
self.tokens.set_mode(self.modes.pop().expect("no pushed mode"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Continues parsing in a group.
|
||||||
|
///
|
||||||
|
/// When the end delimiter of the group is reached, all subsequent calls to
|
||||||
|
/// `eat()` and `peek()` return `None`. Parsing can only continue with
|
||||||
|
/// a matching call to `end_group`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This panics if the next token does not start the given group.
|
||||||
|
pub fn start_group(&mut self, group: Group) {
|
||||||
|
let start = self.pos();
|
||||||
|
match group {
|
||||||
|
Group::Paren => self.eat_assert(Token::LeftParen),
|
||||||
|
Group::Bracket => self.eat_assert(Token::LeftBracket),
|
||||||
|
Group::Brace => self.eat_assert(Token::LeftBrace),
|
||||||
|
Group::Subheader => {}
|
||||||
|
}
|
||||||
|
self.groups.push((start, group));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ends the parsing of a group and returns the span of the whole group.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This panics if no group was started.
|
||||||
|
pub fn end_group(&mut self) -> Span {
|
||||||
|
debug_assert_eq!(self.peek(), None, "unfinished group");
|
||||||
|
|
||||||
|
let (start, group) = self.groups.pop().expect("unstarted group");
|
||||||
|
let end = match group {
|
||||||
|
Group::Paren => Some(Token::RightParen),
|
||||||
|
Group::Bracket => Some(Token::RightBracket),
|
||||||
|
Group::Brace => Some(Token::RightBrace),
|
||||||
|
Group::Subheader => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(token) = end {
|
||||||
|
let next = self.tokens.clone().next().map(|s| s.v);
|
||||||
|
if next == Some(token) {
|
||||||
|
self.tokens.next();
|
||||||
|
} else {
|
||||||
|
self.diag(error!(self.pos(), "expected {}", token.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Span::new(start, self.pos())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume the next token.
|
||||||
|
pub fn eat(&mut self) -> Option<Spanned<Token<'s>>> {
|
||||||
|
next_group_aware(&mut self.tokens, &self.groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume the next token if it is the given one.
|
||||||
|
pub fn eat_if(&mut self, t: Token) -> Option<Spanned<Token<'s>>> {
|
||||||
|
// Don't call eat() twice if it suceeds.
|
||||||
|
//
|
||||||
|
// TODO: Benchmark this vs. the naive version.
|
||||||
|
let before = self.pos();
|
||||||
|
let token = self.eat()?;
|
||||||
|
if token.v == t {
|
||||||
|
Some(token)
|
||||||
|
} else {
|
||||||
|
self.jump(before);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume the next token if the closure maps to `Some`.
|
||||||
|
pub fn eat_map<T>(
|
||||||
|
&mut self,
|
||||||
|
mut f: impl FnMut(Token<'s>) -> Option<T>,
|
||||||
|
) -> Option<Spanned<T>> {
|
||||||
|
let before = self.pos();
|
||||||
|
let token = self.eat()?;
|
||||||
|
if let Some(t) = f(token.v) {
|
||||||
|
Some(t.span_with(token.span))
|
||||||
|
} else {
|
||||||
|
self.jump(before);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume the next token, debug-asserting that it is the given one.
|
||||||
|
pub fn eat_assert(&mut self, t: Token) {
|
||||||
|
let next = self.eat();
|
||||||
|
debug_assert_eq!(next.map(|s| s.v), Some(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume tokens while the condition is true.
|
||||||
|
///
|
||||||
|
/// Returns how many tokens were eaten.
|
||||||
|
pub fn eat_while(&mut self, mut f: impl FnMut(Token<'s>) -> bool) -> usize {
|
||||||
|
self.eat_until(|t| !f(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume tokens until the condition is true.
|
||||||
|
///
|
||||||
|
/// Returns how many tokens were eaten.
|
||||||
|
pub fn eat_until(&mut self, mut f: impl FnMut(Token<'s>) -> bool) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
let mut before = self.pos();
|
||||||
|
while let Some(t) = self.eat() {
|
||||||
|
if f(t.v) {
|
||||||
|
// Undo the last eat by jumping. This prevents
|
||||||
|
// double-tokenization by not peeking all the time.
|
||||||
|
//
|
||||||
|
// TODO: Benchmark this vs. the naive peeking version.
|
||||||
|
self.jump(before);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
before = self.pos();
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peek at the next token without consuming it.
|
||||||
|
pub fn peek(&self) -> Option<Token<'s>> {
|
||||||
|
next_group_aware(&mut self.tokens.clone(), &self.groups).map(|s| s.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether the next token fulfills a condition.
|
||||||
|
///
|
||||||
|
/// Returns `false` if there is no next token.
|
||||||
|
pub fn check(&self, f: impl FnMut(Token<'s>) -> bool) -> bool {
|
||||||
|
self.peek().map(f).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the there is no next token.
|
||||||
|
pub fn eof(&self) -> bool {
|
||||||
|
self.peek().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skip whitespace tokens.
|
||||||
|
pub fn skip_white(&mut self) {
|
||||||
|
self.eat_while(|t| {
|
||||||
|
matches!(t,
|
||||||
|
Token::Space(_) |
|
||||||
|
Token::LineComment(_) |
|
||||||
|
Token::BlockComment(_))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The position in the string at which the last token ends and next token
|
||||||
|
/// will start.
|
||||||
|
pub fn pos(&self) -> Pos {
|
||||||
|
self.tokens.pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Jump to a position in the source string.
|
||||||
|
pub fn jump(&mut self, pos: Pos) {
|
||||||
|
self.tokens.jump(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The full source string.
|
||||||
|
pub fn src(&self) -> &'s str {
|
||||||
|
self.scanner().src()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The part of the source string that is spanned by the given span.
|
||||||
|
pub fn get(&self, span: Span) -> &'s str {
|
||||||
|
self.scanner().get(span.start.to_usize() .. span.end.to_usize())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The underlying scanner.
|
||||||
|
pub fn scanner(&self) -> &Scanner<'s> {
|
||||||
|
self.tokens.scanner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `tokens.next()`, but is group-aware.
|
||||||
|
fn next_group_aware<'s>(
|
||||||
|
tokens: &mut Tokens<'s>,
|
||||||
|
groups: &[(Pos, Group)],
|
||||||
|
) -> Option<Spanned<Token<'s>>> {
|
||||||
|
let pos = tokens.pos();
|
||||||
|
let token = tokens.next();
|
||||||
|
|
||||||
|
let group = match token?.v {
|
||||||
|
Token::RightParen => Group::Paren,
|
||||||
|
Token::RightBracket => Group::Bracket,
|
||||||
|
Token::RightBrace => Group::Brace,
|
||||||
|
Token::Chain => Group::Subheader,
|
||||||
|
_ => return token,
|
||||||
|
};
|
||||||
|
|
||||||
|
if groups.iter().rev().any(|&(_, g)| g == group) {
|
||||||
|
tokens.jump(pos);
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Parser<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
let s = self.scanner();
|
||||||
|
write!(f, "Parser({}|{})", s.eaten(), s.rest())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A group, confined by optional start and end delimiters.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum Group {
|
||||||
|
/// A parenthesized group: `(...)`.
|
||||||
|
Paren,
|
||||||
|
/// A bracketed group: `[...]`.
|
||||||
|
Bracket,
|
||||||
|
/// A curly-braced group: `{...}`.
|
||||||
|
Brace,
|
||||||
|
/// A group ended by a chained subheader or a closing bracket:
|
||||||
|
/// `... >>`, `...]`.
|
||||||
|
Subheader,
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
use super::{is_newline, Scanner};
|
use super::{is_newline, Scanner};
|
||||||
use crate::syntax::{Ident, NodeRaw};
|
use crate::syntax::{Ident, NodeRaw};
|
||||||
|
|
||||||
/// Resolves all escape sequences in a string.
|
/// Resolve all escape sequences in a string.
|
||||||
pub fn resolve_string(string: &str) -> String {
|
pub fn resolve_string(string: &str) -> String {
|
||||||
let mut out = String::with_capacity(string.len());
|
let mut out = String::with_capacity(string.len());
|
||||||
let mut s = Scanner::new(string);
|
let mut s = Scanner::new(string);
|
||||||
@ -48,10 +48,10 @@ pub fn resolve_hex(sequence: &str) -> Option<char> {
|
|||||||
u32::from_str_radix(sequence, 16).ok().and_then(std::char::from_u32)
|
u32::from_str_radix(sequence, 16).ok().and_then(std::char::from_u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves the language tag and trims the raw text.
|
/// Resolve the language tag and trims the raw text.
|
||||||
pub fn resolve_raw(raw: &str, backticks: usize) -> NodeRaw {
|
pub fn resolve_raw(text: &str, backticks: usize) -> NodeRaw {
|
||||||
if backticks > 1 {
|
if backticks > 1 {
|
||||||
let (tag, inner) = split_at_lang_tag(raw);
|
let (tag, inner) = split_at_lang_tag(text);
|
||||||
let (lines, had_newline) = trim_and_split_raw(inner);
|
let (lines, had_newline) = trim_and_split_raw(inner);
|
||||||
NodeRaw {
|
NodeRaw {
|
||||||
lang: Ident::new(tag),
|
lang: Ident::new(tag),
|
||||||
@ -61,7 +61,7 @@ pub fn resolve_raw(raw: &str, backticks: usize) -> NodeRaw {
|
|||||||
} else {
|
} else {
|
||||||
NodeRaw {
|
NodeRaw {
|
||||||
lang: None,
|
lang: None,
|
||||||
lines: split_lines(raw),
|
lines: split_lines(text),
|
||||||
inline: true,
|
inline: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ fn split_at_lang_tag(raw: &str) -> (&str, &str) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trims raw text and splits it into lines.
|
/// Trim raw text and splits it into lines.
|
||||||
///
|
///
|
||||||
/// Returns whether at least one newline was contained in `raw`.
|
/// Returns whether at least one newline was contained in `raw`.
|
||||||
fn trim_and_split_raw(raw: &str) -> (Vec<String>, bool) {
|
fn trim_and_split_raw(raw: &str) -> (Vec<String>, bool) {
|
||||||
@ -101,7 +101,7 @@ fn trim_and_split_raw(raw: &str) -> (Vec<String>, bool) {
|
|||||||
(lines, had_newline)
|
(lines, had_newline)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Splits a string into a vector of lines
|
/// Split a string into a vector of lines
|
||||||
/// (respecting Unicode, Unix, Mac and Windows line breaks).
|
/// (respecting Unicode, Unix, Mac and Windows line breaks).
|
||||||
pub fn split_lines(text: &str) -> Vec<String> {
|
pub fn split_lines(text: &str) -> Vec<String> {
|
||||||
let mut s = Scanner::new(text);
|
let mut s = Scanner::new(text);
|
||||||
@ -147,8 +147,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split_at_lang_tag() {
|
fn test_split_at_lang_tag() {
|
||||||
fn test(raw: &str, lang: &str, inner: &str) {
|
fn test(text: &str, lang: &str, inner: &str) {
|
||||||
assert_eq!(split_at_lang_tag(raw), (lang, inner));
|
assert_eq!(split_at_lang_tag(text), (lang, inner));
|
||||||
}
|
}
|
||||||
|
|
||||||
test("typst it!", "typst", " it!");
|
test("typst it!", "typst", " it!");
|
||||||
@ -161,8 +161,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_trim_raw() {
|
fn test_trim_raw() {
|
||||||
fn test(raw: &str, expected: Vec<&str>) {
|
fn test(text: &str, expected: Vec<&str>) {
|
||||||
assert_eq!(trim_and_split_raw(raw).0, expected);
|
assert_eq!(trim_and_split_raw(text).0, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
test(" hi", vec!["hi"]);
|
test(" hi", vec!["hi"]);
|
||||||
@ -178,8 +178,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split_lines() {
|
fn test_split_lines() {
|
||||||
fn test(raw: &str, expected: Vec<&str>) {
|
fn test(text: &str, expected: Vec<&str>) {
|
||||||
assert_eq!(split_lines(raw), expected);
|
assert_eq!(split_lines(text), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
test("raw\ntext", vec!["raw", "text"]);
|
test("raw\ntext", vec!["raw", "text"]);
|
||||||
|
@ -4,7 +4,8 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::slice::SliceIndex;
|
use std::slice::SliceIndex;
|
||||||
use std::str::Chars;
|
use std::str::Chars;
|
||||||
|
|
||||||
/// A low-level featureful char scanner.
|
/// A low-level featureful char-based scanner.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Scanner<'s> {
|
pub struct Scanner<'s> {
|
||||||
src: &'s str,
|
src: &'s str,
|
||||||
iter: Chars<'s>,
|
iter: Chars<'s>,
|
||||||
@ -98,24 +99,22 @@ impl<'s> Scanner<'s> {
|
|||||||
|
|
||||||
/// Checks whether the next character fulfills a condition.
|
/// Checks whether the next character fulfills a condition.
|
||||||
///
|
///
|
||||||
/// Returns `false` is there is no next character.
|
/// Returns `false` if there is no next character.
|
||||||
pub fn check(&self, f: impl FnMut(char) -> bool) -> bool {
|
pub fn check(&self, f: impl FnMut(char) -> bool) -> bool {
|
||||||
self.peek().map(f).unwrap_or(false)
|
self.peek().map(f).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Go back to the where the index says.
|
/// Whether the end of the source string is reached.
|
||||||
fn reset(&mut self) {
|
pub fn eof(&self) -> bool {
|
||||||
self.iter = self.src[self.index ..].chars();
|
self.iter.as_str().is_empty()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> Scanner<'s> {
|
/// The current index in the source string.
|
||||||
/// The current index in the string.
|
|
||||||
pub fn index(&self) -> usize {
|
pub fn index(&self) -> usize {
|
||||||
self.index
|
self.index
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The previous index in the string.
|
/// The previous index in the source string.
|
||||||
pub fn prev_index(&self) -> usize {
|
pub fn prev_index(&self) -> usize {
|
||||||
self.src[.. self.index]
|
self.src[.. self.index]
|
||||||
.chars()
|
.chars()
|
||||||
@ -124,6 +123,17 @@ impl<'s> Scanner<'s> {
|
|||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Jump to an index in the source string.
|
||||||
|
pub fn jump(&mut self, index: usize) {
|
||||||
|
self.index = index;
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The full source string.
|
||||||
|
pub fn src(&self) -> &'s str {
|
||||||
|
self.src
|
||||||
|
}
|
||||||
|
|
||||||
/// Slice a part out of the source string.
|
/// Slice a part out of the source string.
|
||||||
pub fn get<I>(&self, index: I) -> &'s str
|
pub fn get<I>(&self, index: I) -> &'s str
|
||||||
where
|
where
|
||||||
@ -132,11 +142,6 @@ impl<'s> Scanner<'s> {
|
|||||||
&self.src[index]
|
&self.src[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The full source string.
|
|
||||||
pub fn src(&self) -> &'s str {
|
|
||||||
self.src
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The full source string up to the current index.
|
/// The full source string up to the current index.
|
||||||
pub fn eaten(&self) -> &'s str {
|
pub fn eaten(&self) -> &'s str {
|
||||||
&self.src[.. self.index]
|
&self.src[.. self.index]
|
||||||
@ -151,6 +156,11 @@ impl<'s> Scanner<'s> {
|
|||||||
pub fn rest(&self) -> &'s str {
|
pub fn rest(&self) -> &'s str {
|
||||||
&self.src[self.index ..]
|
&self.src[self.index ..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Go back to the where the index says.
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.iter = self.src[self.index ..].chars();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Scanner<'_> {
|
impl Debug for Scanner<'_> {
|
||||||
|
@ -14,7 +14,7 @@ use crate::syntax::*;
|
|||||||
|
|
||||||
use Decoration::*;
|
use Decoration::*;
|
||||||
use SynNode::{
|
use SynNode::{
|
||||||
Linebreak as L, Parbreak as P, Spacing as S, ToggleBolder as B, ToggleItalic as I,
|
Linebreak as L, Parbreak as P, Space as S, ToggleBolder as B, ToggleItalic as I,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn T(text: &str) -> SynNode {
|
fn T(text: &str) -> SynNode {
|
||||||
@ -80,21 +80,21 @@ fn Str(string: &str) -> Expr {
|
|||||||
|
|
||||||
macro_rules! Dict {
|
macro_rules! Dict {
|
||||||
(@dict=$dict:expr,) => {};
|
(@dict=$dict:expr,) => {};
|
||||||
(@dict=$dict:expr, $key:expr => $value:expr $(, $($tts:tt)*)?) => {{
|
(@dict=$dict:expr, $key:expr => $expr:expr $(, $($tts:tt)*)?) => {{
|
||||||
let key = Into::<Spanned<&str>>::into($key);
|
let key = Into::<Spanned<&str>>::into($key);
|
||||||
let key = key.map(Into::<DictKey>::into);
|
let key = key.map(Into::<DictKey>::into);
|
||||||
let value = Into::<Spanned<Expr>>::into($value);
|
let expr = Into::<Spanned<Expr>>::into($expr);
|
||||||
$dict.0.push(LitDictEntry { key: Some(key), value });
|
$dict.0.push(LitDictEntry { key: Some(key), expr });
|
||||||
Dict![@dict=$dict, $($($tts)*)?];
|
Dict![@dict=$dict, $($($tts)*)?];
|
||||||
}};
|
}};
|
||||||
(@dict=$dict:expr, $value:expr $(, $($tts:tt)*)?) => {
|
(@dict=$dict:expr, $expr:expr $(, $($tts:tt)*)?) => {
|
||||||
let value = Into::<Spanned<Expr>>::into($value);
|
let expr = Into::<Spanned<Expr>>::into($expr);
|
||||||
$dict.0.push(LitDictEntry { key: None, value });
|
$dict.0.push(LitDictEntry { key: None, expr });
|
||||||
Dict![@dict=$dict, $($($tts)*)?];
|
Dict![@dict=$dict, $($($tts)*)?];
|
||||||
};
|
};
|
||||||
(@$($tts:tt)*) => {{
|
(@$($tts:tt)*) => {{
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut dict = LitDict::default();
|
let mut dict = LitDict::new();
|
||||||
Dict![@dict=dict, $($tts)*];
|
Dict![@dict=dict, $($tts)*];
|
||||||
dict
|
dict
|
||||||
}};
|
}};
|
||||||
@ -344,7 +344,6 @@ fn test_parse_function_names() {
|
|||||||
fn test_parse_chaining() {
|
fn test_parse_chaining() {
|
||||||
// Things the parser has to make sense of
|
// Things the parser has to make sense of
|
||||||
t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Dict![Float(5.0), Float(2.1)], Tree![F!("you")]));
|
t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Dict![Float(5.0), Float(2.1)], Tree![F!("you")]));
|
||||||
t!("[box >>][Hi]" => F!("box"; Tree![T("Hi")]));
|
|
||||||
t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![
|
t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![
|
||||||
F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi")))
|
F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi")))
|
||||||
]));
|
]));
|
||||||
@ -354,7 +353,8 @@ fn test_parse_chaining() {
|
|||||||
|
|
||||||
// Errors for unclosed / empty predecessor groups
|
// Errors for unclosed / empty predecessor groups
|
||||||
e!("[hi: (5.0, 2.1 >> you]" => s(15, 15, "expected closing paren"));
|
e!("[hi: (5.0, 2.1 >> you]" => s(15, 15, "expected closing paren"));
|
||||||
e!("[>> abc]" => s(1, 1, "expected function name"));
|
e!("[>> abc]" => s(1, 1, "expected function name"));
|
||||||
|
e!("[box >>][Hi]" => s(7, 7, "expected function name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -482,7 +482,7 @@ fn test_parse_expressions() {
|
|||||||
|
|
||||||
// Invalid expressions.
|
// Invalid expressions.
|
||||||
v!("4pt--" => Len(Length::pt(4.0)));
|
v!("4pt--" => Len(Length::pt(4.0)));
|
||||||
e!("[val: 4pt--]" => s(10, 11, "dangling minus"),
|
e!("[val: 4pt--]" => s(10, 11, "missing factor"),
|
||||||
s(6, 10, "missing right summand"));
|
s(6, 10, "missing right summand"));
|
||||||
|
|
||||||
v!("3mm+4pt*" => Binary(Add, Len(Length::mm(3.0)), Len(Length::pt(4.0))));
|
v!("3mm+4pt*" => Binary(Add, Len(Length::mm(3.0)), Len(Length::pt(4.0))));
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
//! Tokenization.
|
//! Tokenization.
|
||||||
|
|
||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use super::{is_newline, Scanner};
|
use super::{is_newline, Scanner};
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
use crate::syntax::{is_ident, Pos, Span, SpanWith, Spanned, Token};
|
use crate::syntax::token::*;
|
||||||
|
use crate::syntax::{is_ident, Pos, Span, SpanWith, Spanned};
|
||||||
|
|
||||||
use TokenMode::*;
|
use TokenMode::*;
|
||||||
|
|
||||||
/// An iterator over the tokens of a string of source code.
|
/// An iterator over the tokens of a string of source code.
|
||||||
#[derive(Debug)]
|
#[derive(Clone)]
|
||||||
pub struct Tokens<'s> {
|
pub struct Tokens<'s> {
|
||||||
s: Scanner<'s>,
|
s: Scanner<'s>,
|
||||||
mode: TokenMode,
|
mode: TokenMode,
|
||||||
stack: Vec<TokenMode>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to tokenize in header mode which yields expression, comma and
|
/// Whether to tokenize in header mode which yields expression, comma and
|
||||||
@ -26,30 +28,34 @@ pub enum TokenMode {
|
|||||||
impl<'s> Tokens<'s> {
|
impl<'s> Tokens<'s> {
|
||||||
/// Create a new token iterator with the given mode.
|
/// Create a new token iterator with the given mode.
|
||||||
pub fn new(src: &'s str, mode: TokenMode) -> Self {
|
pub fn new(src: &'s str, mode: TokenMode) -> Self {
|
||||||
Self {
|
Self { s: Scanner::new(src), mode }
|
||||||
s: Scanner::new(src),
|
|
||||||
mode,
|
|
||||||
stack: vec![],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the token mode and push the old one on a stack.
|
/// Get the current token mode.
|
||||||
pub fn push_mode(&mut self, mode: TokenMode) {
|
pub fn mode(&self) -> TokenMode {
|
||||||
self.stack.push(self.mode);
|
self.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the token mode.
|
||||||
|
pub fn set_mode(&mut self, mode: TokenMode) {
|
||||||
self.mode = mode;
|
self.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop the old token mode from the stack. This panics if there is no mode
|
|
||||||
/// on the stack.
|
|
||||||
pub fn pop_mode(&mut self) {
|
|
||||||
self.mode = self.stack.pop().expect("no pushed mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The position in the string at which the last token ends and next token
|
/// The position in the string at which the last token ends and next token
|
||||||
/// will start.
|
/// will start.
|
||||||
pub fn pos(&self) -> Pos {
|
pub fn pos(&self) -> Pos {
|
||||||
self.s.index().into()
|
self.s.index().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Jump to a position in the source string.
|
||||||
|
pub fn jump(&mut self, pos: Pos) {
|
||||||
|
self.s.jump(pos.to_usize());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The underlying scanner.
|
||||||
|
pub fn scanner(&self) -> &Scanner<'s> {
|
||||||
|
&self.s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Iterator for Tokens<'s> {
|
impl<'s> Iterator for Tokens<'s> {
|
||||||
@ -59,8 +65,12 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let start = self.s.index();
|
let start = self.s.index();
|
||||||
let token = match self.s.eat()? {
|
let token = match self.s.eat()? {
|
||||||
// Whitespace.
|
// Whitespace with fast path for just a single space.
|
||||||
c if c.is_whitespace() => self.read_whitespace(c),
|
' ' if !self.s.check(|c| c.is_whitespace()) => Token::Space(0),
|
||||||
|
c if c.is_whitespace() => {
|
||||||
|
self.s.jump(start);
|
||||||
|
self.read_whitespace()
|
||||||
|
}
|
||||||
|
|
||||||
// Comments.
|
// Comments.
|
||||||
'/' if self.s.eat_if('/') => self.read_line_comment(),
|
'/' if self.s.eat_if('/') => self.read_line_comment(),
|
||||||
@ -76,8 +86,8 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
// Syntactic elements in body text.
|
// Syntactic elements in body text.
|
||||||
'*' if self.mode == Body => Token::Star,
|
'*' if self.mode == Body => Token::Star,
|
||||||
'_' if self.mode == Body => Token::Underscore,
|
'_' if self.mode == Body => Token::Underscore,
|
||||||
'`' if self.mode == Body => self.read_raw(),
|
|
||||||
'#' if self.mode == Body => Token::Hashtag,
|
'#' if self.mode == Body => Token::Hashtag,
|
||||||
|
'`' if self.mode == Body => self.read_raw(),
|
||||||
'~' if self.mode == Body => Token::Text("\u{00A0}"),
|
'~' if self.mode == Body => Token::Text("\u{00A0}"),
|
||||||
'\\' if self.mode == Body => self.read_escaped(),
|
'\\' if self.mode == Body => self.read_escaped(),
|
||||||
|
|
||||||
@ -88,12 +98,12 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
',' if self.mode == Header => Token::Comma,
|
',' if self.mode == Header => Token::Comma,
|
||||||
'=' if self.mode == Header => Token::Equals,
|
'=' if self.mode == Header => Token::Equals,
|
||||||
'>' if self.mode == Header && self.s.eat_if('>') => Token::Chain,
|
'>' if self.mode == Header && self.s.eat_if('>') => Token::Chain,
|
||||||
|
|
||||||
// Expressions in headers.
|
|
||||||
'+' if self.mode == Header => Token::Plus,
|
'+' if self.mode == Header => Token::Plus,
|
||||||
'-' if self.mode == Header => Token::Hyphen,
|
'-' if self.mode == Header => Token::Hyphen,
|
||||||
'*' if self.mode == Header => Token::Star,
|
'*' if self.mode == Header => Token::Star,
|
||||||
'/' if self.mode == Header => Token::Slash,
|
'/' if self.mode == Header => Token::Slash,
|
||||||
|
|
||||||
|
// Expressions in headers.
|
||||||
'#' if self.mode == Header => self.read_hex(),
|
'#' if self.mode == Header => self.read_hex(),
|
||||||
'"' if self.mode == Header => self.read_string(),
|
'"' if self.mode == Header => self.read_string(),
|
||||||
|
|
||||||
@ -107,18 +117,7 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Tokens<'s> {
|
impl<'s> Tokens<'s> {
|
||||||
fn read_whitespace(&mut self, first: char) -> Token<'s> {
|
fn read_whitespace(&mut self) -> Token<'s> {
|
||||||
// Shortcut for common case of exactly one space.
|
|
||||||
if first == ' ' && !self.s.check(|c| c.is_whitespace()) {
|
|
||||||
return Token::Space(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uneat the first char if it's a newline, so that it's counted in the
|
|
||||||
// loop.
|
|
||||||
if is_newline(first) {
|
|
||||||
self.s.uneat();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count the number of newlines.
|
// Count the number of newlines.
|
||||||
let mut newlines = 0;
|
let mut newlines = 0;
|
||||||
while let Some(c) = self.s.eat_merging_crlf() {
|
while let Some(c) = self.s.eat_merging_crlf() {
|
||||||
@ -169,27 +168,6 @@ impl<'s> Tokens<'s> {
|
|||||||
Token::BlockComment(self.s.get(start .. end))
|
Token::BlockComment(self.s.get(start .. end))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_hex(&mut self) -> Token<'s> {
|
|
||||||
// This parses more than the permissable 0-9, a-f, A-F character ranges
|
|
||||||
// to provide nicer error messages later.
|
|
||||||
Token::Hex(self.s.eat_while(|c| c.is_ascii_alphanumeric()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_string(&mut self) -> Token<'s> {
|
|
||||||
let mut escaped = false;
|
|
||||||
Token::Str {
|
|
||||||
string: self.s.eat_until(|c| {
|
|
||||||
if c == '"' && !escaped {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
escaped = c == '\\' && !escaped;
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
terminated: self.s.eat_if('"'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_raw(&mut self) -> Token<'s> {
|
fn read_raw(&mut self) -> Token<'s> {
|
||||||
let mut backticks = 1;
|
let mut backticks = 1;
|
||||||
while self.s.eat_if('`') {
|
while self.s.eat_if('`') {
|
||||||
@ -210,11 +188,11 @@ impl<'s> Tokens<'s> {
|
|||||||
let terminated = found == backticks;
|
let terminated = found == backticks;
|
||||||
let end = self.s.index() - if terminated { found } else { 0 };
|
let end = self.s.index() - if terminated { found } else { 0 };
|
||||||
|
|
||||||
Token::Raw {
|
Token::Raw(TokenRaw {
|
||||||
raw: self.s.get(start .. end),
|
text: self.s.get(start .. end),
|
||||||
backticks,
|
backticks,
|
||||||
terminated,
|
terminated,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_escaped(&mut self) -> Token<'s> {
|
fn read_escaped(&mut self) -> Token<'s> {
|
||||||
@ -228,10 +206,10 @@ impl<'s> Tokens<'s> {
|
|||||||
'u' if self.s.peek_nth(1) == Some('{') => {
|
'u' if self.s.peek_nth(1) == Some('{') => {
|
||||||
self.s.eat_assert('u');
|
self.s.eat_assert('u');
|
||||||
self.s.eat_assert('{');
|
self.s.eat_assert('{');
|
||||||
Token::UnicodeEscape {
|
Token::UnicodeEscape(TokenUnicodeEscape {
|
||||||
sequence: self.s.eat_while(|c| c.is_ascii_hexdigit()),
|
sequence: self.s.eat_while(|c| c.is_ascii_hexdigit()),
|
||||||
terminated: self.s.eat_if('}'),
|
terminated: self.s.eat_if('}'),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
c if c.is_whitespace() => Token::Backslash,
|
c if c.is_whitespace() => Token::Backslash,
|
||||||
_ => Token::Text("\\"),
|
_ => Token::Text("\\"),
|
||||||
@ -241,6 +219,27 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_hex(&mut self) -> Token<'s> {
|
||||||
|
// This parses more than the permissable 0-9, a-f, A-F character ranges
|
||||||
|
// to provide nicer error messages later.
|
||||||
|
Token::Hex(self.s.eat_while(|c| c.is_ascii_alphanumeric()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_string(&mut self) -> Token<'s> {
|
||||||
|
let mut escaped = false;
|
||||||
|
Token::Str(TokenStr {
|
||||||
|
string: self.s.eat_until(|c| {
|
||||||
|
if c == '"' && !escaped {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
escaped = c == '\\' && !escaped;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
terminated: self.s.eat_if('"'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn read_text_or_expr(&mut self, start: usize) -> Token<'s> {
|
fn read_text_or_expr(&mut self, start: usize) -> Token<'s> {
|
||||||
let body = self.mode == Body;
|
let body = self.mode == Body;
|
||||||
let header = self.mode == Header;
|
let header = self.mode == Header;
|
||||||
@ -268,6 +267,12 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Tokens<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Tokens({}|{})", self.s.eaten(), self.s.rest())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_expr(text: &str) -> Token<'_> {
|
fn parse_expr(text: &str) -> Token<'_> {
|
||||||
if let Ok(b) = text.parse::<bool>() {
|
if let Ok(b) = text.parse::<bool>() {
|
||||||
Token::Bool(b)
|
Token::Bool(b)
|
||||||
@ -303,13 +308,13 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn Str(string: &str, terminated: bool) -> Token {
|
fn Str(string: &str, terminated: bool) -> Token {
|
||||||
Token::Str { string, terminated }
|
Token::Str(TokenStr { string, terminated })
|
||||||
}
|
}
|
||||||
fn Raw(raw: &str, backticks: usize, terminated: bool) -> Token {
|
fn Raw(text: &str, backticks: usize, terminated: bool) -> Token {
|
||||||
Token::Raw { raw, backticks, terminated }
|
Token::Raw(TokenRaw { text, backticks, terminated })
|
||||||
}
|
}
|
||||||
fn UE(sequence: &str, terminated: bool) -> Token {
|
fn UE(sequence: &str, terminated: bool) -> Token {
|
||||||
Token::UnicodeEscape { sequence, terminated }
|
Token::UnicodeEscape(TokenUnicodeEscape { sequence, terminated })
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! t { ($($tts:tt)*) => {test!(@spans=false, $($tts)*)} }
|
macro_rules! t { ($($tts:tt)*) => {test!(@spans=false, $($tts)*)} }
|
||||||
@ -388,64 +393,6 @@ mod tests {
|
|||||||
t!(Body, "````\n```js\nalert()\n```\n````" => Raw("\n```js\nalert()\n```\n", 4, true));
|
t!(Body, "````\n```js\nalert()\n```\n````" => Raw("\n```js\nalert()\n```\n", 4, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tokenize_header_tokens() {
|
|
||||||
t!(Header, "__main__" => Id("__main__"));
|
|
||||||
t!(Header, "_func_box" => Id("_func_box"));
|
|
||||||
t!(Header, ">main" => Invalid(">main"));
|
|
||||||
t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma);
|
|
||||||
t!(Header, "{abc}" => LB, Id("abc"), RB);
|
|
||||||
t!(Header, "(1,2)" => LP, Num(1.0), Comma, Num(2.0), RP);
|
|
||||||
t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0)));
|
|
||||||
t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), Chain, S(0), Id("g"));
|
|
||||||
t!(Header, "=3.14" => Equals, Num(3.14));
|
|
||||||
t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1"));
|
|
||||||
t!(Header, "a:b" => Id("a"), Colon, Id("b"));
|
|
||||||
t!(Header, "(){}:=," => LP, RP, LB, RB, Colon, Equals, Comma);
|
|
||||||
t!(Body, "c=d, " => T("c=d,"), S(0));
|
|
||||||
t!(Body, "a: b" => T("a:"), S(0), T("b"));
|
|
||||||
t!(Header, "a: true, x=1" => Id("a"), Colon, S(0), Bool(true), Comma, S(0),
|
|
||||||
Id("x"), Equals, Num(1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tokenize_numeric_values() {
|
|
||||||
t!(Header, "12.3e5" => Num(12.3e5));
|
|
||||||
t!(Header, "120%" => Num(1.2));
|
|
||||||
t!(Header, "12e4%" => Num(1200.0));
|
|
||||||
t!(Header, "1e5in" => Len(Length::inches(100000.0)));
|
|
||||||
t!(Header, "2.3cm" => Len(Length::cm(2.3)));
|
|
||||||
t!(Header, "02.4mm" => Len(Length::mm(2.4)));
|
|
||||||
t!(Header, "2.4.cm" => Invalid("2.4.cm"));
|
|
||||||
t!(Header, "#6ae6dd" => Hex("6ae6dd"));
|
|
||||||
t!(Header, "#8A083c" => Hex("8A083c"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tokenize_strings() {
|
|
||||||
t!(Body, "a \"hi\" string" => T("a"), S(0), T("\"hi\""), S(0), T("string"));
|
|
||||||
t!(Header, "\"hello" => Str("hello", false));
|
|
||||||
t!(Header, "\"hello world\"" => Str("hello world", true));
|
|
||||||
t!(Header, "\"hello\nworld\"" => Str("hello\nworld", true));
|
|
||||||
t!(Header, r#"1"hello\nworld"false"# => Num(1.0), Str("hello\\nworld", true), Bool(false));
|
|
||||||
t!(Header, r#""a\"bc""# => Str(r#"a\"bc"#, true));
|
|
||||||
t!(Header, r#""a\\"bc""# => Str(r#"a\\"#, true), Id("bc"), Str("", false));
|
|
||||||
t!(Header, r#""a\tbc"# => Str("a\\tbc", false));
|
|
||||||
t!(Header, "\"🌎\"" => Str("🌎", true));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tokenize_math() {
|
|
||||||
t!(Header, "12e-3in" => Len(Length::inches(12e-3)));
|
|
||||||
t!(Header, "-1" => Min, Num(1.0));
|
|
||||||
t!(Header, "--1" => Min, Min, Num(1.0));
|
|
||||||
t!(Header, "- 1" => Min, S(0), Num(1.0));
|
|
||||||
t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)),
|
|
||||||
Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0));
|
|
||||||
t!(Header, "(5 - 1) / 2.1" => LP, Num(5.0), S(0), Min, S(0), Num(1.0), RP,
|
|
||||||
S(0), Slash, S(0), Num(2.1));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_escaped_symbols() {
|
fn tokenize_escaped_symbols() {
|
||||||
t!(Body, r"\\" => T(r"\"));
|
t!(Body, r"\\" => T(r"\"));
|
||||||
@ -475,6 +422,64 @@ mod tests {
|
|||||||
t!(Header, r"\," => Invalid(r"\"), Comma);
|
t!(Header, r"\," => Invalid(r"\"), Comma);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokenize_header_tokens() {
|
||||||
|
t!(Header, "__main__" => Id("__main__"));
|
||||||
|
t!(Header, "_func_box" => Id("_func_box"));
|
||||||
|
t!(Header, ">main" => Invalid(">main"));
|
||||||
|
t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma);
|
||||||
|
t!(Header, "{abc}" => LB, Id("abc"), RB);
|
||||||
|
t!(Header, "(1,2)" => LP, Num(1.0), Comma, Num(2.0), RP);
|
||||||
|
t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0)));
|
||||||
|
t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), Chain, S(0), Id("g"));
|
||||||
|
t!(Header, "=3.14" => Equals, Num(3.14));
|
||||||
|
t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1"));
|
||||||
|
t!(Header, "a:b" => Id("a"), Colon, Id("b"));
|
||||||
|
t!(Header, "(){}:=," => LP, RP, LB, RB, Colon, Equals, Comma);
|
||||||
|
t!(Body, "c=d, " => T("c=d,"), S(0));
|
||||||
|
t!(Body, "a: b" => T("a:"), S(0), T("b"));
|
||||||
|
t!(Header, "a: true, x=1" => Id("a"), Colon, S(0), Bool(true), Comma, S(0),
|
||||||
|
Id("x"), Equals, Num(1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokenize_numeric_values() {
|
||||||
|
t!(Header, "12.3e5" => Num(12.3e5));
|
||||||
|
t!(Header, "120%" => Num(1.2));
|
||||||
|
t!(Header, "12e4%" => Num(1200.0));
|
||||||
|
t!(Header, "1e5in" => Len(Length::inches(100000.0)));
|
||||||
|
t!(Header, "2.3cm" => Len(Length::cm(2.3)));
|
||||||
|
t!(Header, "02.4mm" => Len(Length::mm(2.4)));
|
||||||
|
t!(Header, "2.4.cm" => Invalid("2.4.cm"));
|
||||||
|
t!(Header, "#6ae6dd" => Hex("6ae6dd"));
|
||||||
|
t!(Header, "#8A083c" => Hex("8A083c"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokenize_strings() {
|
||||||
|
t!(Body, "a \"hi\" string" => T("a"), S(0), T("\"hi\""), S(0), T("string"));
|
||||||
|
t!(Header, "\"hello" => Str("hello", false));
|
||||||
|
t!(Header, "\"hello world\"" => Str("hello world", true));
|
||||||
|
t!(Header, "\"hello\nworld\"" => Str("hello\nworld", true));
|
||||||
|
t!(Header, r#"1"hello\nworld"false"# => Num(1.0), Str("hello\\nworld", true), Bool(false));
|
||||||
|
t!(Header, r#""a\"bc""# => Str(r#"a\"bc"#, true));
|
||||||
|
t!(Header, r#""a\\"bc""# => Str(r#"a\\"#, true), Id("bc"), Str("", false));
|
||||||
|
t!(Header, r#""a\tbc"# => Str("a\\tbc", false));
|
||||||
|
t!(Header, "\"🌎\"" => Str("🌎", true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokenize_math() {
|
||||||
|
t!(Header, "12e-3in" => Len(Length::inches(12e-3)));
|
||||||
|
t!(Header, "-1" => Min, Num(1.0));
|
||||||
|
t!(Header, "--1" => Min, Min, Num(1.0));
|
||||||
|
t!(Header, "- 1" => Min, S(0), Num(1.0));
|
||||||
|
t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)),
|
||||||
|
Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0));
|
||||||
|
t!(Header, "(5 - 1) / 2.1" => LP, Num(5.0), S(0), Min, S(0), Num(1.0), RP,
|
||||||
|
S(0), Slash, S(0), Num(2.1));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_with_spans() {
|
fn tokenize_with_spans() {
|
||||||
ts!(Body, "hello" => s(0, 5, T("hello")));
|
ts!(Body, "hello" => s(0, 5, T("hello")));
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
//! Expressions.
|
//! Expressions.
|
||||||
|
|
||||||
use super::span::{SpanWith, Spanned};
|
|
||||||
use super::{Decoration, Ident, Lit, LitDict};
|
|
||||||
use crate::eval::Value;
|
use crate::eval::Value;
|
||||||
use crate::layout::LayoutContext;
|
use crate::layout::LayoutContext;
|
||||||
|
use crate::syntax::{Decoration, Ident, Lit, LitDict, SpanWith, Spanned};
|
||||||
use crate::Feedback;
|
use crate::Feedback;
|
||||||
|
|
||||||
/// An expression.
|
/// An expression.
|
||||||
@ -50,7 +49,7 @@ impl ExprUnary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A unary operator.
|
/// A unary operator.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum UnOp {
|
pub enum UnOp {
|
||||||
/// The negation operator: `-`.
|
/// The negation operator: `-`.
|
||||||
Neg,
|
Neg,
|
||||||
@ -80,7 +79,7 @@ impl ExprBinary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A binary operator.
|
/// A binary operator.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum BinOp {
|
pub enum BinOp {
|
||||||
/// The addition operator: `+`.
|
/// The addition operator: `+`.
|
||||||
Add,
|
Add,
|
@ -1,10 +1,10 @@
|
|||||||
//! Literals.
|
//! Literals.
|
||||||
|
|
||||||
use super::{Expr, Ident, SpanWith, Spanned, SynTree};
|
|
||||||
use crate::color::RgbaColor;
|
use crate::color::RgbaColor;
|
||||||
use crate::eval::{DictKey, DictValue, SpannedEntry, Value};
|
use crate::eval::{DictKey, DictValue, SpannedEntry, Value};
|
||||||
use crate::layout::LayoutContext;
|
use crate::layout::LayoutContext;
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
|
use crate::syntax::{Expr, Ident, SpanWith, Spanned, SynTree};
|
||||||
use crate::{DynFuture, Feedback};
|
use crate::{DynFuture, Feedback};
|
||||||
|
|
||||||
/// A literal.
|
/// A literal.
|
||||||
@ -55,7 +55,7 @@ impl Lit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A dictionary literal: `(false, 12cm, greeting = "hi")`.
|
/// A dictionary literal: `(false, 12cm, greeting = "hi")`.
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct LitDict(pub Vec<LitDictEntry>);
|
pub struct LitDict(pub Vec<LitDictEntry>);
|
||||||
|
|
||||||
impl LitDict {
|
impl LitDict {
|
||||||
@ -74,8 +74,8 @@ impl LitDict {
|
|||||||
let mut dict = DictValue::new();
|
let mut dict = DictValue::new();
|
||||||
|
|
||||||
for entry in &self.0 {
|
for entry in &self.0 {
|
||||||
let val = entry.value.v.eval(ctx, f).await;
|
let val = entry.expr.v.eval(ctx, f).await;
|
||||||
let spanned = val.span_with(entry.value.span);
|
let spanned = val.span_with(entry.expr.span);
|
||||||
if let Some(key) = &entry.key {
|
if let Some(key) = &entry.key {
|
||||||
dict.insert(&key.v, SpannedEntry::new(key.span, spanned));
|
dict.insert(&key.v, SpannedEntry::new(key.span, spanned));
|
||||||
} else {
|
} else {
|
||||||
@ -94,5 +94,5 @@ pub struct LitDictEntry {
|
|||||||
/// The key of the entry if there was one: `greeting`.
|
/// The key of the entry if there was one: `greeting`.
|
||||||
pub key: Option<Spanned<DictKey>>,
|
pub key: Option<Spanned<DictKey>>,
|
||||||
/// The value of the entry: `"hi"`.
|
/// The value of the entry: `"hi"`.
|
||||||
pub value: Spanned<Expr>,
|
pub expr: Spanned<Expr>,
|
||||||
}
|
}
|
9
src/syntax/ast/mod.rs
Normal file
9
src/syntax/ast/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//! Abstract syntax tree definition.
|
||||||
|
|
||||||
|
mod expr;
|
||||||
|
mod lit;
|
||||||
|
mod tree;
|
||||||
|
|
||||||
|
pub use expr::*;
|
||||||
|
pub use lit::*;
|
||||||
|
pub use tree::*;
|
@ -1,7 +1,6 @@
|
|||||||
//! The syntax tree.
|
//! The syntax tree.
|
||||||
|
|
||||||
use super::span::{SpanVec, Spanned};
|
use crate::syntax::{Expr, Ident, SpanVec, Spanned};
|
||||||
use super::{Expr, Ident};
|
|
||||||
|
|
||||||
/// A collection of nodes which form a tree together with the nodes' children.
|
/// A collection of nodes which form a tree together with the nodes' children.
|
||||||
pub type SynTree = SpanVec<SynNode>;
|
pub type SynTree = SpanVec<SynNode>;
|
||||||
@ -11,7 +10,10 @@ pub type SynTree = SpanVec<SynNode>;
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum SynNode {
|
pub enum SynNode {
|
||||||
/// Whitespace containing less than two newlines.
|
/// Whitespace containing less than two newlines.
|
||||||
Spacing,
|
Space,
|
||||||
|
/// Plain text.
|
||||||
|
Text(String),
|
||||||
|
|
||||||
/// A forced line break.
|
/// A forced line break.
|
||||||
Linebreak,
|
Linebreak,
|
||||||
/// A paragraph break.
|
/// A paragraph break.
|
||||||
@ -20,16 +22,25 @@ pub enum SynNode {
|
|||||||
ToggleItalic,
|
ToggleItalic,
|
||||||
/// Bolder was enabled / disabled.
|
/// Bolder was enabled / disabled.
|
||||||
ToggleBolder,
|
ToggleBolder,
|
||||||
/// Plain text.
|
|
||||||
Text(String),
|
|
||||||
/// An optionally syntax-highlighted raw block.
|
|
||||||
Raw(NodeRaw),
|
|
||||||
/// A section heading.
|
/// A section heading.
|
||||||
Heading(NodeHeading),
|
Heading(NodeHeading),
|
||||||
|
/// An optionally syntax-highlighted raw block.
|
||||||
|
Raw(NodeRaw),
|
||||||
|
|
||||||
/// An expression.
|
/// An expression.
|
||||||
Expr(Expr),
|
Expr(Expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A section heading.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct NodeHeading {
|
||||||
|
/// The section depth (how many hashtags minus 1).
|
||||||
|
pub level: Spanned<u8>,
|
||||||
|
/// The contents of the heading.
|
||||||
|
pub contents: SynTree,
|
||||||
|
}
|
||||||
|
|
||||||
/// A raw block, rendered in monospace with optional syntax highlighting.
|
/// A raw block, rendered in monospace with optional syntax highlighting.
|
||||||
///
|
///
|
||||||
/// Raw blocks start with an arbitrary number of backticks and end with the same
|
/// Raw blocks start with an arbitrary number of backticks and end with the same
|
||||||
@ -108,12 +119,3 @@ pub struct NodeRaw {
|
|||||||
/// are inline-level when they contain no newlines.
|
/// are inline-level when they contain no newlines.
|
||||||
pub inline: bool,
|
pub inline: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A section heading.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct NodeHeading {
|
|
||||||
/// The section depth (how many hashtags minus 1).
|
|
||||||
pub level: Spanned<u8>,
|
|
||||||
/// The contents of the heading.
|
|
||||||
pub contents: SynTree,
|
|
||||||
}
|
|
@ -1,19 +1,10 @@
|
|||||||
//! Syntax types.
|
//! Syntax types.
|
||||||
|
|
||||||
mod expr;
|
pub mod ast;
|
||||||
mod ident;
|
pub mod token;
|
||||||
mod lit;
|
|
||||||
mod span;
|
|
||||||
mod token;
|
|
||||||
mod tree;
|
|
||||||
|
|
||||||
/// Abstract syntax tree definition.
|
mod ident;
|
||||||
pub mod ast {
|
mod span;
|
||||||
use super::*;
|
|
||||||
pub use expr::*;
|
|
||||||
pub use lit::*;
|
|
||||||
pub use tree::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use ast::*;
|
pub use ast::*;
|
||||||
pub use ident::*;
|
pub use ident::*;
|
||||||
|
@ -13,7 +13,7 @@ thread_local! {
|
|||||||
/// Annotate a value with a span.
|
/// Annotate a value with a span.
|
||||||
pub trait SpanWith: Sized {
|
pub trait SpanWith: Sized {
|
||||||
/// Wraps `self` in a `Spanned` with the given span.
|
/// Wraps `self` in a `Spanned` with the given span.
|
||||||
fn span_with(self, span: Span) -> Spanned<Self> {
|
fn span_with(self, span: impl Into<Span>) -> Spanned<Self> {
|
||||||
Spanned::new(self, span)
|
Spanned::new(self, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,8 +50,8 @@ pub struct Spanned<T> {
|
|||||||
|
|
||||||
impl<T> Spanned<T> {
|
impl<T> Spanned<T> {
|
||||||
/// Create a new instance from a value and its span.
|
/// Create a new instance from a value and its span.
|
||||||
pub fn new(v: T, span: Span) -> Self {
|
pub fn new(v: T, span: impl Into<Span>) -> Self {
|
||||||
Self { v, span }
|
Self { v, span: span.into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new instance from a value with the zero span.
|
/// Create a new instance from a value with the zero span.
|
||||||
@ -123,16 +123,16 @@ impl Span {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new span with the earlier start and later end position.
|
/// Create a new span with the earlier start and later end position.
|
||||||
pub fn merge(a: Self, b: Self) -> Self {
|
pub fn join(self, other: Self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
start: a.start.min(b.start),
|
start: self.start.min(other.start),
|
||||||
end: a.end.max(b.end),
|
end: self.end.max(other.end),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand a span by merging it with another span.
|
/// Expand a span by merging it with another span.
|
||||||
pub fn expand(&mut self, other: Self) {
|
pub fn expand(&mut self, other: Self) {
|
||||||
*self = Self::merge(*self, other)
|
*self = self.join(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When set to `false` comparisons with `PartialEq` ignore spans.
|
/// When set to `false` comparisons with `PartialEq` ignore spans.
|
||||||
@ -164,6 +164,24 @@ impl PartialEq for Span {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for Span
|
||||||
|
where
|
||||||
|
T: Into<Pos> + Copy,
|
||||||
|
{
|
||||||
|
fn from(pos: T) -> Self {
|
||||||
|
Self::at(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<(T, T)> for Span
|
||||||
|
where
|
||||||
|
T: Into<Pos>,
|
||||||
|
{
|
||||||
|
fn from((start, end): (T, T)) -> Self {
|
||||||
|
Self::new(start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for Span {
|
impl Debug for Span {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "<{:?}-{:?}>", self.start, self.end)
|
write!(f, "<{:?}-{:?}>", self.start, self.end)
|
||||||
@ -185,6 +203,12 @@ impl Pos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Offset for Pos {
|
||||||
|
fn offset(self, by: Self) -> Self {
|
||||||
|
Pos(self.0 + by.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u32> for Pos {
|
impl From<u32> for Pos {
|
||||||
fn from(index: u32) -> Self {
|
fn from(index: u32) -> Self {
|
||||||
Self(index)
|
Self(index)
|
||||||
@ -197,12 +221,6 @@ impl From<usize> for Pos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Offset for Pos {
|
|
||||||
fn offset(self, by: Self) -> Self {
|
|
||||||
Pos(self.0 + by.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Pos {
|
impl Debug for Pos {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
Debug::fmt(&self.0, f)
|
Debug::fmt(&self.0, f)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Tokenization.
|
//! Token definition.
|
||||||
|
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
|
|
||||||
@ -8,6 +8,8 @@ pub enum Token<'s> {
|
|||||||
/// One or more whitespace characters. The contained `usize` denotes the
|
/// One or more whitespace characters. The contained `usize` denotes the
|
||||||
/// number of newlines that were contained in the whitespace.
|
/// number of newlines that were contained in the whitespace.
|
||||||
Space(usize),
|
Space(usize),
|
||||||
|
/// A consecutive non-markup string.
|
||||||
|
Text(&'s str),
|
||||||
|
|
||||||
/// A line comment with inner string contents `//<str>\n`.
|
/// A line comment with inner string contents `//<str>\n`.
|
||||||
LineComment(&'s str),
|
LineComment(&'s str),
|
||||||
@ -15,6 +17,20 @@ pub enum Token<'s> {
|
|||||||
/// can contain nested block comments.
|
/// can contain nested block comments.
|
||||||
BlockComment(&'s str),
|
BlockComment(&'s str),
|
||||||
|
|
||||||
|
/// A star. It can appear in a function header where it signifies the
|
||||||
|
/// multiplication of expressions or the body where it modifies the styling.
|
||||||
|
Star,
|
||||||
|
/// An underscore in body-text.
|
||||||
|
Underscore,
|
||||||
|
/// A backslash followed by whitespace in text.
|
||||||
|
Backslash,
|
||||||
|
/// A hashtag indicating a section heading.
|
||||||
|
Hashtag,
|
||||||
|
/// A raw block.
|
||||||
|
Raw(TokenRaw<'s>),
|
||||||
|
/// A unicode escape sequence.
|
||||||
|
UnicodeEscape(TokenUnicodeEscape<'s>),
|
||||||
|
|
||||||
/// A left bracket starting a function invocation or body: `[`.
|
/// A left bracket starting a function invocation or body: `[`.
|
||||||
LeftBracket,
|
LeftBracket,
|
||||||
/// A right bracket ending a function invocation or body: `]`.
|
/// A right bracket ending a function invocation or body: `]`.
|
||||||
@ -28,37 +44,14 @@ pub enum Token<'s> {
|
|||||||
/// A right parenthesis in a function header: `)`.
|
/// A right parenthesis in a function header: `)`.
|
||||||
RightParen,
|
RightParen,
|
||||||
|
|
||||||
/// A double forward chevron in a function header: `>>`.
|
|
||||||
Chain,
|
|
||||||
|
|
||||||
/// A colon in a function header: `:`.
|
/// A colon in a function header: `:`.
|
||||||
Colon,
|
Colon,
|
||||||
/// A comma in a function header: `,`.
|
/// A comma in a function header: `,`.
|
||||||
Comma,
|
Comma,
|
||||||
/// An equals sign in a function header: `=`.
|
/// An equals sign in a function header: `=`.
|
||||||
Equals,
|
Equals,
|
||||||
|
/// A double forward chevron in a function header: `>>`.
|
||||||
/// An identifier in a function header: `center`.
|
Chain,
|
||||||
Ident(&'s str),
|
|
||||||
/// A quoted string in a function header: `"..."`.
|
|
||||||
Str {
|
|
||||||
/// The string inside the quotes.
|
|
||||||
///
|
|
||||||
/// _Note_: If the string contains escape sequences these are not yet
|
|
||||||
/// applied to be able to just store a string slice here instead of
|
|
||||||
/// a String. The escaping is done later in the parser.
|
|
||||||
string: &'s str,
|
|
||||||
/// Whether the closing quote was present.
|
|
||||||
terminated: bool,
|
|
||||||
},
|
|
||||||
/// A boolean in a function header: `true | false`.
|
|
||||||
Bool(bool),
|
|
||||||
/// A number in a function header: `3.14`.
|
|
||||||
Number(f64),
|
|
||||||
/// A length in a function header: `12pt`.
|
|
||||||
Length(Length),
|
|
||||||
/// A hex value in a function header: `#20d82a`.
|
|
||||||
Hex(&'s str),
|
|
||||||
/// A plus in a function header, signifying the addition of expressions.
|
/// A plus in a function header, signifying the addition of expressions.
|
||||||
Plus,
|
Plus,
|
||||||
/// A hyphen in a function header, signifying the subtraction of
|
/// A hyphen in a function header, signifying the subtraction of
|
||||||
@ -67,75 +60,95 @@ pub enum Token<'s> {
|
|||||||
/// A slash in a function header, signifying the division of expressions.
|
/// A slash in a function header, signifying the division of expressions.
|
||||||
Slash,
|
Slash,
|
||||||
|
|
||||||
/// A star. It can appear in a function header where it signifies the
|
/// An identifier in a function header: `center`.
|
||||||
/// multiplication of expressions or the body where it modifies the styling.
|
Ident(&'s str),
|
||||||
Star,
|
/// A boolean in a function header: `true | false`.
|
||||||
/// An underscore in body-text.
|
Bool(bool),
|
||||||
Underscore,
|
/// A number in a function header: `3.14`.
|
||||||
/// A backslash followed by whitespace in text.
|
Number(f64),
|
||||||
Backslash,
|
/// A length in a function header: `12pt`.
|
||||||
|
Length(Length),
|
||||||
/// A hashtag token in the body can indicate compute mode or headings.
|
/// A hex value in a function header: `#20d82a`.
|
||||||
Hashtag,
|
Hex(&'s str),
|
||||||
|
/// A quoted string in a function header: `"..."`.
|
||||||
/// A unicode escape sequence.
|
Str(TokenStr<'s>),
|
||||||
UnicodeEscape {
|
|
||||||
/// The escape sequence between two braces.
|
|
||||||
sequence: &'s str,
|
|
||||||
/// Whether the closing brace was present.
|
|
||||||
terminated: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Raw block.
|
|
||||||
Raw {
|
|
||||||
/// The raw text between the backticks.
|
|
||||||
raw: &'s str,
|
|
||||||
/// The number of opening backticks.
|
|
||||||
backticks: usize,
|
|
||||||
/// Whether all closing backticks were present.
|
|
||||||
terminated: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Any other consecutive string.
|
|
||||||
Text(&'s str),
|
|
||||||
|
|
||||||
/// Things that are not valid in the context they appeared in.
|
/// Things that are not valid in the context they appeared in.
|
||||||
Invalid(&'s str),
|
Invalid(&'s str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A quoted string in a function header: `"..."`.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub struct TokenStr<'s> {
|
||||||
|
/// The string inside the quotes.
|
||||||
|
///
|
||||||
|
/// _Note_: If the string contains escape sequences these are not yet
|
||||||
|
/// applied to be able to just store a string slice here instead of
|
||||||
|
/// a `String`. The resolving is done later in the parser.
|
||||||
|
pub string: &'s str,
|
||||||
|
/// Whether the closing quote was present.
|
||||||
|
pub terminated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A unicode escape sequence.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub struct TokenUnicodeEscape<'s> {
|
||||||
|
/// The escape sequence between two braces.
|
||||||
|
pub sequence: &'s str,
|
||||||
|
/// Whether the closing brace was present.
|
||||||
|
pub terminated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A raw block.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub struct TokenRaw<'s> {
|
||||||
|
/// The raw text between the backticks.
|
||||||
|
pub text: &'s str,
|
||||||
|
/// The number of opening backticks.
|
||||||
|
pub backticks: usize,
|
||||||
|
/// Whether all closing backticks were present.
|
||||||
|
pub terminated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'s> Token<'s> {
|
impl<'s> Token<'s> {
|
||||||
/// The natural-language name for this token for use in error messages.
|
/// The natural-language name for this token for use in error messages.
|
||||||
pub fn name(self) -> &'static str {
|
pub fn name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Space(_) => "space",
|
Self::Space(_) => "space",
|
||||||
|
Self::Text(_) => "text",
|
||||||
|
|
||||||
Self::LineComment(_) => "line comment",
|
Self::LineComment(_) => "line comment",
|
||||||
Self::BlockComment(_) => "block comment",
|
Self::BlockComment(_) => "block comment",
|
||||||
Self::LeftBracket => "opening bracket",
|
|
||||||
Self::RightBracket => "closing bracket",
|
|
||||||
Self::LeftParen => "opening paren",
|
|
||||||
Self::RightParen => "closing paren",
|
|
||||||
Self::LeftBrace => "opening brace",
|
|
||||||
Self::RightBrace => "closing brace",
|
|
||||||
Self::Chain => "function chain operator",
|
|
||||||
Self::Colon => "colon",
|
|
||||||
Self::Comma => "comma",
|
|
||||||
Self::Equals => "equals sign",
|
|
||||||
Self::Ident(_) => "identifier",
|
|
||||||
Self::Str { .. } => "string",
|
|
||||||
Self::Bool(_) => "bool",
|
|
||||||
Self::Number(_) => "number",
|
|
||||||
Self::Length(_) => "length",
|
|
||||||
Self::Hex(_) => "hex value",
|
|
||||||
Self::Plus => "plus",
|
|
||||||
Self::Hyphen => "minus",
|
|
||||||
Self::Slash => "slash",
|
|
||||||
Self::Star => "star",
|
Self::Star => "star",
|
||||||
Self::Underscore => "underscore",
|
Self::Underscore => "underscore",
|
||||||
Self::Backslash => "backslash",
|
Self::Backslash => "backslash",
|
||||||
Self::Hashtag => "hashtag",
|
Self::Hashtag => "hashtag",
|
||||||
Self::UnicodeEscape { .. } => "unicode escape sequence",
|
|
||||||
Self::Raw { .. } => "raw block",
|
Self::Raw { .. } => "raw block",
|
||||||
Self::Text(_) => "text",
|
Self::UnicodeEscape { .. } => "unicode escape sequence",
|
||||||
|
|
||||||
|
Self::LeftBracket => "opening bracket",
|
||||||
|
Self::RightBracket => "closing bracket",
|
||||||
|
Self::LeftBrace => "opening brace",
|
||||||
|
Self::RightBrace => "closing brace",
|
||||||
|
Self::LeftParen => "opening paren",
|
||||||
|
Self::RightParen => "closing paren",
|
||||||
|
|
||||||
|
Self::Colon => "colon",
|
||||||
|
Self::Comma => "comma",
|
||||||
|
Self::Equals => "equals sign",
|
||||||
|
Self::Chain => "function chaining operator",
|
||||||
|
Self::Plus => "plus sign",
|
||||||
|
Self::Hyphen => "minus sign",
|
||||||
|
Self::Slash => "slash",
|
||||||
|
|
||||||
|
Self::Ident(_) => "identifier",
|
||||||
|
Self::Bool(_) => "bool",
|
||||||
|
Self::Number(_) => "number",
|
||||||
|
Self::Length(_) => "length",
|
||||||
|
Self::Hex(_) => "hex value",
|
||||||
|
Self::Str { .. } => "string",
|
||||||
|
|
||||||
Self::Invalid("*/") => "end of block comment",
|
Self::Invalid("*/") => "end of block comment",
|
||||||
Self::Invalid(_) => "invalid token",
|
Self::Invalid(_) => "invalid token",
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user