Checkpoint 🏁
This commit is contained in:
parent
bd702c2029
commit
a8f711d49a
@ -144,7 +144,7 @@ macro_rules! parse {
|
||||
|
||||
(optional: $body:expr, $ctx:expr) => (
|
||||
if let Some(body) = $body {
|
||||
Some($crate::syntax::parse(body, $ctx)?)
|
||||
Some($crate::syntax::parse(body, $ctx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
||||
Node::Space => self.layout_space(),
|
||||
Node::Newline => self.layout_paragraph()?,
|
||||
|
||||
Node::ToggleItalics => self.style.text.variant.style.toggle(),
|
||||
Node::ToggleItalic => self.style.text.variant.style.toggle(),
|
||||
Node::ToggleBolder => {
|
||||
self.style.text.variant.weight.0 += 300 *
|
||||
if self.style.text.bolder { -1 } else { 1 };
|
||||
|
@ -84,7 +84,7 @@ impl<'p> Typesetter<'p> {
|
||||
}
|
||||
|
||||
/// Parse source code into a syntax tree.
|
||||
pub fn parse(&self, src: &str) -> ParseResult<SyntaxTree> {
|
||||
pub fn parse(&self, src: &str) -> SyntaxTree {
|
||||
let scope = Scope::with_std();
|
||||
parse(src, ParseContext { scope: &scope })
|
||||
}
|
||||
@ -115,7 +115,7 @@ impl<'p> Typesetter<'p> {
|
||||
|
||||
/// Process source code directly into a layout.
|
||||
pub async fn typeset(&self, src: &str) -> TypesetResult<MultiLayout> {
|
||||
let tree = self.parse(src)?;
|
||||
let tree = self.parse(src);
|
||||
let layout = self.layout(&tree).await?;
|
||||
Ok(layout)
|
||||
}
|
||||
|
28
src/syntax/color.rs
Normal file
28
src/syntax/color.rs
Normal file
@ -0,0 +1,28 @@
|
||||
/// Entities which can be colored by syntax highlighting.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ColorToken {
|
||||
Comment,
|
||||
|
||||
Bracket,
|
||||
FuncName,
|
||||
Colon,
|
||||
|
||||
Key,
|
||||
Equals,
|
||||
Comma,
|
||||
|
||||
Paren,
|
||||
Brace,
|
||||
|
||||
ExprIdent,
|
||||
ExprString,
|
||||
ExprNumber,
|
||||
ExprSize,
|
||||
ExprBool,
|
||||
|
||||
Bold,
|
||||
Italic,
|
||||
Monospace,
|
||||
|
||||
Invalid,
|
||||
}
|
248
src/syntax/expr.rs
Normal file
248
src/syntax/expr.rs
Normal file
@ -0,0 +1,248 @@
|
||||
use super::*;
|
||||
|
||||
|
||||
/// The arguments passed to a function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncArgs {
|
||||
pub pos: Vec<Spanned<PosArg>>,
|
||||
pub key: Vec<Spanned<KeyArg>>,
|
||||
}
|
||||
|
||||
impl FuncArgs {
|
||||
/// Create an empty collection of arguments.
|
||||
pub fn new() -> FuncArgs {
|
||||
FuncArgs {
|
||||
pos: vec![],
|
||||
key: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a positional argument.
|
||||
pub fn add_pos(&mut self, arg: Spanned<PosArg>) {
|
||||
self.pos.push(arg);
|
||||
}
|
||||
|
||||
/// Add a keyword argument.
|
||||
pub fn add_key(&mut self, arg: Spanned<KeyArg>) {
|
||||
self.key.push(arg);
|
||||
}
|
||||
|
||||
/// Force-extract the first positional argument.
|
||||
pub fn get_pos<E: ExpressionKind>(&mut self) -> ParseResult<E> {
|
||||
expect(self.get_pos_opt())
|
||||
}
|
||||
|
||||
/// Extract the first positional argument.
|
||||
pub fn get_pos_opt<E: ExpressionKind>(&mut self) -> ParseResult<Option<E>> {
|
||||
Ok(if !self.pos.is_empty() {
|
||||
let spanned = self.pos.remove(0);
|
||||
Some(E::from_expr(spanned)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterator over positional arguments.
|
||||
pub fn pos(&mut self) -> std::vec::IntoIter<Spanned<PosArg>> {
|
||||
let vec = std::mem::replace(&mut self.pos, vec![]);
|
||||
vec.into_iter()
|
||||
}
|
||||
|
||||
/// Force-extract a keyword argument.
|
||||
pub fn get_key<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<E> {
|
||||
expect(self.get_key_opt(name))
|
||||
}
|
||||
|
||||
/// Extract a keyword argument.
|
||||
pub fn get_key_opt<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<Option<E>> {
|
||||
Ok(if let Some(index) = self.key.iter().position(|arg| arg.v.key.v.0 == name) {
|
||||
let value = self.key.swap_remove(index).v.value;
|
||||
Some(E::from_expr(value)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract any keyword argument.
|
||||
pub fn get_key_next(&mut self) -> Option<Spanned<KeyArg>> {
|
||||
self.key.pop()
|
||||
}
|
||||
|
||||
/// Iterator over all keyword arguments.
|
||||
pub fn keys(&mut self) -> std::vec::IntoIter<Spanned<KeyArg>> {
|
||||
let vec = std::mem::replace(&mut self.key, vec![]);
|
||||
vec.into_iter()
|
||||
}
|
||||
|
||||
/// Clear the argument lists.
|
||||
pub fn clear(&mut self) {
|
||||
self.pos.clear();
|
||||
self.key.clear();
|
||||
}
|
||||
|
||||
/// Whether both the positional and keyword argument lists are empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.pos.is_empty() && self.key.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the option expression kind from the option or return an error.
|
||||
fn expect<E: ExpressionKind>(opt: ParseResult<Option<E>>) -> ParseResult<E> {
|
||||
match opt {
|
||||
Ok(Some(spanned)) => Ok(spanned),
|
||||
Ok(None) => error!("expected {}", E::NAME),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// A positional argument passed to a function.
|
||||
pub type PosArg = Expression;
|
||||
|
||||
/// A keyword argument passed to a function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct KeyArg {
|
||||
pub key: Spanned<Ident>,
|
||||
pub value: Spanned<Expression>,
|
||||
}
|
||||
|
||||
/// Either a positional or keyword argument.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DynArg {
|
||||
Pos(Spanned<PosArg>),
|
||||
Key(Spanned<KeyArg>),
|
||||
}
|
||||
|
||||
/// An argument or return value.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Expression {
|
||||
Ident(Ident),
|
||||
Str(String),
|
||||
Num(f64),
|
||||
Size(Size),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
impl Display for Expression {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
use Expression::*;
|
||||
match self {
|
||||
Ident(i) => write!(f, "{}", i),
|
||||
Str(s) => write!(f, "{:?}", s),
|
||||
Num(n) => write!(f, "{}", n),
|
||||
Size(s) => write!(f, "{}", s),
|
||||
Bool(b) => write!(f, "{}", b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_display!(Expression);
|
||||
|
||||
pub struct Tuple;
|
||||
pub struct Object;
|
||||
|
||||
/// An identifier.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Ident(pub String);
|
||||
|
||||
impl Ident {
|
||||
pub fn new<S>(ident: S) -> Option<Ident> where S: AsRef<str> + Into<String> {
|
||||
if is_identifier(ident.as_ref()) {
|
||||
Some(Ident(ident.into()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ident {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
debug_display!(Ident);
|
||||
|
||||
/// Whether this word is a valid identifier.
|
||||
pub fn is_identifier(string: &str) -> bool {
|
||||
let mut chars = string.chars();
|
||||
|
||||
match chars.next() {
|
||||
Some('-') => {}
|
||||
Some(c) if UnicodeXID::is_xid_start(c) => {}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
match c {
|
||||
'.' | '-' => {}
|
||||
c if UnicodeXID::is_xid_continue(c) => {}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Kinds of expressions.
|
||||
pub trait ExpressionKind: Sized {
|
||||
const NAME: &'static str;
|
||||
|
||||
/// Create from expression.
|
||||
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self>;
|
||||
}
|
||||
|
||||
macro_rules! kind {
|
||||
($type:ty, $name:expr, $($patterns:tt)*) => {
|
||||
impl ExpressionKind for $type {
|
||||
const NAME: &'static str = $name;
|
||||
|
||||
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self> {
|
||||
#[allow(unreachable_patterns)]
|
||||
Ok(match expr.v {
|
||||
$($patterns)*,
|
||||
_ => error!("expected {}", Self::NAME),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
kind!(Expression, "expression", e => e);
|
||||
kind!(Ident, "identifier", Expression::Ident(ident) => ident);
|
||||
kind!(String, "string", Expression::Str(string) => string);
|
||||
kind!(f64, "number", Expression::Num(num) => num);
|
||||
kind!(bool, "boolean", Expression::Bool(boolean) => boolean);
|
||||
kind!(Size, "size", Expression::Size(size) => size);
|
||||
kind!(ScaleSize, "number or size",
|
||||
Expression::Size(size) => ScaleSize::Absolute(size),
|
||||
Expression::Num(scale) => ScaleSize::Scaled(scale as f32)
|
||||
);
|
||||
|
||||
impl<T> ExpressionKind for Spanned<T> where T: ExpressionKind {
|
||||
const NAME: &'static str = T::NAME;
|
||||
|
||||
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Spanned<T>> {
|
||||
let span = expr.span;
|
||||
T::from_expr(expr)
|
||||
.map(|v| Spanned::new(v, span))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ExpressionKind for Option<T> where T: ExpressionKind {
|
||||
const NAME: &'static str = T::NAME;
|
||||
|
||||
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Option<T>> {
|
||||
if let Expression::Ident(ident) = &expr.v {
|
||||
match ident.as_str() {
|
||||
"default" | "none" => return Ok(None),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
T::from_expr(expr).map(|v| Some(v))
|
||||
}
|
||||
}
|
@ -6,312 +6,11 @@ use unicode_xid::UnicodeXID;
|
||||
use crate::func::LayoutFunc;
|
||||
use crate::size::{Size, ScaleSize};
|
||||
|
||||
|
||||
pub type ParseResult<T> = crate::TypesetResult<T>;
|
||||
|
||||
pub_use_mod!(color);
|
||||
pub_use_mod!(expr);
|
||||
pub_use_mod!(tokens);
|
||||
pub_use_mod!(parsing);
|
||||
pub_use_mod!(span);
|
||||
|
||||
|
||||
/// A tree representation of source code.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SyntaxTree {
|
||||
pub nodes: Vec<Spanned<Node>>,
|
||||
}
|
||||
|
||||
impl SyntaxTree {
|
||||
/// Create an empty syntax tree.
|
||||
pub fn new() -> SyntaxTree {
|
||||
SyntaxTree { nodes: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the syntax tree.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Node {
|
||||
/// Whitespace.
|
||||
Space,
|
||||
/// A line feed.
|
||||
Newline,
|
||||
/// Indicates that italics were toggled.
|
||||
ToggleItalics,
|
||||
/// Indicates that bolder text was toggled.
|
||||
ToggleBolder,
|
||||
/// Indicates that monospace was toggled.
|
||||
ToggleMonospace,
|
||||
/// Literal text.
|
||||
Text(String),
|
||||
/// A function invocation.
|
||||
Func(FuncCall),
|
||||
}
|
||||
|
||||
/// A thing to be syntax highlighted.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ColorToken {
|
||||
Comment,
|
||||
Bracket,
|
||||
FuncName,
|
||||
Colon,
|
||||
KeyArg,
|
||||
Equals,
|
||||
Comma,
|
||||
ExprNumber,
|
||||
ExprSize,
|
||||
ExprStr,
|
||||
ExprIdent,
|
||||
ExprBool,
|
||||
Bold,
|
||||
Italic,
|
||||
Monospace,
|
||||
}
|
||||
|
||||
/// An invocation of a function.
|
||||
#[derive(Debug)]
|
||||
pub struct FuncCall(pub Box<dyn LayoutFunc>);
|
||||
|
||||
impl PartialEq for FuncCall {
|
||||
fn eq(&self, other: &FuncCall) -> bool {
|
||||
&self.0 == &other.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The arguments passed to a function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncArgs {
|
||||
pub pos: Vec<Spanned<PosArg>>,
|
||||
pub key: Vec<Spanned<KeyArg>>,
|
||||
}
|
||||
|
||||
impl FuncArgs {
|
||||
/// Create an empty collection of arguments.
|
||||
pub fn new() -> FuncArgs {
|
||||
FuncArgs {
|
||||
pos: vec![],
|
||||
key: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a positional argument.
|
||||
pub fn add_pos(&mut self, arg: Spanned<PosArg>) {
|
||||
self.pos.push(arg);
|
||||
}
|
||||
|
||||
/// Add a keyword argument.
|
||||
pub fn add_key(&mut self, arg: Spanned<KeyArg>) {
|
||||
self.key.push(arg);
|
||||
}
|
||||
|
||||
/// Force-extract the first positional argument.
|
||||
pub fn get_pos<E: ExpressionKind>(&mut self) -> ParseResult<E> {
|
||||
expect(self.get_pos_opt())
|
||||
}
|
||||
|
||||
/// Extract the first positional argument.
|
||||
pub fn get_pos_opt<E: ExpressionKind>(&mut self) -> ParseResult<Option<E>> {
|
||||
Ok(if !self.pos.is_empty() {
|
||||
let spanned = self.pos.remove(0);
|
||||
Some(E::from_expr(spanned)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterator over positional arguments.
|
||||
pub fn pos(&mut self) -> std::vec::IntoIter<Spanned<PosArg>> {
|
||||
let vec = std::mem::replace(&mut self.pos, vec![]);
|
||||
vec.into_iter()
|
||||
}
|
||||
|
||||
/// Force-extract a keyword argument.
|
||||
pub fn get_key<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<E> {
|
||||
expect(self.get_key_opt(name))
|
||||
}
|
||||
|
||||
/// Extract a keyword argument.
|
||||
pub fn get_key_opt<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<Option<E>> {
|
||||
Ok(if let Some(index) = self.key.iter().position(|arg| arg.v.key.v.0 == name) {
|
||||
let value = self.key.swap_remove(index).v.value;
|
||||
Some(E::from_expr(value)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract any keyword argument.
|
||||
pub fn get_key_next(&mut self) -> Option<Spanned<KeyArg>> {
|
||||
self.key.pop()
|
||||
}
|
||||
|
||||
/// Iterator over all keyword arguments.
|
||||
pub fn keys(&mut self) -> std::vec::IntoIter<Spanned<KeyArg>> {
|
||||
let vec = std::mem::replace(&mut self.key, vec![]);
|
||||
vec.into_iter()
|
||||
}
|
||||
|
||||
/// Clear the argument lists.
|
||||
pub fn clear(&mut self) {
|
||||
self.pos.clear();
|
||||
self.key.clear();
|
||||
}
|
||||
|
||||
/// Whether both the positional and keyword argument lists are empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.pos.is_empty() && self.key.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the option expression kind from the option or return an error.
|
||||
fn expect<E: ExpressionKind>(opt: ParseResult<Option<E>>) -> ParseResult<E> {
|
||||
match opt {
|
||||
Ok(Some(spanned)) => Ok(spanned),
|
||||
Ok(None) => error!("expected {}", E::NAME),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// A positional argument passed to a function.
|
||||
pub type PosArg = Expression;
|
||||
|
||||
/// A keyword argument passed to a function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct KeyArg {
|
||||
pub key: Spanned<Ident>,
|
||||
pub value: Spanned<Expression>,
|
||||
}
|
||||
|
||||
/// Either a positional or keyword argument.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DynArg {
|
||||
Pos(Spanned<PosArg>),
|
||||
Key(Spanned<KeyArg>),
|
||||
}
|
||||
|
||||
/// An argument or return value.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Expression {
|
||||
Ident(Ident),
|
||||
Str(String),
|
||||
Num(f64),
|
||||
Size(Size),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
impl Display for Expression {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
use Expression::*;
|
||||
match self {
|
||||
Ident(i) => write!(f, "{}", i),
|
||||
Str(s) => write!(f, "{:?}", s),
|
||||
Num(n) => write!(f, "{}", n),
|
||||
Size(s) => write!(f, "{}", s),
|
||||
Bool(b) => write!(f, "{}", b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_display!(Expression);
|
||||
|
||||
/// An identifier.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Ident(pub String);
|
||||
|
||||
impl Ident {
|
||||
pub fn new<S>(ident: S) -> Option<Ident> where S: AsRef<str> + Into<String> {
|
||||
if is_identifier(ident.as_ref()) {
|
||||
Some(Ident(ident.into()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ident {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
debug_display!(Ident);
|
||||
|
||||
/// Whether this word is a valid identifier.
|
||||
fn is_identifier(string: &str) -> bool {
|
||||
let mut chars = string.chars();
|
||||
|
||||
match chars.next() {
|
||||
Some('-') => {}
|
||||
Some(c) if UnicodeXID::is_xid_start(c) => {}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
match c {
|
||||
'.' | '-' => {}
|
||||
c if UnicodeXID::is_xid_continue(c) => {}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Kinds of expressions.
|
||||
pub trait ExpressionKind: Sized {
|
||||
const NAME: &'static str;
|
||||
|
||||
/// Create from expression.
|
||||
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self>;
|
||||
}
|
||||
|
||||
macro_rules! kind {
|
||||
($type:ty, $name:expr, $($patterns:tt)*) => {
|
||||
impl ExpressionKind for $type {
|
||||
const NAME: &'static str = $name;
|
||||
|
||||
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self> {
|
||||
#[allow(unreachable_patterns)]
|
||||
Ok(match expr.v {
|
||||
$($patterns)*,
|
||||
_ => error!("expected {}", Self::NAME),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
kind!(Expression, "expression", e => e);
|
||||
kind!(Ident, "identifier", Expression::Ident(ident) => ident);
|
||||
kind!(String, "string", Expression::Str(string) => string);
|
||||
kind!(f64, "number", Expression::Num(num) => num);
|
||||
kind!(bool, "boolean", Expression::Bool(boolean) => boolean);
|
||||
kind!(Size, "size", Expression::Size(size) => size);
|
||||
kind!(ScaleSize, "number or size",
|
||||
Expression::Size(size) => ScaleSize::Absolute(size),
|
||||
Expression::Num(scale) => ScaleSize::Scaled(scale as f32)
|
||||
);
|
||||
|
||||
impl<T> ExpressionKind for Spanned<T> where T: ExpressionKind {
|
||||
const NAME: &'static str = T::NAME;
|
||||
|
||||
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Spanned<T>> {
|
||||
let span = expr.span;
|
||||
T::from_expr(expr)
|
||||
.map(|v| Spanned::new(v, span))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ExpressionKind for Option<T> where T: ExpressionKind {
|
||||
const NAME: &'static str = T::NAME;
|
||||
|
||||
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Option<T>> {
|
||||
if let Expression::Ident(ident) = &expr.v {
|
||||
match ident.as_str() {
|
||||
"default" | "none" => return Ok(None),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
T::from_expr(expr).map(|v| Some(v))
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,55 @@
|
||||
use std::iter::Peekable;
|
||||
|
||||
use crate::func::Scope;
|
||||
use super::*;
|
||||
use Token::*;
|
||||
|
||||
|
||||
/// The result type for parsing.
|
||||
pub type ParseResult<T> = crate::TypesetResult<T>;
|
||||
/// A tree representation of source code.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SyntaxTree {
|
||||
pub nodes: Vec<Spanned<Node>>,
|
||||
}
|
||||
|
||||
impl SyntaxTree {
|
||||
/// Create an empty syntax tree.
|
||||
pub fn new() -> SyntaxTree {
|
||||
SyntaxTree { nodes: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the syntax tree.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Node {
|
||||
/// A number of whitespace characters containing less than two newlines.
|
||||
Space,
|
||||
/// Whitespace characters with more than two newlines.
|
||||
Newline,
|
||||
/// Plain text.
|
||||
Text(String),
|
||||
/// Italics enabled / disabled.
|
||||
ToggleItalic,
|
||||
/// Bolder enabled / disabled.
|
||||
ToggleBolder,
|
||||
/// Monospace enabled / disabled.
|
||||
ToggleMonospace,
|
||||
/// A function invocation.
|
||||
Func(FuncCall),
|
||||
}
|
||||
|
||||
/// An invocation of a function.
|
||||
#[derive(Debug)]
|
||||
pub struct FuncCall(pub Box<dyn LayoutFunc>);
|
||||
|
||||
impl PartialEq for FuncCall {
|
||||
fn eq(&self, other: &FuncCall) -> bool {
|
||||
&self.0 == &other.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses source code into a syntax tree given a context.
|
||||
pub fn parse(src: &str, ctx: ParseContext) -> ParseResult<SyntaxTree> {
|
||||
unimplemented!()
|
||||
pub fn parse(src: &str, ctx: ParseContext) -> SyntaxTree {
|
||||
Parser::new(src, ctx).parse()
|
||||
}
|
||||
|
||||
/// The context for parsing.
|
||||
@ -16,3 +58,342 @@ pub struct ParseContext<'a> {
|
||||
/// The scope containing function definitions.
|
||||
pub scope: &'a Scope,
|
||||
}
|
||||
|
||||
struct Parser<'s> {
|
||||
src: &'s str,
|
||||
ctx: ParseContext<'s>,
|
||||
tokens: Peekable<Tokens<'s>>,
|
||||
errors: Vec<Spanned<String>>,
|
||||
colored: Vec<Spanned<ColorToken>>,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
macro_rules! defer {
|
||||
($($tts:tt)*) => (
|
||||
unimplemented!()
|
||||
);
|
||||
}
|
||||
|
||||
impl<'s> Parser<'s> {
|
||||
fn new(src: &'s str, ctx: ParseContext<'s>) -> Parser<'s> {
|
||||
Parser {
|
||||
src,
|
||||
ctx,
|
||||
tokens: Tokens::new(src).peekable(),
|
||||
errors: vec![],
|
||||
colored: vec![],
|
||||
span: Span::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(mut self) -> SyntaxTree {
|
||||
let mut tree = SyntaxTree::new();
|
||||
|
||||
loop {
|
||||
self.skip_whitespace();
|
||||
|
||||
let start = self.position();
|
||||
|
||||
let node = match self.next() {
|
||||
Some(LeftBracket) => self.parse_func().map(|f| Node::Func(f)),
|
||||
Some(Star) => Some(Node::ToggleBolder),
|
||||
Some(Underscore) => Some(Node::ToggleItalic),
|
||||
Some(Backtick) => Some(Node::ToggleMonospace),
|
||||
Some(Text(text)) => Some(Node::Text(text.to_owned())),
|
||||
Some(other) => { self.unexpected(other); None },
|
||||
None => break,
|
||||
};
|
||||
|
||||
if let Some(node) = node {
|
||||
let end = self.position();
|
||||
let span = Span { start, end };
|
||||
|
||||
tree.nodes.push(Spanned { v: node, span });
|
||||
}
|
||||
}
|
||||
|
||||
tree
|
||||
}
|
||||
|
||||
fn parse_func(&mut self) -> Option<FuncCall> {
|
||||
let (name, args) = self.parse_func_header()?;
|
||||
self.parse_func_call(name, args)
|
||||
}
|
||||
|
||||
fn parse_func_header(&mut self) -> Option<(Spanned<Ident>, FuncArgs)> {
|
||||
defer! { self.eat_until(|t| t == RightBracket, true); }
|
||||
|
||||
self.skip_whitespace();
|
||||
|
||||
let name = self.parse_func_name()?;
|
||||
|
||||
self.skip_whitespace();
|
||||
|
||||
let args = match self.next() {
|
||||
Some(Colon) => self.parse_func_args(),
|
||||
Some(RightBracket) => FuncArgs::new(),
|
||||
other => {
|
||||
self.expected("colon or closing bracket", other);
|
||||
FuncArgs::new()
|
||||
}
|
||||
};
|
||||
|
||||
Some((name, args))
|
||||
}
|
||||
|
||||
fn parse_func_call(
|
||||
&mut self,
|
||||
name: Spanned<Ident>,
|
||||
args: FuncArgs,
|
||||
) -> Option<FuncCall> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn parse_func_name(&mut self) -> Option<Spanned<Ident>> {
|
||||
match self.next() {
|
||||
Some(ExprIdent(ident)) => {
|
||||
self.color_span(ColorToken::FuncName, self.span(), true);
|
||||
Some(Spanned { v: Ident(ident.to_string()), span: self.span() })
|
||||
}
|
||||
other => {
|
||||
self.expected("identifier", other);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_func_args(&mut self) -> FuncArgs {
|
||||
enum State {
|
||||
Start,
|
||||
Identifier(Spanned<Ident>),
|
||||
Assignment(Spanned<Ident>),
|
||||
Value,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn expected(&self) -> &'static str {
|
||||
match self {
|
||||
State::Start => "value or key",
|
||||
State::Identifier(_) => "comma or assignment",
|
||||
State::Assignment(_) => "value",
|
||||
State::Value => "comma",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut args = FuncArgs::new();
|
||||
let mut state = State::Start;
|
||||
|
||||
loop {
|
||||
self.skip_whitespace();
|
||||
|
||||
/*
|
||||
let token = self.next();
|
||||
match token {
|
||||
Some(ExprIdent(ident)) => match state {
|
||||
State::Start => {
|
||||
state = State::Identifier(Spanned {
|
||||
v: Ident(ident.to_string()),
|
||||
span: self.span(),
|
||||
});
|
||||
}
|
||||
State::Identifier(prev) => {
|
||||
self.expected(state.expected(), token);
|
||||
args.add_pos(prev.map(|id| Expression::Ident(id)));
|
||||
state = State::Identifier(Spanned {
|
||||
v: Ident(ident.to_string()),
|
||||
span: self.span(),
|
||||
});
|
||||
}
|
||||
State::Assignment(key) => {
|
||||
let span = Span::merge(key.span, self.span());
|
||||
args.add_key(Spanned::new(KeyArg {
|
||||
key,
|
||||
value: Spanned {
|
||||
v: Expression::Ident(Ident(ident.to_string())),
|
||||
span: self.span(),
|
||||
},
|
||||
}, span));
|
||||
state = State::Value;
|
||||
}
|
||||
State::Value => {
|
||||
self.expected(state.expected(), token);
|
||||
state = State::Identifier(Spanned {
|
||||
v: Ident(ident.to_string()),
|
||||
span: self.span(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle expressions.
|
||||
Some(Expr(_)) | Some(LeftParen) | Some(LeftBrace) => {
|
||||
let expr = match token.unwrap() {
|
||||
Expr(e) => e,
|
||||
LeftParen => self.parse_tuple(),
|
||||
LeftBrace => self.parse_object(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Handle commas after values.
|
||||
Some(Comma) => match state {
|
||||
State::Identifier(ident) => {
|
||||
args.add_pos(ident.map(|id| Expression::Ident(id)));
|
||||
state = State::Start;
|
||||
}
|
||||
State::Value => state = State::Start,
|
||||
_ => self.expected(state.expected(), token),
|
||||
}
|
||||
|
||||
// Handle the end of the function header.
|
||||
Some(RightBracket) => {
|
||||
match state {
|
||||
State::Identifier(ident) => {
|
||||
args.add_pos(ident.map(|id| Expression::Ident(id)));
|
||||
}
|
||||
State::Assignment(_) => {
|
||||
self.expected(state.expected(), token);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
fn handle_expr(&mut self, expr: Spanned<Expression>) {
|
||||
|
||||
}
|
||||
|
||||
fn parse_tuple(&mut self) -> Spanned<Tuple> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn parse_object(&mut self) -> Spanned<Object> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
self.eat_until(|t| match t {
|
||||
Whitespace(_) | LineComment(_) | BlockComment(_) => false,
|
||||
_ => true,
|
||||
}, false)
|
||||
}
|
||||
|
||||
fn eat_until<F>(&mut self, mut f: F, eat_match: bool)
|
||||
where F: FnMut(Token<'s>) -> bool {
|
||||
while let Some(token) = self.tokens.peek() {
|
||||
if f(token.v) {
|
||||
if eat_match {
|
||||
self.next();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Option<Token<'s>> {
|
||||
self.tokens.next().map(|spanned| {
|
||||
self.color_token(&spanned.v, spanned.span);
|
||||
self.span = spanned.span;
|
||||
spanned.v
|
||||
})
|
||||
}
|
||||
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
fn position(&self) -> Position {
|
||||
self.span.end
|
||||
}
|
||||
|
||||
fn unexpected(&mut self, found: Token) {
|
||||
self.errors.push(Spanned {
|
||||
v: format!("unexpected {}", name(found)),
|
||||
span: self.span(),
|
||||
});
|
||||
}
|
||||
|
||||
fn expected(&mut self, thing: &str, found: Option<Token>) {
|
||||
let message = if let Some(found) = found {
|
||||
format!("expected {}, found {}", thing, name(found))
|
||||
} else {
|
||||
format!("expected {}", thing)
|
||||
};
|
||||
|
||||
self.errors.push(Spanned {
|
||||
v: message,
|
||||
span: self.span(),
|
||||
});
|
||||
}
|
||||
|
||||
fn color_token(&mut self, token: &Token<'s>, span: Span) {
|
||||
let colored = match token {
|
||||
LineComment(_) | BlockComment(_) => Some(ColorToken::Comment),
|
||||
StarSlash => Some(ColorToken::Invalid),
|
||||
LeftBracket | RightBracket => Some(ColorToken::Bracket),
|
||||
LeftParen | RightParen => Some(ColorToken::Paren),
|
||||
LeftBrace | RightBrace => Some(ColorToken::Brace),
|
||||
Colon => Some(ColorToken::Colon),
|
||||
Comma => Some(ColorToken::Comma),
|
||||
Equals => Some(ColorToken::Equals),
|
||||
ExprIdent(_) => Some(ColorToken::ExprIdent),
|
||||
ExprString(_) => Some(ColorToken::ExprString),
|
||||
ExprNumber(_) => Some(ColorToken::ExprNumber),
|
||||
ExprSize(_) => Some(ColorToken::ExprSize),
|
||||
ExprBool(_) => Some(ColorToken::ExprBool),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(color) = colored {
|
||||
self.colored.push(Spanned { v: color, span });
|
||||
}
|
||||
}
|
||||
|
||||
fn color_span(&mut self, color: ColorToken, span: Span, replace_last: bool) {
|
||||
let token = Spanned { v: color, span };
|
||||
|
||||
if replace_last {
|
||||
if let Some(last) = self.colored.last_mut() {
|
||||
*last = token;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.colored.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
fn name(token: Token) -> &'static str {
|
||||
match token {
|
||||
Whitespace(_) => "whitespace",
|
||||
LineComment(_) | BlockComment(_) => "comment",
|
||||
StarSlash => "end of block comment",
|
||||
LeftBracket => "opening bracket",
|
||||
RightBracket => "closing bracket",
|
||||
LeftParen => "opening paren",
|
||||
RightParen => "closing paren",
|
||||
LeftBrace => "opening brace",
|
||||
RightBrace => "closing brace",
|
||||
Colon => "colon",
|
||||
Comma => "comma",
|
||||
Equals => "equals sign",
|
||||
ExprIdent(_) => "identifier",
|
||||
ExprString(_) => "string",
|
||||
ExprNumber(_) => "number",
|
||||
ExprSize(_) => "size",
|
||||
ExprBool(_) => "bool",
|
||||
Star => "star",
|
||||
Underscore => "underscore",
|
||||
Backtick => "backtick",
|
||||
Text(_) => "text",
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,11 @@ impl<T> Spanned<T> {
|
||||
self.v
|
||||
}
|
||||
|
||||
pub fn map<V>(&self, new_v: V) -> Spanned<V> {
|
||||
pub fn map<F, V>(self, f: F) -> Spanned<V> where F: FnOnce(T) -> V {
|
||||
Spanned { v: f(self.v), span: self.span }
|
||||
}
|
||||
|
||||
pub fn map_v<V>(&self, new_v: V) -> Spanned<V> {
|
||||
Spanned { v: new_v, span: self.span }
|
||||
}
|
||||
}
|
||||
@ -40,6 +44,8 @@ pub struct Span {
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub const ZERO: Span = Span { start: Position::ZERO, end: Position::ZERO };
|
||||
|
||||
pub fn new(start: Position, end: Position) -> Span {
|
||||
Span { start, end }
|
||||
}
|
||||
@ -78,6 +84,8 @@ pub struct Position {
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub const ZERO: Position = Position { line: 0, column: 0 };
|
||||
|
||||
pub fn new(line: usize, column: usize) -> Position {
|
||||
Position { line, column }
|
||||
}
|
||||
|
@ -6,12 +6,8 @@ use Token::*;
|
||||
use State::*;
|
||||
|
||||
|
||||
pub fn tokenize(src: &str) -> Tokens {
|
||||
Tokens::new(src)
|
||||
}
|
||||
|
||||
/// A minimal semantic entity of source code.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum Token<'s> {
|
||||
/// One or more whitespace characters. The contained `usize` denotes the
|
||||
/// number of newlines that were contained in the whitespace.
|
||||
@ -46,8 +42,16 @@ pub enum Token<'s> {
|
||||
/// An equals sign in a function header: `=`.
|
||||
Equals,
|
||||
|
||||
/// An expression in a function header.
|
||||
Expr(Expression),
|
||||
/// An identifier in a function header: `center`.
|
||||
ExprIdent(&'s str),
|
||||
/// A quoted string in a function header: `"..."`.
|
||||
ExprString(&'s str),
|
||||
/// A number in a function header: `3.14`.
|
||||
ExprNumber(f64),
|
||||
/// A size in a function header: `12pt`.
|
||||
ExprSize(Size),
|
||||
/// A boolean in a function header: `true | false`.
|
||||
ExprBool(bool),
|
||||
|
||||
/// A star in body-text.
|
||||
Star,
|
||||
@ -60,6 +64,11 @@ pub enum Token<'s> {
|
||||
Text(&'s str),
|
||||
}
|
||||
|
||||
/// Decomposes text into a sequence of semantic tokens.
|
||||
pub fn tokenize(src: &str) -> Tokens {
|
||||
Tokens::new(src)
|
||||
}
|
||||
|
||||
/// An iterator over the tokens of a string of source code.
|
||||
pub struct Tokens<'s> {
|
||||
src: &'s str,
|
||||
@ -138,7 +147,7 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
|
||||
// Expressions or just strings.
|
||||
c => {
|
||||
let word = self.read_string_until(|n| {
|
||||
let text = self.read_string_until(|n| {
|
||||
match n {
|
||||
c if c.is_whitespace() => true,
|
||||
'\\' | '[' | ']' | '*' | '_' | '`' | ':' | '=' |
|
||||
@ -148,9 +157,9 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
}, false, -(c.len_utf8() as isize), 0);
|
||||
|
||||
if self.state == Header {
|
||||
self.parse_expr(word)
|
||||
self.parse_expr(text)
|
||||
} else {
|
||||
Text(word)
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -169,7 +178,6 @@ impl<'s> Tokens<'s> {
|
||||
|
||||
fn parse_block_comment(&mut self) -> Token<'s> {
|
||||
enum Last { Slash, Star, Other }
|
||||
use Last::*;
|
||||
|
||||
self.eat();
|
||||
|
||||
@ -181,15 +189,15 @@ impl<'s> Tokens<'s> {
|
||||
BlockComment(self.read_string_until(|n| {
|
||||
match n {
|
||||
'/' => match last {
|
||||
Star if depth == 0 => return true,
|
||||
Star => depth -= 1,
|
||||
_ => last = Slash
|
||||
Last::Star if depth == 0 => return true,
|
||||
Last::Star => depth -= 1,
|
||||
_ => last = Last::Slash
|
||||
}
|
||||
'*' => match last {
|
||||
Slash => depth += 1,
|
||||
_ => last = Star,
|
||||
Last::Slash => depth += 1,
|
||||
_ => last = Last::Star,
|
||||
}
|
||||
_ => last = Other,
|
||||
_ => last = Last::Other,
|
||||
}
|
||||
|
||||
false
|
||||
@ -205,7 +213,7 @@ impl<'s> Tokens<'s> {
|
||||
|
||||
fn parse_string(&mut self) -> Token<'s> {
|
||||
let mut escaped = false;
|
||||
Expr(Expression::Str(self.read_string_until(|n| {
|
||||
ExprString(self.read_string_until(|n| {
|
||||
if n == '"' && !escaped {
|
||||
return true;
|
||||
} else if n == '\\' {
|
||||
@ -215,7 +223,7 @@ impl<'s> Tokens<'s> {
|
||||
}
|
||||
|
||||
false
|
||||
}, true, 0, -1).to_string()))
|
||||
}, true, 0, -1))
|
||||
}
|
||||
|
||||
fn parse_escaped(&mut self) -> Token<'s> {
|
||||
@ -236,19 +244,19 @@ impl<'s> Tokens<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self, word: &'s str) -> Token<'s> {
|
||||
if let Ok(b) = word.parse::<bool>() {
|
||||
Expr(Expression::Bool(b))
|
||||
} else if let Ok(num) = word.parse::<f64>() {
|
||||
Expr(Expression::Num(num))
|
||||
} else if let Ok(num) = parse_percentage(word) {
|
||||
Expr(Expression::Num(num / 100.0))
|
||||
} else if let Ok(size) = word.parse::<Size>() {
|
||||
Expr(Expression::Size(size))
|
||||
} else if let Some(ident) = Ident::new(word) {
|
||||
Expr(Expression::Ident(ident))
|
||||
fn parse_expr(&mut self, text: &'s str) -> Token<'s> {
|
||||
if let Ok(b) = text.parse::<bool>() {
|
||||
ExprBool(b)
|
||||
} else if let Ok(num) = text.parse::<f64>() {
|
||||
ExprNumber(num)
|
||||
} else if let Some(num) = parse_percentage(text) {
|
||||
ExprNumber(num / 100.0)
|
||||
} else if let Ok(size) = text.parse::<Size>() {
|
||||
ExprSize(size)
|
||||
} else if is_identifier(text) {
|
||||
ExprIdent(text)
|
||||
} else {
|
||||
Text(word)
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,11 +304,11 @@ impl<'s> Tokens<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_percentage(word: &str) -> Result<f64, ()> {
|
||||
if word.ends_with('%') {
|
||||
word[.. word.len() - 1].parse::<f64>().map_err(|_| ())
|
||||
fn parse_percentage(text: &str) -> Option<f64> {
|
||||
if text.ends_with('%') {
|
||||
text[.. text.len() - 1].parse::<f64>().ok()
|
||||
} else {
|
||||
Err(())
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,7 +333,7 @@ impl<'s> Characters<'s> {
|
||||
fn new(src: &'s str) -> Characters<'s> {
|
||||
Characters {
|
||||
iter: src.chars().peekable(),
|
||||
position: Position::new(0, 0),
|
||||
position: Position::ZERO,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user