summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2026-09-27 15:48:03 -0400
committerMica White <botahamec@outlook.com>2026-09-27 15:48:03 -0400
commit35f6fc17d0c98f424e01904b8856cf42c80bcfd3 (patch)
treeba99ada4ac6e149c00e5d820b9c5f65e82fed980
parent8399983fd45bc0f11eac9def280c0833ffb93c04 (diff)
Add buggy diagnostics
-rw-r--r--Cargo.lock231
-rw-r--r--Cargo.toml2
-rw-r--r--src/analyze.rs17
-rw-r--r--src/compile.rs10
-rw-r--r--src/diagnostics.rs104
-rw-r--r--src/diagnostics/non_alphabetic_tag_name.rs38
-rw-r--r--src/main.rs54
-rw-r--r--src/tokenize.rs2
8 files changed, 358 insertions, 100 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2342946..c84eab9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,51 @@
version = 4
[[package]]
+name = "addr2line"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-link",
+]
+
+[[package]]
+name = "backtrace-ext"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
+
+[[package]]
name = "bpaf"
version = "0.9.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -19,7 +64,7 @@ checksum = "2f7e98cee839b19076cb3ce1afdb62bb182e04ff5f71f70188827002fae91094"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -35,12 +80,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "gimli"
+version = "0.32.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
+
+[[package]]
name = "html-escape"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c1ff2d1cbf39efe5af0900ced8a069b5e61557a17544eb0c4a50239937389e"
[[package]]
+name = "is_ci"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
+
+[[package]]
name = "kuht"
version = "0.1.0"
dependencies = [
@@ -53,14 +120,40 @@ dependencies = [
]
[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
name = "miette"
version = "7.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
dependencies = [
+ "backtrace",
+ "backtrace-ext",
"cfg-if",
"miette-derive",
- "unicode-width",
+ "owo-colors",
+ "supports-color",
+ "supports-hyperlinks",
+ "supports-unicode",
+ "terminal_size",
+ "textwrap",
+ "unicode-width 0.1.14",
]
[[package]]
@@ -71,10 +164,34 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "object"
+version = "0.37.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "owo-colors"
+version = "4.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
+
+[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -93,12 +210,52 @@ dependencies = [
]
[[package]]
+name = "rustc-demangle"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
+
+[[package]]
+name = "rustix"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
name = "snob"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39dc8789b83604bf5864a6f6d669263e1d4895a5d847047288f308997292443f"
[[package]]
+name = "supports-color"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6"
+dependencies = [
+ "is_ci",
+]
+
+[[package]]
+name = "supports-hyperlinks"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
+
+[[package]]
+name = "supports-unicode"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
+
+[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -110,23 +267,54 @@ dependencies = [
]
[[package]]
+name = "syn"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
+dependencies = [
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
+dependencies = [
+ "unicode-linebreak",
+ "unicode-width 0.2.2",
+]
+
+[[package]]
name = "thiserror"
-version = "1.0.69"
+version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.69"
+version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.6",
]
[[package]]
@@ -136,7 +324,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
+name = "unicode-linebreak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
+
+[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 573fead..b57b0e7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,6 @@ edition = "2024"
bumpalo = "3"
bpaf = { version = "0.9", features = ["derive"] }
html-escape = "0.2"
-miette = "7"
+miette = { version = "7", features = ["fancy"] }
snob = "0.1"
thiserror = "2"
diff --git a/src/analyze.rs b/src/analyze.rs
index 7debb0d..b8a7012 100644
--- a/src/analyze.rs
+++ b/src/analyze.rs
@@ -1,7 +1,4 @@
-use std::{
- collections::HashMap,
- sync::atomic::{AtomicUsize, Ordering::Relaxed},
-};
+use std::{collections::HashMap, sync::atomic::AtomicUsize};
use bumpalo::Bump;
@@ -12,10 +9,12 @@ use crate::{
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
+#[derive(Debug, Default)]
pub struct SymbolTableSet<'a> {
symbol_tables: HashMap<SymbolTableId, ScopedSymbolTable<'a>>,
}
+#[derive(Debug, Default)]
pub struct ScopedSymbolTable<'a> {
pub modules: HashMap<ModuleSymbolId, ModuleSymbol<'a>>,
pub constants: HashMap<ConstantSymbolId, ConstantSymbol<'a>>,
@@ -32,6 +31,7 @@ pub struct ConstantSymbolId(pub usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HeadSymbolId(pub usize);
+#[derive(Debug)]
pub struct ModuleSymbol<'a> {
pub id: ModuleSymbolId,
pub name: Option<&'a str>,
@@ -39,6 +39,7 @@ pub struct ModuleSymbol<'a> {
pub definition_site: Option<Element<'a>>,
}
+#[derive(Debug)]
pub struct ConstantSymbol<'a> {
pub id: ConstantSymbolId,
pub name: Option<&'a str>,
@@ -47,12 +48,14 @@ pub struct ConstantSymbol<'a> {
pub definition_site: Option<Element<'a>>,
}
+#[derive(Debug)]
pub struct HeadSymbol<'a> {
pub id: HeadSymbolId,
pub contents: Option<ConstantSymbolId>,
pub definition_site: Option<Element<'a>>,
}
+#[derive(Debug)]
pub enum ConstantType<'a> {
String,
Block {
@@ -61,10 +64,12 @@ pub enum ConstantType<'a> {
},
}
+#[derive(Debug)]
pub enum ConstantValue<'a> {
String(&'a str),
}
+#[derive(Debug)]
pub enum ConstantExpression<'a> {
Name(&'a str),
Value(ConstantValue<'a>),
@@ -77,7 +82,7 @@ pub enum ConstantExpression<'a> {
}
fn next_id() -> usize {
- NEXT_ID.fetch_add(1, Relaxed)
+ NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
fn element_attributes<'a>(
@@ -273,7 +278,7 @@ fn add_block<'a>(
ConstantSymbol {
id,
name,
- // fill this in
+ // TODO fill this in
ty: None,
value: Some(fragment),
definition_site: Some(*element),
diff --git a/src/compile.rs b/src/compile.rs
index ccbf6f0..9e19b18 100644
--- a/src/compile.rs
+++ b/src/compile.rs
@@ -8,16 +8,12 @@ fn symbol_table_from_path<'a>(
) -> Option<&'a ScopedSymbolTable<'a>> {
let mut symbol_table = symbol_table;
for component in path.split("::") {
- if let Some(module) = symbol_table
+ let module = symbol_table
.modules
.values()
.find(|module| module.name == Some(component))
- .and_then(|module| module.symbol_table)
- {
- symbol_table = module;
- } else {
- return None;
- }
+ .and_then(|module| module.symbol_table)?;
+ symbol_table = module;
}
Some(symbol_table)
diff --git a/src/diagnostics.rs b/src/diagnostics.rs
index 514818a..d2db96b 100644
--- a/src/diagnostics.rs
+++ b/src/diagnostics.rs
@@ -13,7 +13,7 @@ mod forbidden_top_level_tag;
mod html_tag;
mod image_tag;
mod non_alphabetic_attribute_name;
-mod non_alphabetic_tag_name;
+mod non_alphanumeric_tag_name;
mod non_self_closing_void_tag;
mod non_void_self_closing_tag;
mod self_closing_end_tag;
@@ -27,7 +27,7 @@ mod unterminated_tag;
const VOID_TAGS: &[&str] = &[
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source",
- "track", "wbr",
+ "track", "wbr", "import",
];
const ALLOWED_TOP_LEVEL_TAGS: &[&str] = &["import", "const", "block", "head", "body"];
@@ -35,6 +35,12 @@ pub struct DiagnosticsContext {
source_code: &'static str,
}
+impl DiagnosticsContext {
+ pub fn new(source_code: &'static str) -> Self {
+ Self { source_code }
+ }
+}
+
trait TokenDiagnostic: Sized + Send + Sync + 'static {
fn handle_token(_: &Token<'_>) -> Option<Self> {
None
@@ -66,13 +72,16 @@ trait AstDiagnostic: Sized + Send + Sync + 'static {
fn maybe_print_diagnostic<D: Diagnostic + Send + Sync + 'static>(
ctx: &DiagnosticsContext,
diagnostic: Option<D>,
-) {
+) -> Option<()> {
if let Some(diagnostic) = diagnostic {
eprintln!(
- "{}",
+ "{:?}",
Error::new(diagnostic).with_source_code(ctx.source_code)
);
+ return None;
}
+
+ Some(())
}
#[derive(Debug, Error, Diagnostic)]
@@ -119,9 +128,10 @@ pub fn run_character_diagnostics(source: &'static [u8]) -> Option<&'static str>
fn run_token_diagnostic<D: Diagnostic + TokenDiagnostic>(
ctx: &DiagnosticsContext,
tokens: &[Token<'_>],
-) {
+) -> Option<()> {
+ let mut has_failed = false;
for token in tokens {
- maybe_print_diagnostic(ctx, D::handle_token(token));
+ has_failed |= maybe_print_diagnostic(ctx, D::handle_token(token)).is_none();
let TokenType::Tag {
attribute_tokens, ..
@@ -131,53 +141,82 @@ fn run_token_diagnostic<D: Diagnostic + TokenDiagnostic>(
};
for token in attribute_tokens {
- maybe_print_diagnostic(ctx, D::handle_attribute_token(token));
+ has_failed |= maybe_print_diagnostic(ctx, D::handle_attribute_token(token)).is_none();
}
}
+
+ (!has_failed).then_some(())
}
fn run_ast_diagnostic<D: AstDiagnostic + Diagnostic>(
ctx: &DiagnosticsContext,
document: &Document<'_>,
-) {
- maybe_print_diagnostic(ctx, D::handle_document(document));
+) -> Option<()> {
+ let mut has_failed = maybe_print_diagnostic(ctx, D::handle_document(document)).is_none();
for node in document.children {
- maybe_print_diagnostic(ctx, D::handle_node(node));
+ has_failed |= maybe_print_diagnostic(ctx, D::handle_node(node)).is_none();
let Node::Element(element) = node else {
continue;
};
- maybe_print_diagnostic(ctx, D::handle_element(element));
+ has_failed |= maybe_print_diagnostic(ctx, D::handle_element(element)).is_none();
for attribute in element.attributes {
- maybe_print_diagnostic(ctx, D::handle_attribute(attribute));
+ has_failed |= maybe_print_diagnostic(ctx, D::handle_attribute(attribute)).is_none();
}
}
+
+ (!has_failed).then_some(())
}
-pub fn run_token_diagnostics(ctx: &DiagnosticsContext, tokens: &[Token<'_>]) {
- run_token_diagnostic::<non_void_self_closing_tag::NonVoidSelfClosingTag>(ctx, tokens);
- run_token_diagnostic::<non_self_closing_void_tag::NonSelfClosingVoidTag>(ctx, tokens);
- run_token_diagnostic::<end_tag_with_attributes::EndTagWithAttributes>(ctx, tokens);
- run_token_diagnostic::<self_closing_end_tag::SelfClosingEndTag>(ctx, tokens);
- run_token_diagnostic::<non_alphabetic_tag_name::NonAlphabeticTagName>(ctx, tokens);
- run_token_diagnostic::<empty_tag_name::EmptyTagName>(ctx, tokens);
- run_token_diagnostic::<unterminated_tag::UnterminatedTag>(ctx, tokens);
- run_token_diagnostic::<unterminated_comment::UnterminatedComment>(ctx, tokens);
- run_token_diagnostic::<non_alphabetic_attribute_name::NonAlphabeticAttributeName>(ctx, tokens);
+pub fn run_token_diagnostics(ctx: &DiagnosticsContext, tokens: &[Token<'_>]) -> Option<()> {
+ let mut has_failed = false;
+ has_failed |=
+ run_token_diagnostic::<non_void_self_closing_tag::NonVoidSelfClosingTag>(ctx, tokens)
+ .is_none();
+ has_failed |=
+ run_token_diagnostic::<non_self_closing_void_tag::NonSelfClosingVoidTag>(ctx, tokens)
+ .is_none();
+ has_failed |=
+ run_token_diagnostic::<end_tag_with_attributes::EndTagWithAttributes>(ctx, tokens)
+ .is_none();
+ has_failed |=
+ run_token_diagnostic::<self_closing_end_tag::SelfClosingEndTag>(ctx, tokens).is_none();
+ has_failed |=
+ run_token_diagnostic::<non_alphanumeric_tag_name::NonAlphaNumericTagName>(ctx, tokens)
+ .is_none();
+ has_failed |= run_token_diagnostic::<empty_tag_name::EmptyTagName>(ctx, tokens).is_none();
+ has_failed |= run_token_diagnostic::<unterminated_tag::UnterminatedTag>(ctx, tokens).is_none();
+ has_failed |=
+ run_token_diagnostic::<unterminated_comment::UnterminatedComment>(ctx, tokens).is_none();
+ has_failed |=
+ run_token_diagnostic::<non_alphabetic_attribute_name::NonAlphabeticAttributeName>(
+ ctx, tokens,
+ )
+ .is_none();
// TODO invalid character reference
+ (!has_failed).then_some(())
}
-pub fn run_ast_diagnostics(ctx: &DiagnosticsContext, document: &Document<'_>) {
- run_ast_diagnostic::<stray_equal_sign::StrayEqualSign>(ctx, document);
- run_ast_diagnostic::<too_many_equal_signs::TooManyEqualSigns>(ctx, document);
- run_ast_diagnostic::<duplicate_attribute_names::DuplicateAttributeNames>(ctx, document);
- run_ast_diagnostic::<top_level_character_data::TopLevelCharacterData>(ctx, document);
- run_ast_diagnostic::<forbidden_top_level_tag::ForbiddenTopLevelTag>(ctx, document);
- run_ast_diagnostic::<stray_end_tag::StrayEndTag>(ctx, document);
- run_ast_diagnostic::<unclosed_start_tag::UnclosedStartTag>(ctx, document);
- run_ast_diagnostic::<html_tag::HtmlTag>(ctx, document);
- run_ast_diagnostic::<image_tag::ImageTag>(ctx, document);
+pub fn run_ast_diagnostics(ctx: &DiagnosticsContext, document: &Document<'_>) -> Option<()> {
+ let mut has_failed = false;
+ has_failed |= run_ast_diagnostic::<stray_equal_sign::StrayEqualSign>(ctx, document).is_none();
+ has_failed |=
+ run_ast_diagnostic::<too_many_equal_signs::TooManyEqualSigns>(ctx, document).is_none();
+ has_failed |=
+ run_ast_diagnostic::<duplicate_attribute_names::DuplicateAttributeNames>(ctx, document)
+ .is_none();
+ has_failed |=
+ run_ast_diagnostic::<top_level_character_data::TopLevelCharacterData>(ctx, document)
+ .is_none();
+ has_failed |=
+ run_ast_diagnostic::<forbidden_top_level_tag::ForbiddenTopLevelTag>(ctx, document)
+ .is_none();
+ has_failed |= run_ast_diagnostic::<stray_end_tag::StrayEndTag>(ctx, document).is_none();
+ has_failed |=
+ run_ast_diagnostic::<unclosed_start_tag::UnclosedStartTag>(ctx, document).is_none();
+ has_failed |= run_ast_diagnostic::<html_tag::HtmlTag>(ctx, document).is_none();
+ has_failed |= run_ast_diagnostic::<image_tag::ImageTag>(ctx, document).is_none();
// TODO invalid <kuht-var />
// TODO invalid <import />
// TODO invalid <const />
@@ -214,4 +253,5 @@ pub fn run_ast_diagnostics(ctx: &DiagnosticsContext, document: &Document<'_>) {
// TODO aria tags don't match tag semantics
// TODO empty or missing image src
// TODO missing alt text for image (unless inside figure with caption, or has title, or meta name = "generator")
+ (!has_failed).then_some(())
}
diff --git a/src/diagnostics/non_alphabetic_tag_name.rs b/src/diagnostics/non_alphabetic_tag_name.rs
deleted file mode 100644
index b36cb75..0000000
--- a/src/diagnostics/non_alphabetic_tag_name.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use miette::Diagnostic;
-use thiserror::Error;
-
-use crate::{
- diagnostics::TokenDiagnostic,
- tokenize::{Span, Token, TokenType},
-};
-
-#[derive(Error, Debug, Diagnostic)]
-#[error("Tag names must be alphabetic")]
-#[diagnostic(code(NonAlphabeticTagName), severity(Error))]
-pub struct NonAlphabeticTagName {
- #[label(primary, "tag names must only contain uppercase and lowercase letters")]
- tag: Span,
-}
-
-impl TokenDiagnostic for NonAlphabeticTagName {
- fn handle_token(token: &Token<'_>) -> Option<Self> {
- let TokenType::Tag {
- is_end,
- lowercase_tag_name,
- ..
- } = token.ty
- else {
- return None;
- };
-
- if is_end
- || lowercase_tag_name
- .chars()
- .all(|char| char.is_ascii_alphabetic())
- {
- return None;
- }
-
- Some(Self { tag: token.span })
- }
-}
diff --git a/src/main.rs b/src/main.rs
index 1ac5a54..dcfd81b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,13 @@ use std::collections::HashMap;
use bpaf::{Bpaf, Parser};
use bumpalo::Bump;
-use kuht::{analyze::symbol_table, compile::compile, parse::parse_document, tokenize::tokenize};
+use kuht::{
+ analyze::symbol_table,
+ compile::compile,
+ diagnostics::{self, DiagnosticsContext},
+ parse::parse_document,
+ tokenize::tokenize,
+};
#[derive(Debug, Clone, Bpaf)]
struct Options {
@@ -21,39 +27,73 @@ struct Options {
fn main() {
let options = options().run();
+ let mut has_failed = false;
+
let sources = options
.files
.into_iter()
.map(|filename| {
- let source = match std::fs::read_to_string(&filename) {
+ let source = match std::fs::read(&filename) {
Ok(source) => source,
Err(error) => {
- eprintln!("{error}");
+ eprintln!("Error: {error}");
std::process::exit(1);
}
};
(filename, source)
})
+ .filter_map(|(filename, source)| {
+ let source: &'static [u8] = Vec::leak(source);
+ let Some(source) = diagnostics::run_character_diagnostics(source) else {
+ has_failed = true;
+ return None;
+ };
+ Some((filename, source))
+ })
.collect::<HashMap<_, _>>();
let arena = Bump::new();
let tokens = sources
.into_iter()
- .map(|(filename, source)| (filename, tokenize(&source, &arena)))
+ .map(|(filename, source)| {
+ (
+ filename,
+ (DiagnosticsContext::new(source), tokenize(source, &arena)),
+ )
+ })
+ .filter(|(_, (ctx, tokens))| {
+ let failed = diagnostics::run_token_diagnostics(ctx, tokens).is_none();
+ has_failed |= failed;
+ failed
+ })
.collect::<HashMap<_, _>>();
let asts = tokens
.into_iter()
- .map(|(filename, tokens)| (filename, parse_document(tokens, &arena)))
+ .map(|(filename, (ctx, tokens))| (filename, (ctx, parse_document(tokens, &arena))))
+ .filter(|(_, (ctx, document))| {
+ let failed = diagnostics::run_ast_diagnostics(ctx, document).is_none();
+ has_failed |= failed;
+ failed
+ })
.collect::<HashMap<_, _>>();
+ if has_failed {
+ std::process::exit(1);
+ }
+
let symbol_tables = asts
.into_iter()
- .map(|(filename, document)| (filename, symbol_table(&arena, document)))
+ .map(|(filename, (ctx, document))| (filename, (ctx, symbol_table(&arena, document))))
.collect::<HashMap<_, _>>();
if !options.dry {
- let output = compile(&symbol_tables.into_values().collect::<Box<_>>());
+ let output = compile(
+ &symbol_tables
+ .into_values()
+ .map(|(_, document)| document)
+ .collect::<Box<_>>(),
+ );
if let Err(e) = std::fs::write(options.out, output.as_bytes()) {
eprintln!("{e}");
std::process::exit(1);
diff --git a/src/tokenize.rs b/src/tokenize.rs
index 43ad305..a8e83c8 100644
--- a/src/tokenize.rs
+++ b/src/tokenize.rs
@@ -52,7 +52,7 @@ pub enum AttributeTokenType<'a> {
impl From<Span> for miette::SourceSpan {
fn from(value: Span) -> Self {
- miette::SourceSpan::from((value.start, value.start - value.end))
+ miette::SourceSpan::from((value.start, value.end - value.start))
}
}