mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Eco string 🌱
This commit is contained in:
parent
9950627789
commit
36b3067c19
389
src/eco.rs
Normal file
389
src/eco.rs
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
//! An economical string.
|
||||||
|
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// A economical string with inline storage and clone-on-write value semantics.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EcoString(Repr);
|
||||||
|
|
||||||
|
/// The internal representation. Either:
|
||||||
|
/// - inline when below a certain number of bytes,
|
||||||
|
/// - or reference-counted on the heap with COW semantics.
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Repr {
|
||||||
|
Small { buf: [u8; LIMIT], len: u8 },
|
||||||
|
Large(Rc<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The maximum number of bytes that can be stored inline.
|
||||||
|
///
|
||||||
|
/// The value is chosen such that `Repr` fits exactly into 16 bytes
|
||||||
|
/// (which are needed anyway due to `Rc`s alignment).
|
||||||
|
///
|
||||||
|
/// Must be at least 4 to hold any char.
|
||||||
|
const LIMIT: usize = 14;
|
||||||
|
|
||||||
|
impl EcoString {
|
||||||
|
/// Create a new, empty string.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Repr::Small { buf: [0; LIMIT], len: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new, empty string with the given `capacity`.
|
||||||
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
|
if capacity <= LIMIT {
|
||||||
|
Self::new()
|
||||||
|
} else {
|
||||||
|
Self(Repr::Large(Rc::new(String::with_capacity(capacity))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an instance from an existing string-like type.
|
||||||
|
pub fn from_str<S>(s: S) -> Self
|
||||||
|
where
|
||||||
|
S: AsRef<str> + Into<String>,
|
||||||
|
{
|
||||||
|
let slice = s.as_ref();
|
||||||
|
let len = slice.len();
|
||||||
|
Self(if len <= LIMIT {
|
||||||
|
let mut buf = [0; LIMIT];
|
||||||
|
buf[.. len].copy_from_slice(slice.as_bytes());
|
||||||
|
Repr::Small { buf, len: len as u8 }
|
||||||
|
} else {
|
||||||
|
Repr::Large(Rc::new(s.into()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the string is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The length of the string in bytes.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match &self.0 {
|
||||||
|
Repr::Small { len, .. } => usize::from(*len),
|
||||||
|
Repr::Large(string) => string.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A string slice containing the entire string.
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends the given character at the end.
|
||||||
|
pub fn push(&mut self, c: char) {
|
||||||
|
match &mut self.0 {
|
||||||
|
Repr::Small { buf, len } => {
|
||||||
|
let prev = usize::from(*len);
|
||||||
|
if c.len_utf8() == 1 && prev < LIMIT {
|
||||||
|
buf[prev] = c as u8;
|
||||||
|
*len += 1;
|
||||||
|
} else {
|
||||||
|
self.push_str(c.encode_utf8(&mut [0; 4]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Repr::Large(rc) => Rc::make_mut(rc).push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends the given string slice at the end.
|
||||||
|
pub fn push_str(&mut self, string: &str) {
|
||||||
|
match &mut self.0 {
|
||||||
|
Repr::Small { buf, len } => {
|
||||||
|
let prev = usize::from(*len);
|
||||||
|
let new = prev + string.len();
|
||||||
|
if new <= LIMIT {
|
||||||
|
buf[prev .. new].copy_from_slice(string.as_bytes());
|
||||||
|
*len = new as u8;
|
||||||
|
} else {
|
||||||
|
let mut spilled = String::with_capacity(new);
|
||||||
|
spilled.push_str(self);
|
||||||
|
spilled.push_str(string);
|
||||||
|
self.0 = Repr::Large(Rc::new(spilled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Repr::Large(rc) => Rc::make_mut(rc).push_str(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the last character from the string.
|
||||||
|
pub fn pop(&mut self) -> Option<char> {
|
||||||
|
let c = self.as_str().chars().rev().next()?;
|
||||||
|
match &mut self.0 {
|
||||||
|
Repr::Small { len, .. } => {
|
||||||
|
*len -= c.len_utf8() as u8;
|
||||||
|
}
|
||||||
|
Repr::Large(rc) => {
|
||||||
|
Rc::make_mut(rc).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Repeats this string `n` times.
|
||||||
|
pub fn repeat(&self, n: usize) -> Self {
|
||||||
|
if let Repr::Small { buf, len } = &self.0 {
|
||||||
|
let prev = usize::from(*len);
|
||||||
|
let new = prev.saturating_mul(n);
|
||||||
|
if new <= LIMIT {
|
||||||
|
let src = &buf[.. prev];
|
||||||
|
let mut buf = [0; LIMIT];
|
||||||
|
for i in 0 .. n {
|
||||||
|
buf[prev * i .. prev * (i + 1)].copy_from_slice(src);
|
||||||
|
}
|
||||||
|
return Self(Repr::Small { buf, len: new as u8 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.as_str().repeat(n).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Self> for EcoString {
|
||||||
|
fn from(s: &Self) -> Self {
|
||||||
|
s.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<char> for EcoString {
|
||||||
|
fn from(c: char) -> Self {
|
||||||
|
let mut buf = [0; LIMIT];
|
||||||
|
let len = c.encode_utf8(&mut buf).len();
|
||||||
|
Self(Repr::Small { buf, len: len as u8 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for EcoString {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Self::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for EcoString {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&String> for EcoString {
|
||||||
|
fn from(s: &String) -> Self {
|
||||||
|
Self::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for EcoString {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &str {
|
||||||
|
match &self.0 {
|
||||||
|
Repr::Small { buf, len } => unsafe {
|
||||||
|
std::str::from_utf8_unchecked(&buf[.. usize::from(*len)])
|
||||||
|
},
|
||||||
|
Repr::Large(string) => string.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for EcoString {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<str> for EcoString {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EcoString {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for EcoString {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Debug::fmt(self.as_str(), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EcoString {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(self.as_str(), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for EcoString {}
|
||||||
|
|
||||||
|
impl PartialEq for EcoString {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.as_str().eq(other.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for EcoString {
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
self.as_str().eq(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for EcoString {
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
self.as_str().eq(*other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<String> for EcoString {
|
||||||
|
fn eq(&self, other: &String) -> bool {
|
||||||
|
self.as_str().eq(other.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for EcoString {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.as_str().cmp(other.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for EcoString {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
self.as_str().partial_cmp(other.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&str> for EcoString {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: &str) -> Self::Output {
|
||||||
|
self.push_str(rhs);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<&str> for EcoString {
|
||||||
|
fn add_assign(&mut self, rhs: &str) {
|
||||||
|
self.push_str(rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for EcoString {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.as_str().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for EcoString {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
self.push_str(s);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_char(&mut self, c: char) -> fmt::Result {
|
||||||
|
self.push(c);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const ALPH: &str = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_str_new() {
|
||||||
|
// Test inline strings.
|
||||||
|
assert_eq!(EcoString::new(), "");
|
||||||
|
assert_eq!(EcoString::from('a'), "a");
|
||||||
|
assert_eq!(EcoString::from('😀'), "😀");
|
||||||
|
assert_eq!(EcoString::from("abc"), "abc");
|
||||||
|
|
||||||
|
// Test around the inline limit.
|
||||||
|
assert_eq!(EcoString::from(&ALPH[.. LIMIT - 1]), ALPH[.. LIMIT - 1]);
|
||||||
|
assert_eq!(EcoString::from(&ALPH[.. LIMIT]), ALPH[.. LIMIT]);
|
||||||
|
assert_eq!(EcoString::from(&ALPH[.. LIMIT + 1]), ALPH[.. LIMIT + 1]);
|
||||||
|
|
||||||
|
// Test heap string.
|
||||||
|
assert_eq!(EcoString::from(ALPH), ALPH);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_str_push() {
|
||||||
|
let mut v = EcoString::new();
|
||||||
|
v.push('a');
|
||||||
|
v.push('b');
|
||||||
|
v.push_str("cd😀");
|
||||||
|
assert_eq!(v, "abcd😀");
|
||||||
|
assert_eq!(v.len(), 8);
|
||||||
|
|
||||||
|
// Test fully filling the inline storage.
|
||||||
|
v.push_str("efghij");
|
||||||
|
assert_eq!(v.len(), LIMIT);
|
||||||
|
|
||||||
|
// Test spilling with `push`.
|
||||||
|
let mut a = v.clone();
|
||||||
|
a.push('k');
|
||||||
|
assert_eq!(a, "abcd😀efghijk");
|
||||||
|
assert_eq!(a.len(), 15);
|
||||||
|
|
||||||
|
// Test spilling with `push_str`.
|
||||||
|
let mut b = v.clone();
|
||||||
|
b.push_str("klmn");
|
||||||
|
assert_eq!(b, "abcd😀efghijklmn");
|
||||||
|
assert_eq!(b.len(), 18);
|
||||||
|
|
||||||
|
// v should be unchanged.
|
||||||
|
assert_eq!(v.len(), LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_str_pop() {
|
||||||
|
// Test with inline string.
|
||||||
|
let mut v = EcoString::from("Hello World!");
|
||||||
|
assert_eq!(v.pop(), Some('!'));
|
||||||
|
assert_eq!(v, "Hello World");
|
||||||
|
|
||||||
|
// Remove one-by-one.
|
||||||
|
for _ in 0 .. 10 {
|
||||||
|
v.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(v, "H");
|
||||||
|
assert_eq!(v.pop(), Some('H'));
|
||||||
|
assert_eq!(v, "");
|
||||||
|
assert!(v.is_empty());
|
||||||
|
|
||||||
|
// Test with large string.
|
||||||
|
let mut v = EcoString::from(ALPH);
|
||||||
|
assert_eq!(v.pop(), Some('z'));
|
||||||
|
assert_eq!(v.len(), 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_str_index() {
|
||||||
|
// Test that we can use the index syntax.
|
||||||
|
let v = EcoString::from("abc");
|
||||||
|
assert_eq!(&v[.. 2], "ab");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_str_repeat() {
|
||||||
|
// Test with empty string.
|
||||||
|
assert_eq!(EcoString::new().repeat(0), "");
|
||||||
|
assert_eq!(EcoString::new().repeat(100), "");
|
||||||
|
|
||||||
|
// Test non-spilling and spilling case.
|
||||||
|
let v = EcoString::from("abc");
|
||||||
|
assert_eq!(v.repeat(0), "");
|
||||||
|
assert_eq!(v.repeat(3), "abcabcabc");
|
||||||
|
assert_eq!(v.repeat(5), "abcabcabcabcabc");
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use crate::cache::Cache;
|
use crate::cache::Cache;
|
||||||
use crate::diag::{Diag, DiagSet, Pass};
|
use crate::diag::{Diag, DiagSet, Pass};
|
||||||
|
use crate::eco::EcoString;
|
||||||
use crate::geom::{Angle, Fractional, Length, Relative};
|
use crate::geom::{Angle, Fractional, Length, Relative};
|
||||||
use crate::loading::{FileHash, Loader};
|
use crate::loading::{FileHash, Loader};
|
||||||
use crate::parse::parse;
|
use crate::parse::parse;
|
||||||
@ -528,7 +529,7 @@ impl Eval for ClosureExpr {
|
|||||||
visitor.finish()
|
visitor.finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = self.name.as_ref().map(|id| id.to_string());
|
let name = self.name.as_ref().map(|name| name.string.clone());
|
||||||
Value::Func(FuncValue::new(name, move |ctx, args| {
|
Value::Func(FuncValue::new(name, move |ctx, args| {
|
||||||
// Don't leak the scopes from the call site. Instead, we use the
|
// Don't leak the scopes from the call site. Instead, we use the
|
||||||
// scope of captured variables we collected earlier.
|
// scope of captured variables we collected earlier.
|
||||||
@ -555,7 +556,7 @@ impl Eval for WithExpr {
|
|||||||
let callee = self.callee.eval(ctx);
|
let callee = self.callee.eval(ctx);
|
||||||
if let Some(func) = ctx.cast::<FuncValue>(callee, self.callee.span()) {
|
if let Some(func) = ctx.cast::<FuncValue>(callee, self.callee.span()) {
|
||||||
let applied = self.args.eval(ctx);
|
let applied = self.args.eval(ctx);
|
||||||
let name = func.name().map(|s| s.to_string());
|
let name = func.name().cloned();
|
||||||
Value::Func(FuncValue::new(name, move |ctx, args| {
|
Value::Func(FuncValue::new(name, move |ctx, args| {
|
||||||
// Remove named arguments that were overridden.
|
// Remove named arguments that were overridden.
|
||||||
let kept: Vec<_> = applied
|
let kept: Vec<_> = applied
|
||||||
@ -698,7 +699,7 @@ impl Eval for ImportExpr {
|
|||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let path = self.path.eval(ctx);
|
let path = self.path.eval(ctx);
|
||||||
if let Some(path) = ctx.cast::<String>(path, self.path.span()) {
|
if let Some(path) = ctx.cast::<EcoString>(path, self.path.span()) {
|
||||||
if let Some(hash) = ctx.import(&path, self.path.span()) {
|
if let Some(hash) = ctx.import(&path, self.path.span()) {
|
||||||
let mut module = &ctx.modules[&hash];
|
let mut module = &ctx.modules[&hash];
|
||||||
match &self.imports {
|
match &self.imports {
|
||||||
@ -734,7 +735,7 @@ impl Eval for IncludeExpr {
|
|||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let path = self.path.eval(ctx);
|
let path = self.path.eval(ctx);
|
||||||
if let Some(path) = ctx.cast::<String>(path, self.path.span()) {
|
if let Some(path) = ctx.cast::<EcoString>(path, self.path.span()) {
|
||||||
if let Some(hash) = ctx.import(&path, self.path.span()) {
|
if let Some(hash) = ctx.import(&path, self.path.span()) {
|
||||||
return Value::Template(ctx.modules[&hash].template.clone());
|
return Value::Template(ctx.modules[&hash].template.clone());
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter};
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::{AnyValue, EvalContext, FuncArgs, FuncValue, Type, Value};
|
use super::{AnyValue, EcoString, EvalContext, FuncArgs, FuncValue, Type, Value};
|
||||||
|
|
||||||
/// A slot where a variable is stored.
|
/// A slot where a variable is stored.
|
||||||
pub type Slot = Rc<RefCell<Value>>;
|
pub type Slot = Rc<RefCell<Value>>;
|
||||||
@ -39,17 +39,17 @@ impl<'a> Scopes<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Define a constant variable with a value in the active scope.
|
/// Define a constant variable with a value in the active scope.
|
||||||
pub fn def_const(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn def_const(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
|
||||||
self.top.def_const(var, value);
|
self.top.def_const(var, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a mutable variable with a value in the active scope.
|
/// Define a mutable variable with a value in the active scope.
|
||||||
pub fn def_mut(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn def_mut(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
|
||||||
self.top.def_mut(var, value);
|
self.top.def_mut(var, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a variable with a slot in the active scope.
|
/// Define a variable with a slot in the active scope.
|
||||||
pub fn def_slot(&mut self, var: impl Into<String>, slot: Slot) {
|
pub fn def_slot(&mut self, var: impl Into<EcoString>, slot: Slot) {
|
||||||
self.top.def_slot(var, slot);
|
self.top.def_slot(var, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ impl<'a> Scopes<'a> {
|
|||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
/// The mapping from names to slots.
|
/// The mapping from names to slots.
|
||||||
values: HashMap<String, Slot>,
|
values: HashMap<EcoString, Slot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
@ -76,7 +76,7 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Define a constant variable with a value.
|
/// Define a constant variable with a value.
|
||||||
pub fn def_const(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn def_const(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
|
||||||
let cell = RefCell::new(value.into());
|
let cell = RefCell::new(value.into());
|
||||||
|
|
||||||
// Make it impossible to write to this value again.
|
// Make it impossible to write to this value again.
|
||||||
@ -87,7 +87,7 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Define a constant function.
|
/// Define a constant function.
|
||||||
pub fn def_func<F>(&mut self, name: impl Into<String>, f: F)
|
pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F)
|
||||||
where
|
where
|
||||||
F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
|
F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
|
||||||
{
|
{
|
||||||
@ -96,7 +96,7 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Define a constant variable with a value of variant `Value::Any`.
|
/// Define a constant variable with a value of variant `Value::Any`.
|
||||||
pub fn def_any<T>(&mut self, var: impl Into<String>, any: T)
|
pub fn def_any<T>(&mut self, var: impl Into<EcoString>, any: T)
|
||||||
where
|
where
|
||||||
T: Type + Debug + Display + Clone + PartialEq + 'static,
|
T: Type + Debug + Display + Clone + PartialEq + 'static,
|
||||||
{
|
{
|
||||||
@ -104,12 +104,12 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Define a mutable variable with a value.
|
/// Define a mutable variable with a value.
|
||||||
pub fn def_mut(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn def_mut(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
|
||||||
self.values.insert(var.into(), Rc::new(RefCell::new(value.into())));
|
self.values.insert(var.into(), Rc::new(RefCell::new(value.into())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a variable with a slot.
|
/// Define a variable with a slot.
|
||||||
pub fn def_slot(&mut self, var: impl Into<String>, slot: Slot) {
|
pub fn def_slot(&mut self, var: impl Into<EcoString>, slot: Slot) {
|
||||||
self.values.insert(var.into(), slot);
|
self.values.insert(var.into(), slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,7 @@ use std::fmt::{self, Debug, Display, Formatter};
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::ops;
|
use super::*;
|
||||||
use super::EvalContext;
|
|
||||||
use crate::color::{Color, RgbaColor};
|
use crate::color::{Color, RgbaColor};
|
||||||
use crate::exec::ExecContext;
|
use crate::exec::ExecContext;
|
||||||
use crate::geom::{Angle, Fractional, Length, Linear, Relative};
|
use crate::geom::{Angle, Fractional, Length, Linear, Relative};
|
||||||
@ -38,7 +37,7 @@ pub enum Value {
|
|||||||
/// A color value: `#f79143ff`.
|
/// A color value: `#f79143ff`.
|
||||||
Color(Color),
|
Color(Color),
|
||||||
/// A string: `"string"`.
|
/// A string: `"string"`.
|
||||||
Str(String),
|
Str(EcoString),
|
||||||
/// An array value: `(1, "hi", 12cm)`.
|
/// An array value: `(1, "hi", 12cm)`.
|
||||||
Array(ArrayValue),
|
Array(ArrayValue),
|
||||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
||||||
@ -76,7 +75,7 @@ impl Value {
|
|||||||
Self::Linear(_) => Linear::TYPE_NAME,
|
Self::Linear(_) => Linear::TYPE_NAME,
|
||||||
Self::Fractional(_) => Fractional::TYPE_NAME,
|
Self::Fractional(_) => Fractional::TYPE_NAME,
|
||||||
Self::Color(_) => Color::TYPE_NAME,
|
Self::Color(_) => Color::TYPE_NAME,
|
||||||
Self::Str(_) => String::TYPE_NAME,
|
Self::Str(_) => EcoString::TYPE_NAME,
|
||||||
Self::Array(_) => ArrayValue::TYPE_NAME,
|
Self::Array(_) => ArrayValue::TYPE_NAME,
|
||||||
Self::Dict(_) => DictValue::TYPE_NAME,
|
Self::Dict(_) => DictValue::TYPE_NAME,
|
||||||
Self::Template(_) => TemplateValue::TYPE_NAME,
|
Self::Template(_) => TemplateValue::TYPE_NAME,
|
||||||
@ -151,7 +150,7 @@ impl Default for Value {
|
|||||||
pub type ArrayValue = Vec<Value>;
|
pub type ArrayValue = Vec<Value>;
|
||||||
|
|
||||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
||||||
pub type DictValue = BTreeMap<String, Value>;
|
pub type DictValue = BTreeMap<EcoString, Value>;
|
||||||
|
|
||||||
/// A template value: `[*Hi* there]`.
|
/// A template value: `[*Hi* there]`.
|
||||||
pub type TemplateValue = Rc<Vec<TemplateNode>>;
|
pub type TemplateValue = Rc<Vec<TemplateNode>>;
|
||||||
@ -171,7 +170,7 @@ pub enum TemplateNode {
|
|||||||
map: ExprMap,
|
map: ExprMap,
|
||||||
},
|
},
|
||||||
/// A template that was converted from a string.
|
/// A template that was converted from a string.
|
||||||
Str(String),
|
Str(EcoString),
|
||||||
/// A function template that can implement custom behaviour.
|
/// A function template that can implement custom behaviour.
|
||||||
Func(TemplateFunc),
|
Func(TemplateFunc),
|
||||||
}
|
}
|
||||||
@ -224,14 +223,14 @@ impl Debug for TemplateFunc {
|
|||||||
pub struct FuncValue {
|
pub struct FuncValue {
|
||||||
/// The string is boxed to make the whole struct fit into 24 bytes, so that
|
/// The string is boxed to make the whole struct fit into 24 bytes, so that
|
||||||
/// a [`Value`] fits into 32 bytes.
|
/// a [`Value`] fits into 32 bytes.
|
||||||
name: Option<Box<String>>,
|
name: Option<Box<EcoString>>,
|
||||||
/// The closure that defines the function.
|
/// The closure that defines the function.
|
||||||
f: Rc<dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value>,
|
f: Rc<dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncValue {
|
impl FuncValue {
|
||||||
/// Create a new function value from a rust function or closure.
|
/// Create a new function value from a rust function or closure.
|
||||||
pub fn new<F>(name: Option<String>, f: F) -> Self
|
pub fn new<F>(name: Option<EcoString>, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
|
F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
|
||||||
{
|
{
|
||||||
@ -239,8 +238,8 @@ impl FuncValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the function.
|
/// The name of the function.
|
||||||
pub fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&EcoString> {
|
||||||
self.name.as_ref().map(|s| s.as_str())
|
self.name.as_ref().map(|s| &**s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +341,7 @@ impl FuncArgs {
|
|||||||
let index = self
|
let index = self
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.position(|arg| arg.name.as_ref().map_or(false, |other| name == other))?;
|
.position(|arg| arg.name.as_ref().map_or(false, |other| other == name))?;
|
||||||
|
|
||||||
let value = self.items.remove(index).value;
|
let value = self.items.remove(index).value;
|
||||||
let span = value.span;
|
let span = value.span;
|
||||||
@ -381,7 +380,7 @@ pub struct FuncArg {
|
|||||||
/// The span of the whole argument.
|
/// The span of the whole argument.
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
/// The name of the argument (`None` for positional arguments).
|
/// The name of the argument (`None` for positional arguments).
|
||||||
pub name: Option<String>,
|
pub name: Option<EcoString>,
|
||||||
/// The value of the argument.
|
/// The value of the argument.
|
||||||
pub value: Spanned<Value>,
|
pub value: Spanned<Value>,
|
||||||
}
|
}
|
||||||
@ -608,7 +607,7 @@ primitive! {
|
|||||||
}
|
}
|
||||||
primitive! { Fractional: "fractional", Value::Fractional }
|
primitive! { Fractional: "fractional", Value::Fractional }
|
||||||
primitive! { Color: "color", Value::Color }
|
primitive! { Color: "color", Value::Color }
|
||||||
primitive! { String: "string", Value::Str }
|
primitive! { EcoString: "string", Value::Str }
|
||||||
primitive! { ArrayValue: "array", Value::Array }
|
primitive! { ArrayValue: "array", Value::Array }
|
||||||
primitive! { DictValue: "dictionary", Value::Dict }
|
primitive! { DictValue: "dictionary", Value::Dict }
|
||||||
primitive! {
|
primitive! {
|
||||||
@ -624,9 +623,15 @@ impl From<usize> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<String> for Value {
|
||||||
|
fn from(v: String) -> Self {
|
||||||
|
Self::Str(v.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&str> for Value {
|
impl From<&str> for Value {
|
||||||
fn from(v: &str) -> Self {
|
fn from(v: &str) -> Self {
|
||||||
Self::Str(v.to_string())
|
Self::Str(v.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use super::{Exec, ExecWithMap, FontFamily, State};
|
use super::{Exec, ExecWithMap, FontFamily, State};
|
||||||
use crate::diag::{Diag, DiagSet, Pass};
|
use crate::diag::{Diag, DiagSet, Pass};
|
||||||
|
use crate::eco::EcoString;
|
||||||
use crate::eval::{ExprMap, TemplateValue};
|
use crate::eval::{ExprMap, TemplateValue};
|
||||||
use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size};
|
use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
@ -77,7 +78,7 @@ impl ExecContext {
|
|||||||
/// Push text into the active paragraph.
|
/// Push text into the active paragraph.
|
||||||
///
|
///
|
||||||
/// The text is split into lines at newlines.
|
/// The text is split into lines at newlines.
|
||||||
pub fn push_text(&mut self, text: impl Into<String>) {
|
pub fn push_text(&mut self, text: impl Into<EcoString>) {
|
||||||
self.stack.par.push(self.make_text_node(text));
|
self.stack.par.push(self.make_text_node(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +144,7 @@ impl ExecContext {
|
|||||||
Pass::new(self.tree, self.diags)
|
Pass::new(self.tree, self.diags)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_text_node(&self, text: impl Into<String>) -> ParChild {
|
fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild {
|
||||||
ParChild::Text(
|
ParChild::Text(
|
||||||
text.into(),
|
text.into(),
|
||||||
self.state.aligns.cross,
|
self.state.aligns.cross,
|
||||||
|
@ -6,6 +6,7 @@ mod state;
|
|||||||
pub use context::*;
|
pub use context::*;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
|
|
||||||
|
use std::fmt::Write;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::diag::Pass;
|
use crate::diag::Pass;
|
||||||
@ -13,6 +14,7 @@ use crate::eval::{ExprMap, TemplateFunc, TemplateNode, TemplateValue, Value};
|
|||||||
use crate::geom::{Dir, Gen};
|
use crate::geom::{Dir, Gen};
|
||||||
use crate::layout::{LayoutTree, StackChild, StackNode};
|
use crate::layout::{LayoutTree, StackChild, StackNode};
|
||||||
use crate::pretty::pretty;
|
use crate::pretty::pretty;
|
||||||
|
use crate::eco::EcoString;
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
|
|
||||||
/// Execute a template to produce a layout tree.
|
/// Execute a template to produce a layout tree.
|
||||||
@ -102,18 +104,25 @@ impl ExecWithMap for HeadingNode {
|
|||||||
|
|
||||||
impl ExecWithMap for ListItem {
|
impl ExecWithMap for ListItem {
|
||||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
||||||
exec_item(ctx, "•".to_string(), &self.body, map);
|
exec_item(ctx, '•'.into(), &self.body, map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecWithMap for EnumItem {
|
impl ExecWithMap for EnumItem {
|
||||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
||||||
let label = self.number.unwrap_or(1).to_string() + ".";
|
let mut label = EcoString::new();
|
||||||
|
write!(&mut label, "{}", self.number.unwrap_or(1)).unwrap();
|
||||||
|
label.push('.');
|
||||||
exec_item(ctx, label, &self.body, map);
|
exec_item(ctx, label, &self.body, map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_item(ctx: &mut ExecContext, label: String, body: &SyntaxTree, map: &ExprMap) {
|
fn exec_item(
|
||||||
|
ctx: &mut ExecContext,
|
||||||
|
label: EcoString,
|
||||||
|
body: &SyntaxTree,
|
||||||
|
map: &ExprMap,
|
||||||
|
) {
|
||||||
let label = ctx.exec_stack(|ctx| ctx.push_text(label));
|
let label = ctx.exec_stack(|ctx| ctx.push_text(label));
|
||||||
let body = ctx.exec_tree_stack(body, map);
|
let body = ctx.exec_tree_stack(body, map);
|
||||||
let stack = StackNode {
|
let stack = StackNode {
|
||||||
|
@ -6,6 +6,7 @@ use xi_unicode::LineBreakIterator;
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::exec::FontState;
|
use crate::exec::FontState;
|
||||||
|
use crate::eco::EcoString;
|
||||||
use crate::util::{RangeExt, SliceExt};
|
use crate::util::{RangeExt, SliceExt};
|
||||||
|
|
||||||
type Range = std::ops::Range<usize>;
|
type Range = std::ops::Range<usize>;
|
||||||
@ -29,7 +30,7 @@ pub enum ParChild {
|
|||||||
/// Spacing between other nodes.
|
/// Spacing between other nodes.
|
||||||
Spacing(Length),
|
Spacing(Length),
|
||||||
/// A run of text and how to align it in its line.
|
/// A run of text and how to align it in its line.
|
||||||
Text(String, Align, Rc<FontState>),
|
Text(EcoString, Align, Rc<FontState>),
|
||||||
/// Any child node and how to align it in its line.
|
/// Any child node and how to align it in its line.
|
||||||
Any(AnyNode, Align),
|
Any(AnyNode, Align),
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ pub mod diag;
|
|||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
pub mod eco;
|
||||||
pub mod exec;
|
pub mod exec;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
|
@ -9,7 +9,7 @@ use crate::layout::{
|
|||||||
|
|
||||||
/// `image`: An image.
|
/// `image`: An image.
|
||||||
pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let path = args.expect::<Spanned<String>>(ctx, "path to image file");
|
let path = args.expect::<Spanned<EcoString>>(ctx, "path to image file");
|
||||||
let width = args.named(ctx, "width");
|
let width = args.named(ctx, "width");
|
||||||
let height = args.named(ctx, "height");
|
let height = args.named(ctx, "height");
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use crate::paper::{Paper, PaperClass};
|
|||||||
/// `page`: Configure pages.
|
/// `page`: Configure pages.
|
||||||
pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let span = args.span;
|
let span = args.span;
|
||||||
let paper = args.eat::<Spanned<String>>(ctx).and_then(|name| {
|
let paper = args.eat::<Spanned<EcoString>>(ctx).and_then(|name| {
|
||||||
Paper::from_name(&name.v).or_else(|| {
|
Paper::from_name(&name.v).or_else(|| {
|
||||||
ctx.diag(error!(name.span, "invalid paper name"));
|
ctx.diag(error!(name.span, "invalid paper name"));
|
||||||
None
|
None
|
||||||
|
@ -17,6 +17,7 @@ use std::fmt::{self, Display, Formatter};
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::color::{Color, RgbaColor};
|
use crate::color::{Color, RgbaColor};
|
||||||
|
use crate::eco::EcoString;
|
||||||
use crate::eval::{EvalContext, FuncArgs, Scope, TemplateValue, Value};
|
use crate::eval::{EvalContext, FuncArgs, Scope, TemplateValue, Value};
|
||||||
use crate::exec::{Exec, FontFamily};
|
use crate::exec::{Exec, FontFamily};
|
||||||
use crate::font::{FontStyle, FontWeight, VerticalFontMetric};
|
use crate::font::{FontStyle, FontWeight, VerticalFontMetric};
|
||||||
|
@ -99,7 +99,7 @@ castable! {
|
|||||||
Value::Array(values) => Self(values
|
Value::Array(values) => Self(values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|v| v.cast().ok())
|
.filter_map(|v| v.cast().ok())
|
||||||
.map(|string: String| string.to_lowercase())
|
.map(|string: EcoString| string.to_lowercase())
|
||||||
.collect()
|
.collect()
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -185,7 +185,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|||||||
|
|
||||||
/// `lang`: Configure the language.
|
/// `lang`: Configure the language.
|
||||||
pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let iso = args.eat::<String>(ctx).map(|s| lang_dir(&s));
|
let iso = args.eat::<EcoString>(ctx).map(|s| lang_dir(&s));
|
||||||
let dir = match args.named::<Spanned<Dir>>(ctx, "dir") {
|
let dir = match args.named::<Spanned<Dir>>(ctx, "dir") {
|
||||||
Some(dir) if dir.v.axis() == SpecAxis::Horizontal => Some(dir.v),
|
Some(dir) if dir.v.axis() == SpecAxis::Horizontal => Some(dir.v),
|
||||||
Some(dir) => {
|
Some(dir) => {
|
||||||
|
@ -39,7 +39,7 @@ pub fn len(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|||||||
/// `rgb`: Create an RGB(A) color.
|
/// `rgb`: Create an RGB(A) color.
|
||||||
pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
Value::Color(Color::Rgba(
|
Value::Color(Color::Rgba(
|
||||||
if let Some(string) = args.eat::<Spanned<String>>(ctx) {
|
if let Some(string) = args.eat::<Spanned<EcoString>>(ctx) {
|
||||||
match RgbaColor::from_str(&string.v) {
|
match RgbaColor::from_str(&string.v) {
|
||||||
Ok(color) => color,
|
Ok(color) => color,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -15,6 +15,7 @@ pub use tokens::*;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::diag::Pass;
|
use crate::diag::Pass;
|
||||||
|
use crate::eco::EcoString;
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
|
|
||||||
/// Parse a string of source code.
|
/// Parse a string of source code.
|
||||||
@ -151,10 +152,10 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a unicode escape sequence.
|
/// Handle a unicode escape sequence.
|
||||||
fn unicode_escape(p: &mut Parser, token: UnicodeEscapeToken) -> String {
|
fn unicode_escape(p: &mut Parser, token: UnicodeEscapeToken) -> EcoString {
|
||||||
let span = p.peek_span();
|
let span = p.peek_span();
|
||||||
let text = if let Some(c) = resolve::resolve_hex(token.sequence) {
|
let text = if let Some(c) = resolve::resolve_hex(token.sequence) {
|
||||||
c.to_string()
|
c.into()
|
||||||
} else {
|
} else {
|
||||||
// Print out the escape sequence verbatim if it is invalid.
|
// Print out the escape sequence verbatim if it is invalid.
|
||||||
p.diag(error!(span, "invalid unicode escape sequence"));
|
p.diag(error!(span, "invalid unicode escape sequence"));
|
||||||
@ -774,7 +775,7 @@ fn ident(p: &mut Parser) -> Option<Ident> {
|
|||||||
if let Some(Token::Ident(string)) = p.peek() {
|
if let Some(Token::Ident(string)) = p.peek() {
|
||||||
Some(Ident {
|
Some(Ident {
|
||||||
span: p.eat_span(),
|
span: p.eat_span(),
|
||||||
string: string.to_string(),
|
string: string.into(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
p.expected("identifier");
|
p.expected("identifier");
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use super::{is_newline, Scanner};
|
use super::{is_newline, Scanner};
|
||||||
|
use crate::eco::EcoString;
|
||||||
use crate::syntax::{Ident, RawNode, Span};
|
use crate::syntax::{Ident, RawNode, Span};
|
||||||
|
|
||||||
/// Resolve 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) -> EcoString {
|
||||||
let mut out = String::with_capacity(string.len());
|
let mut out = EcoString::with_capacity(string.len());
|
||||||
let mut s = Scanner::new(string);
|
let mut s = Scanner::new(string);
|
||||||
|
|
||||||
while let Some(c) = s.eat() {
|
while let Some(c) = s.eat() {
|
||||||
@ -52,12 +53,12 @@ pub fn resolve_raw(span: Span, text: &str, backticks: usize) -> RawNode {
|
|||||||
let (tag, inner) = split_at_lang_tag(text);
|
let (tag, inner) = split_at_lang_tag(text);
|
||||||
let (text, block) = trim_and_split_raw(inner);
|
let (text, block) = trim_and_split_raw(inner);
|
||||||
let lang = Ident::new(tag, span.start .. span.start + tag.len());
|
let lang = Ident::new(tag, span.start .. span.start + tag.len());
|
||||||
RawNode { span, lang, text, block }
|
RawNode { span, lang, text: text.into(), block }
|
||||||
} else {
|
} else {
|
||||||
RawNode {
|
RawNode {
|
||||||
span,
|
span,
|
||||||
lang: None,
|
lang: None,
|
||||||
text: split_lines(text).join("\n"),
|
text: split_lines(text).join("\n").into(),
|
||||||
block: false,
|
block: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -617,7 +617,7 @@ mod tests {
|
|||||||
($($k:ident: $v:expr),* $(,)?) => {{
|
($($k:ident: $v:expr),* $(,)?) => {{
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut m = BTreeMap::new();
|
let mut m = BTreeMap::new();
|
||||||
$(m.insert(stringify!($k).to_string(), $v);)*
|
$(m.insert(stringify!($k).into(), $v);)*
|
||||||
m
|
m
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ pub enum Expr {
|
|||||||
/// A fraction unit literal: `1fr`.
|
/// A fraction unit literal: `1fr`.
|
||||||
Fractional(Span, f64),
|
Fractional(Span, f64),
|
||||||
/// A string literal: `"hello!"`.
|
/// A string literal: `"hello!"`.
|
||||||
Str(Span, String),
|
Str(Span, EcoString),
|
||||||
/// An identifier: `left`.
|
/// An identifier: `left`.
|
||||||
Ident(Ident),
|
Ident(Ident),
|
||||||
/// An array expression: `(1, "hi", 12cm)`.
|
/// An array expression: `(1, "hi", 12cm)`.
|
||||||
|
@ -3,6 +3,7 @@ use std::ops::Deref;
|
|||||||
use unicode_xid::UnicodeXID;
|
use unicode_xid::UnicodeXID;
|
||||||
|
|
||||||
use super::Span;
|
use super::Span;
|
||||||
|
use crate::eco::EcoString;
|
||||||
|
|
||||||
/// An unicode identifier with a few extra permissible characters.
|
/// An unicode identifier with a few extra permissible characters.
|
||||||
///
|
///
|
||||||
@ -16,7 +17,7 @@ pub struct Ident {
|
|||||||
/// The source code location.
|
/// The source code location.
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
/// The identifier string.
|
/// The identifier string.
|
||||||
pub string: String,
|
pub string: EcoString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ident {
|
impl Ident {
|
||||||
@ -26,7 +27,10 @@ impl Ident {
|
|||||||
span: impl Into<Span>,
|
span: impl Into<Span>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
if is_ident(string.as_ref()) {
|
if is_ident(string.as_ref()) {
|
||||||
Some(Self { span: span.into(), string: string.into() })
|
Some(Self {
|
||||||
|
span: span.into(),
|
||||||
|
string: EcoString::from_str(string),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -34,13 +38,13 @@ impl Ident {
|
|||||||
|
|
||||||
/// Return a reference to the underlying string.
|
/// Return a reference to the underlying string.
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
self.string.as_str()
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<str> for Ident {
|
impl AsRef<str> for Ident {
|
||||||
fn as_ref(&self) -> &str {
|
fn as_ref(&self) -> &str {
|
||||||
self.as_str()
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +52,7 @@ impl Deref for Ident {
|
|||||||
type Target = str;
|
type Target = str;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
self.as_str()
|
self.string.as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ pub use node::*;
|
|||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use token::*;
|
pub use token::*;
|
||||||
|
|
||||||
|
use crate::eco::EcoString;
|
||||||
|
|
||||||
/// The abstract syntax tree.
|
/// The abstract syntax tree.
|
||||||
///
|
///
|
||||||
/// This type can represent a full parsed document.
|
/// This type can represent a full parsed document.
|
||||||
|
@ -6,7 +6,7 @@ use super::*;
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
/// Plain text.
|
/// Plain text.
|
||||||
Text(String),
|
Text(EcoString),
|
||||||
/// Whitespace containing less than two newlines.
|
/// Whitespace containing less than two newlines.
|
||||||
Space,
|
Space,
|
||||||
/// A forced line break: `\`.
|
/// A forced line break: `\`.
|
||||||
@ -38,7 +38,7 @@ pub struct RawNode {
|
|||||||
pub lang: Option<Ident>,
|
pub lang: Option<Ident>,
|
||||||
/// The raw text, determined as the raw string between the backticks trimmed
|
/// The raw text, determined as the raw string between the backticks trimmed
|
||||||
/// according to the above rules.
|
/// according to the above rules.
|
||||||
pub text: String,
|
pub text: EcoString,
|
||||||
/// Whether the element is block-level, that is, it has 3+ backticks
|
/// Whether the element is block-level, that is, it has 3+ backticks
|
||||||
/// and contains at least one newline.
|
/// and contains at least one newline.
|
||||||
pub block: bool,
|
pub block: bool,
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
// Addition.
|
// Addition.
|
||||||
#test(2 + 4, 6)
|
#test(2 + 4, 6)
|
||||||
#test("a" + "b", "ab")
|
#test("a" + "b", "ab")
|
||||||
|
#test(13 * "a" + "bbbbbb", "aaaaaaaaaaaaabbbbbb")
|
||||||
#test((1, 2) + (3, 4), (1, 2, 3, 4))
|
#test((1, 2) + (3, 4), (1, 2, 3, 4))
|
||||||
#test((a: 1) + (b: 2, c: 3), (a: 1, b: 2, c: 3))
|
#test((a: 1) + (b: 2, c: 3), (a: 1, b: 2, c: 3))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user