Compare commits
6 Commits
5616237eea
...
dfdf8d73b0
Author | SHA1 | Date |
---|---|---|
mars | dfdf8d73b0 | |
mars | b89496f15b | |
mars | bb11a857da | |
mars | c8fd64bd79 | |
mars | 461e9b6529 | |
mars | b1b5b187ba |
|
@ -87,35 +87,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "lexpr"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ceee0b80e0043f17bf81130471e1b0975179af75fe657af45577d80e2698fe3b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"lexpr-macros",
|
||||
"proc-macro-hack",
|
||||
"ryu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexpr-macros"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd627fb38e19c00d8d068618259205f7a91c91aeade5c15bc35dbca037bb1c35"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.134"
|
||||
|
@ -157,12 +128,6 @@ version = "1.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.46"
|
||||
|
@ -204,12 +169,6 @@ version = "1.0.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.1"
|
||||
|
@ -267,7 +226,7 @@ dependencies = [
|
|||
"ansi_term",
|
||||
"bitflags",
|
||||
"figlet-rs",
|
||||
"lexpr",
|
||||
"fnv",
|
||||
"logos",
|
||||
"strum",
|
||||
"textwrap",
|
||||
|
|
|
@ -4,10 +4,10 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12"
|
||||
bitflags = "1.3"
|
||||
figlet-rs = "0.1"
|
||||
lexpr = "0.2"
|
||||
fnv = "1.0"
|
||||
logos = "0.12"
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
ansi_term = "0.12"
|
||||
textwrap = "0.15"
|
||||
|
|
13
src/ast.rs
13
src/ast.rs
|
@ -16,6 +16,9 @@ pub enum TokenKind {
|
|||
#[regex(r"[a-zA-Z-][a-zA-Z0-9-]*")]
|
||||
Symbol,
|
||||
|
||||
#[regex(r"-?[0-9]*")]
|
||||
Number,
|
||||
|
||||
#[regex(":[a-zA-Z-][a-zA-Z0-9-]*")]
|
||||
Keyword,
|
||||
|
||||
|
@ -221,6 +224,13 @@ impl<'a> AstBuilder<'a> {
|
|||
TokenKind::Eof | TokenKind::ParenClose => Ok(None),
|
||||
TokenKind::String => Ok(Some(self.token_to_string(token)?.to_val_string())),
|
||||
TokenKind::Symbol => Ok(Some(self.token_to_string(token)?.to_val_symbol())),
|
||||
TokenKind::Number => {
|
||||
if let Ok(val) = token.content.parse() {
|
||||
Ok(Some(token.map(ValueKind::Number(val))))
|
||||
} else {
|
||||
Err(token.map(ParseErrorKind::BadNumber))
|
||||
}
|
||||
}
|
||||
TokenKind::ParenOpen => {
|
||||
let start = token.range.end;
|
||||
let next = self.expect_next()?;
|
||||
|
@ -246,6 +256,7 @@ impl<'a> AstBuilder<'a> {
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum ValueKind {
|
||||
String(String),
|
||||
Number(i32),
|
||||
Symbol(String),
|
||||
Tag(Tag),
|
||||
List(Vec<Value>),
|
||||
|
@ -412,6 +423,7 @@ pub enum ParseErrorKind {
|
|||
UnmatchedParen,
|
||||
UnexpectedToken(TokenKind, Option<TokenKind>),
|
||||
UnexpectedEof,
|
||||
BadNumber,
|
||||
}
|
||||
|
||||
impl Display for ParseErrorKind {
|
||||
|
@ -428,6 +440,7 @@ impl Display for ParseErrorKind {
|
|||
}
|
||||
UnexpectedToken(kind, None) => write!(f, "unexpected token {:?}", kind),
|
||||
UnexpectedEof => write!(f, "unexpected end-of-file"),
|
||||
BadNumber => write!(f, "badly-formed number"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
use strum::EnumString;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumString)]
|
||||
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
|
||||
pub enum BlockTagKind {
|
||||
Section,
|
||||
P,
|
||||
Figlet,
|
||||
Code,
|
||||
Ul,
|
||||
Ol,
|
||||
Li,
|
||||
Box,
|
||||
}
|
||||
|
||||
impl BlockTagKind {
|
||||
pub fn children(&self) -> AllowedChildren {
|
||||
use BlockTagKind::*;
|
||||
match self {
|
||||
Section => AllowedChildren::Block,
|
||||
P => AllowedChildren::TaggedText,
|
||||
Figlet | Code => AllowedChildren::PlainText,
|
||||
Ul | Ol => AllowedChildren::Specific(&[Li, Ul, Ol]),
|
||||
Li => AllowedChildren::TaggedText,
|
||||
Box => AllowedChildren::Block,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AllowedChildren {
|
||||
Block,
|
||||
PlainText,
|
||||
TaggedText,
|
||||
Specific(&'static [BlockTagKind]),
|
||||
}
|
||||
|
||||
impl AllowedChildren {
|
||||
pub fn allows_styled_text(&self) -> bool {
|
||||
use AllowedChildren::*;
|
||||
match self {
|
||||
TaggedText => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allows_block(&self, kind: &BlockTagKind) -> bool {
|
||||
use AllowedChildren::*;
|
||||
match self {
|
||||
Block => true,
|
||||
Specific(allowed) => allowed.contains(kind),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
290
src/dom.rs
290
src/dom.rs
|
@ -1,6 +1,290 @@
|
|||
use crate::tag::BlockTag;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Debug, Hash)]
|
||||
use crate::ast::{self, TagBody, WithSource};
|
||||
use crate::block::{AllowedChildren, BlockTagKind};
|
||||
use crate::node::{NodeId, NodeStore};
|
||||
use crate::text::{TaggedText, TextBlock, TextParseErrorKind, TextStyle, TextTagKind, TextParseError, TextLayout};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dom {
|
||||
pub tags: Vec<BlockTag>,
|
||||
pub root: NodeId,
|
||||
pub nodes: NodeStore,
|
||||
}
|
||||
|
||||
impl Dom {
|
||||
pub fn parse(body: &TagBody) -> DomResult<Self> {
|
||||
let mut builder = DomBuilder::default();
|
||||
let root = builder.new_node();
|
||||
let mut block = BlockBuilder::new(BlockTagKind::Box, root);
|
||||
let mut text_style = TextStyle::default();
|
||||
|
||||
let mut state = DomWalkState {
|
||||
builder: &mut builder,
|
||||
block: &mut block,
|
||||
text_style: &mut text_style,
|
||||
};
|
||||
|
||||
state.add_children(&body.children)?;
|
||||
|
||||
block.build(&mut builder);
|
||||
|
||||
Ok(Self {
|
||||
root,
|
||||
nodes: builder.node_store,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
self.print_from(self.root, 0);
|
||||
}
|
||||
|
||||
pub fn print_from(&self, node: NodeId, indent: usize) {
|
||||
let margin: String = std::iter::repeat(" ").take(indent * 4).collect();
|
||||
let kind: &NodeKind = self.nodes.get(node).unwrap();
|
||||
let children: &NodeChildren = self.nodes.get(node).unwrap();
|
||||
let text = self.nodes.get::<TextBlock>(node);
|
||||
println!("{}{:?}: {:?} {:?}", margin, kind, children, text);
|
||||
|
||||
for child in children.0.iter() {
|
||||
self.print_from(*child, indent + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NodeChildren(Vec<NodeId>);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct NodeParent(NodeId);
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum NodeKind {
|
||||
Block,
|
||||
Text,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DomBuilder {
|
||||
pub node_store: NodeStore,
|
||||
pub next_node: NodeId,
|
||||
}
|
||||
|
||||
impl Deref for DomBuilder {
|
||||
type Target = NodeStore;
|
||||
fn deref(&self) -> &NodeStore {
|
||||
&self.node_store
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DomBuilder {
|
||||
fn deref_mut(&mut self) -> &mut NodeStore {
|
||||
&mut self.node_store
|
||||
}
|
||||
}
|
||||
|
||||
impl DomBuilder {
|
||||
pub fn new_node(&mut self) -> NodeId {
|
||||
let node = self.next_node;
|
||||
self.next_node += 1;
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BlockBuilder {
|
||||
pub id: NodeId,
|
||||
pub children: Vec<NodeId>,
|
||||
pub allowed_children: AllowedChildren,
|
||||
pub kind: BlockTagKind,
|
||||
}
|
||||
|
||||
impl BlockBuilder {
|
||||
pub fn new(kind: BlockTagKind, id: NodeId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
children: Vec::new(),
|
||||
allowed_children: kind.children(),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self, dom: &mut DomBuilder) -> NodeId {
|
||||
for child in self.children.iter() {
|
||||
dom.insert(*child, NodeParent(self.id));
|
||||
}
|
||||
|
||||
let children = NodeChildren(self.children);
|
||||
dom.insert(self.id, children);
|
||||
|
||||
use BlockTagKind::*;
|
||||
let kind = match self.kind {
|
||||
P | Figlet | Code | Li => NodeKind::Text,
|
||||
Ul | Ol | Box | Section => NodeKind::Block,
|
||||
};
|
||||
|
||||
dom.insert(self.id, kind);
|
||||
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DomWalkState<'a> {
|
||||
pub builder: &'a mut DomBuilder,
|
||||
pub block: &'a mut BlockBuilder,
|
||||
pub text_style: &'a mut TextStyle,
|
||||
}
|
||||
|
||||
impl<'a> DomWalkState<'a> {
|
||||
pub fn fork(&mut self) -> DomWalkState {
|
||||
DomWalkState {
|
||||
builder: &mut *self.builder,
|
||||
block: &mut *self.block,
|
||||
text_style: &mut *self.text_style,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_text_block(&mut self, text: TaggedText) -> TextBlock {
|
||||
use BlockTagKind::*;
|
||||
match self.block.kind {
|
||||
P => TextBlock {
|
||||
text,
|
||||
initial_indent: None,
|
||||
subsequent_indent: None,
|
||||
},
|
||||
_ => unimplemented!("Text block for {:?} tag", self.block.kind),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_text_block(&mut self, text: TaggedText) -> NodeId {
|
||||
let node = self.builder.new_node();
|
||||
let block = self.make_text_block(text);
|
||||
self.builder.insert(node, block);
|
||||
node
|
||||
}
|
||||
|
||||
pub fn make_text_layout(&self, text: String) -> TextLayout {
|
||||
use BlockTagKind::*;
|
||||
match self.block.kind {
|
||||
Figlet => {
|
||||
let font = figlet_rs::FIGfont::standard().unwrap();
|
||||
let figure = font.convert(&text).expect("Failed to FIG-ify text");
|
||||
TextLayout::from_plain(&figure.to_string())
|
||||
},
|
||||
Code => TextLayout::from_plain(&text),
|
||||
_ => unimplemented!("Text layout for {:?} tag", self.block.kind),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_block_tag(&mut self, kind: BlockTagKind, body: &ast::TagBody) -> DomResult<NodeId> {
|
||||
let node = self.builder.new_node();
|
||||
self.block.children.push(node);
|
||||
|
||||
let mut block = BlockBuilder::new(kind, node);
|
||||
let mut state = DomWalkState {
|
||||
block: &mut block,
|
||||
..self.fork()
|
||||
};
|
||||
|
||||
match kind.children() {
|
||||
AllowedChildren::TaggedText => {
|
||||
let text = TaggedText::parse(&state.text_style, &body.children)
|
||||
.map_err(|e| e.map(e.inner.clone().into()))?;
|
||||
let block = state.make_text_block(text);
|
||||
self.builder.insert(node, block);
|
||||
}
|
||||
AllowedChildren::PlainText => {
|
||||
let text = crate::text::parse_plain(&body.children)
|
||||
.map_err(|e| e.map(e.inner.clone().into()))?;
|
||||
let layout = state.make_text_layout(text);
|
||||
self.builder.insert(node, layout);
|
||||
}
|
||||
_ => {
|
||||
state.add_children(&body.children)?;
|
||||
}
|
||||
}
|
||||
|
||||
block.build(&mut self.builder);
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
pub fn add_tag(mut self, tag: &ast::Tag) -> DomResult<()> {
|
||||
if let Ok(kind) = TextTagKind::from_str(&tag.id) {
|
||||
if !self.block.allowed_children.allows_styled_text() {
|
||||
return Err(tag.id.map(DomErrorKind::InvalidChild));
|
||||
}
|
||||
|
||||
let style = &self.text_style;
|
||||
let styled = TaggedText::parse_tag_body(style, &kind, &tag.body)
|
||||
.map_err(|e| e.map(e.inner.clone().into()))?;
|
||||
self.add_text_block(styled);
|
||||
} else if let Ok(kind) = BlockTagKind::from_str(&tag.id) {
|
||||
if !self.block.allowed_children.allows_block(&kind) {
|
||||
return Err(tag.id.map(DomErrorKind::InvalidChild));
|
||||
}
|
||||
|
||||
self.add_block_tag(kind, &tag.body)?;
|
||||
} else {
|
||||
return Err(tag.id.map(DomErrorKind::InvalidTag));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_children(&mut self, vals: &[ast::Value]) -> DomResult<()> {
|
||||
for child in vals.iter() {
|
||||
self.add_child(child)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_child(&mut self, val: &ast::Value) -> DomResult<()> {
|
||||
use ast::ValueKind::*;
|
||||
match &val.inner {
|
||||
Tag(tag) => {
|
||||
let state = self.fork();
|
||||
state.add_tag(tag)
|
||||
}
|
||||
List(list) => self.add_children(list),
|
||||
String(_string) => {
|
||||
println!("{}", val.map("Insert string code here:"));
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(val.map(DomErrorKind::InvalidChild)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DomResult<T> = Result<T, WithSource<DomErrorKind>>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DomErrorKind {
|
||||
InvalidTag,
|
||||
InvalidChild,
|
||||
TextError(TextParseErrorKind),
|
||||
}
|
||||
|
||||
impl From<TextParseErrorKind> for DomErrorKind {
|
||||
fn from(e: TextParseErrorKind) -> Self {
|
||||
DomErrorKind::TextError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DomErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use DomErrorKind::*;
|
||||
if let TextError(e) = &self {
|
||||
return e.fmt(f);
|
||||
}
|
||||
|
||||
write!(f, "DOM error: ")?;
|
||||
|
||||
match self {
|
||||
InvalidTag => write!(f, "invalid tag"),
|
||||
InvalidChild => write!(f, "invalid child"),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -1,22 +1,13 @@
|
|||
pub mod ast;
|
||||
pub mod block;
|
||||
pub mod dom;
|
||||
pub mod layout;
|
||||
pub mod parse;
|
||||
pub mod style;
|
||||
pub mod tag;
|
||||
pub mod node;
|
||||
pub mod text;
|
||||
|
||||
use logos::Logos;
|
||||
use style::Stylesheet;
|
||||
|
||||
fn main() {
|
||||
let src_path = std::env::args().nth(1).unwrap();
|
||||
let src = std::fs::read_to_string(src_path).unwrap();
|
||||
|
||||
let lexer = ast::TokenKind::lexer(&src);
|
||||
let tokens: Vec<_> = lexer.collect();
|
||||
println!("{:?}", tokens);
|
||||
|
||||
let source = std::sync::Arc::new(ast::Source::from_string(src.clone()));
|
||||
let mut ast_builder = ast::AstBuilder::new(&source);
|
||||
let body = match ast_builder.parse_tag_body() {
|
||||
|
@ -26,30 +17,11 @@ fn main() {
|
|||
|
||||
println!("{:#?}", body);
|
||||
|
||||
let style = text::TextStyle::default();
|
||||
let text = match text::StyledText::parse(&style, &body.children) {
|
||||
Ok(text) => text,
|
||||
Err(e) => panic!("Text parse error:\n{}", e),
|
||||
let dom = match dom::Dom::parse(&body) {
|
||||
Ok(dom) => dom,
|
||||
Err(e) => panic!("DOM parse error:\n{}", e),
|
||||
};
|
||||
|
||||
println!("{:#?}", text);
|
||||
|
||||
let options = lexpr::parse::Options::new()
|
||||
.with_string_syntax(lexpr::parse::StringSyntax::Elisp)
|
||||
.with_keyword_syntax(lexpr::parse::KeywordSyntax::ColonPrefix);
|
||||
let expr = lexpr::from_str_custom(&src, options).unwrap();
|
||||
|
||||
println!("{:#?}", expr);
|
||||
|
||||
let dom = parse::Parser::parse(&expr);
|
||||
let style = Stylesheet::default();
|
||||
let width = 40;
|
||||
|
||||
for tag in dom.tags.iter() {
|
||||
for line in tag.layout(&style, width) {
|
||||
println!("{}", line);
|
||||
}
|
||||
|
||||
println!("");
|
||||
}
|
||||
dom.print();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use fnv::FnvHashMap as HashMap;
|
||||
|
||||
pub type NodeId = u32;
|
||||
pub type ComponentStorage<T> = HashMap<NodeId, T>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NodeStore {
|
||||
storage: HashMap<TypeId, Box<dyn ComponentStorageTrait>>,
|
||||
}
|
||||
|
||||
impl NodeStore {
|
||||
pub fn get_storage<T: 'static>(&self) -> Option<&ComponentStorage<T>> {
|
||||
let id = TypeId::of::<T>();
|
||||
let boxed = self.storage.get(&id)?;
|
||||
boxed.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
pub fn get_storage_mut<T: 'static>(&mut self) -> Option<&mut ComponentStorage<T>> {
|
||||
let id = TypeId::of::<T>();
|
||||
let boxed = self.storage.get_mut(&id)?;
|
||||
boxed.as_any_mut().downcast_mut()
|
||||
}
|
||||
|
||||
pub fn get<T: 'static>(&self, id: NodeId) -> Option<&T> {
|
||||
let storage = self.get_storage::<T>()?;
|
||||
storage.get(&id)
|
||||
}
|
||||
|
||||
pub fn get_mut<T: 'static>(&mut self, id: NodeId) -> Option<&mut T> {
|
||||
let storage = self.get_storage_mut::<T>()?;
|
||||
storage.get_mut(&id)
|
||||
}
|
||||
|
||||
pub fn insert<T: 'static + Debug + Clone>(&mut self, id: NodeId, component: T) -> Option<T> {
|
||||
if let Some(storage) = self.get_storage_mut::<T>() {
|
||||
storage.insert(id, component)
|
||||
} else {
|
||||
let mut storage = ComponentStorage::<T>::default();
|
||||
let old = storage.insert(id, component);
|
||||
let id = TypeId::of::<T>();
|
||||
self.storage.insert(id, Box::new(storage));
|
||||
old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NodeStore {
|
||||
fn clone(&self) -> Self {
|
||||
let mut storage = HashMap::default();
|
||||
|
||||
for (id, src) in self.storage.iter() {
|
||||
storage.insert(*id, src.as_ref().clone());
|
||||
}
|
||||
|
||||
Self { storage }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComponentStorageTrait: Any + Debug {
|
||||
fn clone(&self) -> Box<dyn ComponentStorageTrait + 'static>;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + 'static> ComponentStorageTrait for ComponentStorage<T> {
|
||||
fn clone(&self) -> Box<dyn ComponentStorageTrait + 'static> {
|
||||
Box::new(Clone::clone(self))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self as _
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self as _
|
||||
}
|
||||
}
|
143
src/parse.rs
143
src/parse.rs
|
@ -1,143 +0,0 @@
|
|||
use crate::dom::Dom;
|
||||
use crate::tag::*;
|
||||
use lexpr::{Cons, Value};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub struct Parser {
|
||||
vals: Vec<Value>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn parse(document: &Value) -> Dom {
|
||||
let mut parser = Self::from_value(document);
|
||||
let (args, children) = parser.parse_args();
|
||||
|
||||
let mut tags = Vec::new();
|
||||
|
||||
for val in children.into_iter() {
|
||||
let mut parser = Self::from_value(&val);
|
||||
let kind = parser.parse_block_kind();
|
||||
let tag = parser.parse_block_tag(kind);
|
||||
tags.push(tag);
|
||||
}
|
||||
|
||||
Dom { tags }
|
||||
}
|
||||
|
||||
pub fn from_value(value: &Value) -> Self {
|
||||
let cons = value.as_cons().expect("Must be a cons");
|
||||
Self::from_cons(cons)
|
||||
}
|
||||
|
||||
pub fn from_cons(cons: &Cons) -> Self {
|
||||
let mut vals = cons.to_vec().0;
|
||||
vals.reverse();
|
||||
Self { vals }
|
||||
}
|
||||
|
||||
pub fn parse_args(&mut self) -> (Vec<(String, Value)>, Vec<Value>) {
|
||||
let mut args = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
|
||||
while let Some(next) = self.vals.pop() {
|
||||
if let Value::Keyword(arg_name) = next {
|
||||
let arg_val = self.vals.pop().expect("Expected value after keyword");
|
||||
args.push((arg_name.to_string(), arg_val));
|
||||
} else {
|
||||
children.push(next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
children.extend(self.vals.drain(..).rev());
|
||||
|
||||
(args, children)
|
||||
}
|
||||
|
||||
pub fn parse_text_tag(&mut self, kind: TextTagKind) -> TextTag {
|
||||
let (_args, children_vals) = self.parse_args();
|
||||
|
||||
let mut children = Vec::new();
|
||||
|
||||
for child in children_vals.into_iter() {
|
||||
let node = match child {
|
||||
Value::Cons(cons) => {
|
||||
let mut parser = Self::from_cons(&cons);
|
||||
let kind = parser.parse_text_kind();
|
||||
TextNode::Tag(parser.parse_text_tag(kind))
|
||||
}
|
||||
Value::String(string) => TextNode::Text(string.to_string()),
|
||||
_ => panic!("Text tag child must be a string or a cons"),
|
||||
};
|
||||
|
||||
children.push(node);
|
||||
}
|
||||
|
||||
TextTag { kind, children }
|
||||
}
|
||||
|
||||
pub fn parse_block_tag(&mut self, kind: BlockTagKind) -> BlockTag {
|
||||
let (_args, children_vals) = self.parse_args();
|
||||
let allowed_children = kind.allowed_children();
|
||||
|
||||
let mut children = Vec::new();
|
||||
for child in children_vals.into_iter() {
|
||||
let node = match child {
|
||||
Value::Cons(cons) => {
|
||||
let mut parser = Self::from_cons(&cons);
|
||||
let symbol = parser.parse_symbol();
|
||||
if let Ok(child_kind) = BlockTagKind::from_str(&symbol) {
|
||||
if allowed_children.allows_block(&child_kind) {
|
||||
BlockNode::Block(parser.parse_block_tag(child_kind))
|
||||
} else {
|
||||
panic!("{:?} tag cannot contain {:?} tag", kind, child_kind);
|
||||
}
|
||||
} else if let Ok(child_kind) = TextTagKind::from_str(&symbol) {
|
||||
if allowed_children.allows_styled_text() {
|
||||
BlockNode::Text(parser.parse_text_tag(child_kind))
|
||||
} else {
|
||||
panic!("{:?} tag cannot contain styled text", kind);
|
||||
}
|
||||
} else {
|
||||
panic!("Unrecognized tag {}", symbol);
|
||||
}
|
||||
}
|
||||
Value::String(string) => {
|
||||
if allowed_children.allows_plain_text() {
|
||||
BlockNode::Text(TextTag {
|
||||
kind: TextTagKind::Plain,
|
||||
children: vec![TextNode::Text(string.to_string())],
|
||||
})
|
||||
} else {
|
||||
panic!("{:?} tag cannot contain plain text", kind);
|
||||
}
|
||||
}
|
||||
_ => panic!("Block tag child must be either a string or a cons"),
|
||||
};
|
||||
|
||||
children.push(node);
|
||||
}
|
||||
|
||||
BlockTag { kind, children }
|
||||
}
|
||||
|
||||
pub fn parse_block_kind(&mut self) -> BlockTagKind {
|
||||
let symbol = self.parse_symbol();
|
||||
BlockTagKind::from_str(&symbol).expect("Unrecognized block tag")
|
||||
}
|
||||
|
||||
pub fn parse_text_kind(&mut self) -> TextTagKind {
|
||||
let symbol = self.parse_symbol();
|
||||
TextTagKind::from_str(&symbol).expect("Unrecognized text tag")
|
||||
}
|
||||
|
||||
pub fn parse_symbol(&mut self) -> String {
|
||||
self.vals
|
||||
.pop()
|
||||
.as_ref()
|
||||
.map(|v| v.as_symbol())
|
||||
.flatten()
|
||||
.expect("Expected symbol")
|
||||
.to_string()
|
||||
}
|
||||
}
|
75
src/text.rs
75
src/text.rs
|
@ -82,7 +82,7 @@ impl Default for TextStyle {
|
|||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn apply_tag(&mut self, kind: TextTagKind) {
|
||||
pub fn apply_tag(&mut self, kind: &TextTagKind) {
|
||||
if let Some(color) = kind.to_color() {
|
||||
self.fg = color;
|
||||
return;
|
||||
|
@ -108,12 +108,27 @@ pub struct StyledString {
|
|||
pub style: TextStyle,
|
||||
}
|
||||
|
||||
impl StyledString {
|
||||
pub fn from_plain(plain: &str) -> Self {
|
||||
Self {
|
||||
string: plain.to_string(),
|
||||
style: TextStyle::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
|
||||
pub struct StyledText {
|
||||
pub struct TaggedText {
|
||||
pub strings: Vec<StyledString>,
|
||||
}
|
||||
|
||||
impl StyledText {
|
||||
impl TaggedText {
|
||||
pub fn from_plain(plain: &str) -> Self {
|
||||
Self {
|
||||
strings: vec![StyledString::from_plain(plain)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn concat(&mut self, mut other: Self) {
|
||||
self.strings.append(&mut other.strings);
|
||||
}
|
||||
|
@ -128,7 +143,7 @@ impl StyledText {
|
|||
}),
|
||||
ast::ValueKind::List(items) => text.concat(Self::parse(style, items)?),
|
||||
ast::ValueKind::Tag(tag) => text.concat(Self::parse_tag(style, tag)?),
|
||||
_ => return Err(val.map(TextParseErrorKind::UnexpectedSymbol)),
|
||||
_ => return Err(val.map(TextParseErrorKind::UnexpectedValue)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,18 +153,41 @@ impl StyledText {
|
|||
pub fn parse_tag(style: &TextStyle, tag: &ast::Tag) -> Result<Self, TextParseError> {
|
||||
let kind = TextTagKind::from_str(&tag.id)
|
||||
.map_err(|_| tag.id.map(TextParseErrorKind::InvalidTag))?;
|
||||
Self::parse_tag_body(style, &kind, &tag.body)
|
||||
}
|
||||
|
||||
pub fn parse_tag_body(
|
||||
style: &TextStyle,
|
||||
kind: &TextTagKind,
|
||||
body: &ast::TagBody,
|
||||
) -> Result<Self, TextParseError> {
|
||||
let mut style = style.clone();
|
||||
style.apply_tag(kind);
|
||||
Self::parse(&style, &tag.body.children)
|
||||
Self::parse(&style, &body.children)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_plain(vals: &[ast::Value]) -> Result<String, TextParseError> {
|
||||
let mut text = String::new();
|
||||
for val in vals.iter() {
|
||||
match &val.inner {
|
||||
ast::ValueKind::List(vals) => text.push_str(&parse_plain(vals)?),
|
||||
ast::ValueKind::String(string) => text.push_str(&string),
|
||||
ast::ValueKind::Tag(_) => return Err(val.map(TextParseErrorKind::UnexpectedTag)),
|
||||
_ => return Err(val.map(TextParseErrorKind::UnexpectedValue)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
pub type TextParseError = WithSource<TextParseErrorKind>;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TextParseErrorKind {
|
||||
InvalidTag,
|
||||
UnexpectedSymbol,
|
||||
UnexpectedValue,
|
||||
UnexpectedTag,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TextParseErrorKind {
|
||||
|
@ -159,7 +197,28 @@ impl std::fmt::Display for TextParseErrorKind {
|
|||
use TextParseErrorKind::*;
|
||||
match self {
|
||||
InvalidTag => write!(f, "invalid tag"),
|
||||
UnexpectedSymbol => write!(f, "unexpected symbol"),
|
||||
UnexpectedValue => write!(f, "unexpected value"),
|
||||
UnexpectedTag => write!(f, "unexpected tag"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextBlock {
|
||||
pub initial_indent: Option<StyledString>,
|
||||
pub subsequent_indent: Option<StyledString>,
|
||||
pub text: TaggedText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextLayout {
|
||||
pub lines: Vec<TaggedText>,
|
||||
}
|
||||
|
||||
impl TextLayout {
|
||||
pub fn from_plain(plain: &str) -> Self {
|
||||
Self {
|
||||
lines: plain.lines().map(|s| TaggedText::from_plain(s)).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue