diff --git a/Cargo.lock b/Cargo.lock index 5374a563..9376a35b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,11 +93,6 @@ dependencies = [ "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "bytecount" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "byteorder" version = "1.3.2" @@ -266,18 +261,6 @@ name = "lazy_static" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "lexical-core" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "stackvector 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "libc" version = "0.2.61" @@ -328,16 +311,6 @@ name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "nom" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lexical-core 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-integer" version = "0.1.39" @@ -346,15 +319,6 @@ dependencies = [ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "num-rational" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-traits" version = "0.2.6" @@ -589,19 +553,6 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rsass" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytecount 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "rust-argon2" version = "0.5.1" @@ -630,11 +581,6 @@ name = "ryu" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "ryu" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "scopeguard" version = "0.3.3" @@ -697,20 +643,6 @@ name = "spin" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "stackvector" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "static_assertions" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "strsim" version = "0.7.0" @@ -825,7 +757,6 @@ dependencies = [ "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rsass 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", @@ -964,7 +895,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2b_simd 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "461f4b879a8eb70c1debf7d0788a9a5ff15f1ea9d25925fea264ef4258bed6b2" -"checksum bytecount 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be0fdd54b507df8f22012890aadd099979befdba27713c767993f8380112ca7c" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" @@ -987,7 +917,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" -"checksum lexical-core 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b0f90c979adde96d19eb10eb6431ba0c441e2f9e9bdff868b2f6f5114ff519" "checksum libc 0.2.61 (registry+https://github.com/rust-lang/crates.io-index)" = "c665266eb592905e8503ba3403020f4b8794d26263f412ca33171600eca9a6fa" "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" @@ -995,9 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0a3eb002f0535929f1199681417029ebea04aadc0c7a4224b46be99c7f5d6a16" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" -"checksum nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9761d859320e381010a4f7f8ed425f2c924de33ad121ace447367c713ad561b" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" -"checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum once_cell 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "532c29a261168a45ce28948f9537ddd7a5dd272cc513b3017b1e82a88f962c37" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" @@ -1025,12 +952,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f" "checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" -"checksum rsass 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4520dc8a2786c0319f3947e3d79e735b27f0c63c555b854aaa802e49e3f45098" "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" -"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" @@ -1040,8 +965,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1764fe2b30ee783bfe3b9b37b2649d8d590b3148bb12e0079715d4d5c673562e" "checksum smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "88aea073965ab29f6edb5493faf96ad662fb18aa9eeb186a3b7057951605ed15" "checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" -"checksum stackvector 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1c4725650978235083241fab0fdc8e694c3de37821524e7534a1a9061d1068af" -"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index fac9d61a..8dd7cb18 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -32,7 +32,6 @@ serde = "1.0" serde_derive = "1.0" regex-syntax = "0.6.4" regex = "1" -rsass = "^0.11.0" tiny_http = "0.6" webbrowser = "0.5.1" diff --git a/cli/src/error.rs b/cli/src/error.rs index 324ad8b1..fab87478 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -74,12 +74,6 @@ impl From for Error { } } -impl From for Error { - fn from(error: rsass::Error) -> Self { - Error::new(error.to_string()) - } -} - impl From for Error { fn from(error: regex_syntax::ast::Error) -> Self { Error::new(error.to_string()) diff --git a/cli/src/generate/mod.rs b/cli/src/generate/mod.rs index be293047..0644e2cd 100644 --- a/cli/src/generate/mod.rs +++ b/cli/src/generate/mod.rs @@ -6,13 +6,12 @@ mod node_types; mod npm_files; pub mod parse_grammar; mod prepare_grammar; -pub mod properties; mod render; mod rules; mod tables; use self::build_tables::build_tables; -use self::grammars::{InlinedProductionMap, LexicalGrammar, SyntaxGrammar, VariableType}; +use self::grammars::{InlinedProductionMap, LexicalGrammar, SyntaxGrammar}; use self::parse_grammar::parse_grammar; use self::prepare_grammar::prepare_grammar; use self::render::render_c_code; @@ -20,9 +19,8 @@ use self::rules::AliasMap; use crate::error::{Error, Result}; use lazy_static::lazy_static; use regex::{Regex, RegexBuilder}; -use std::collections::HashSet; -use std::fs::{self, File}; -use std::io::{BufWriter, Write}; +use std::fs; +use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -51,13 +49,11 @@ struct GeneratedParser { pub fn generate_parser_in_directory( repo_path: &PathBuf, grammar_path: Option<&str>, - properties_only: bool, next_abi: bool, report_symbol_name: Option<&str>, ) -> Result<()> { let src_path = repo_path.join("src"); let header_path = src_path.join("tree_sitter"); - let properties_dir_path = repo_path.join("properties"); // Ensure that the output directories exist. fs::create_dir_all(&src_path)?; @@ -82,71 +78,48 @@ pub fn generate_parser_in_directory( prepare_grammar(&input_grammar)?; let language_name = input_grammar.name; - // If run with no arguments, read all of the property sheets and compile them to JSON. - if grammar_path.is_none() { - let token_names = get_token_names(&syntax_grammar, &lexical_grammar); - if let Ok(entries) = fs::read_dir(properties_dir_path) { - for entry in entries { - let css_path = entry?.path(); - let css = fs::read_to_string(&css_path)?; - let sheet = properties::generate_property_sheet(&css_path, &css, &token_names)?; - let property_sheet_json_path = src_path - .join(css_path.file_name().unwrap()) - .with_extension("json"); - let property_sheet_json_file = - File::create(&property_sheet_json_path).map_err(Error::wrap(|| { - format!("Failed to create {:?}", property_sheet_json_path) - }))?; - let mut writer = BufWriter::new(property_sheet_json_file); - serde_json::to_writer_pretty(&mut writer, &sheet)?; - } - } - } - // Generate the parser and related files. - if !properties_only { - let GeneratedParser { - c_code, - node_types_json, - } = generate_parser_for_grammar_with_opts( - &language_name, - syntax_grammar, - lexical_grammar, - inlines, - simple_aliases, - next_abi, - report_symbol_name, - )?; + let GeneratedParser { + c_code, + node_types_json, + } = generate_parser_for_grammar_with_opts( + &language_name, + syntax_grammar, + lexical_grammar, + inlines, + simple_aliases, + next_abi, + report_symbol_name, + )?; - write_file(&src_path.join("parser.c"), c_code)?; - write_file(&src_path.join("node-types.json"), node_types_json)?; + write_file(&src_path.join("parser.c"), c_code)?; + write_file(&src_path.join("node-types.json"), node_types_json)?; - if next_abi { - write_file(&header_path.join("parser.h"), tree_sitter::PARSER_HEADER)?; - } else { - let mut header = tree_sitter::PARSER_HEADER.to_string(); + if next_abi { + write_file(&header_path.join("parser.h"), tree_sitter::PARSER_HEADER)?; + } else { + let mut header = tree_sitter::PARSER_HEADER.to_string(); - for part in &NEW_HEADER_PARTS { - let pos = header - .find(part) - .expect("Missing expected part of parser.h header"); - header.replace_range(pos..(pos + part.len()), ""); - } - - write_file(&header_path.join("parser.h"), header)?; + for part in &NEW_HEADER_PARTS { + let pos = header + .find(part) + .expect("Missing expected part of parser.h header"); + header.replace_range(pos..(pos + part.len()), ""); } - ensure_file(&repo_path.join("index.js"), || { - npm_files::index_js(&language_name) - })?; - ensure_file(&src_path.join("binding.cc"), || { - npm_files::binding_cc(&language_name) - })?; - ensure_file(&repo_path.join("binding.gyp"), || { - npm_files::binding_gyp(&language_name) - })?; + write_file(&header_path.join("parser.h"), header)?; } + ensure_file(&repo_path.join("index.js"), || { + npm_files::index_js(&language_name) + })?; + ensure_file(&src_path.join("binding.cc"), || { + npm_files::binding_cc(&language_name) + })?; + ensure_file(&repo_path.join("binding.gyp"), || { + npm_files::binding_gyp(&language_name) + })?; + Ok(()) } @@ -208,35 +181,6 @@ fn generate_parser_for_grammar_with_opts( }) } -fn get_token_names( - syntax_grammar: &SyntaxGrammar, - lexical_grammar: &LexicalGrammar, -) -> HashSet { - let mut result = HashSet::new(); - for variable in &lexical_grammar.variables { - if variable.kind == VariableType::Named { - result.insert(variable.name.clone()); - } - } - for token in &syntax_grammar.external_tokens { - if token.kind == VariableType::Named { - result.insert(token.name.clone()); - } - } - for variable in &syntax_grammar.variables { - for production in &variable.productions { - for step in &production.steps { - if let Some(alias) = &step.alias { - if !step.symbol.is_non_terminal() && alias.is_named { - result.insert(alias.value.clone()); - } - } - } - } - } - result -} - fn load_grammar_file(grammar_path: &Path) -> Result { match grammar_path.extension().and_then(|e| e.to_str()) { Some("js") => Ok(load_js_grammar_file(grammar_path)?), diff --git a/cli/src/generate/properties.rs b/cli/src/generate/properties.rs deleted file mode 100644 index 5091eafc..00000000 --- a/cli/src/generate/properties.rs +++ /dev/null @@ -1,1499 +0,0 @@ -use crate::error::{Error, Result}; -use crate::generate::dedup::split_state_id_groups; -use rsass; -use rsass::sass::Value; -use rsass::selectors::SelectorPart; -use serde_derive::Serialize; -use std::cmp::Ordering; -use std::collections::hash_map::Entry; -use std::collections::{btree_map, BTreeMap, HashMap, HashSet, VecDeque}; -use std::fmt::{self, Write}; -use std::hash::{Hash, Hasher}; -use std::mem; -use std::path::{Path, PathBuf}; -use tree_sitter::{self, PropertyStateJSON, PropertyTransitionJSON}; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] -#[serde(untagged)] -pub(crate) enum PropertyValue { - Number(isize), - Boolean(bool), - String(String), - Object(PropertySet), - Array(Vec), -} - -type PropertySet = BTreeMap; -type PropertySheetJSON = tree_sitter::PropertySheetJSON; -type StateId = usize; -type PropertySetId = usize; - -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct SelectorStep { - kind: Option, - field: Option, - child_index: Option, - text_pattern: Option, - is_named: Option, - is_immediate: bool, -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct Selector(Vec); - -#[derive(Debug, PartialEq, Eq)] -struct Rule { - selectors: Vec, - properties: PropertySet, -} - -#[derive(Clone, Copy, Debug)] -struct Item<'a> { - rule_id: u32, - selector: &'a Selector, - step_id: u32, -} - -#[derive(Clone, PartialEq, Eq)] -struct ItemSet<'a>(Vec>); - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -struct SelectorMatch { - specificity: u32, - rule_id: u32, -} - -struct Builder<'a> { - rules: &'a Vec, - output: PropertySheetJSON, - start_item_set: ItemSet<'a>, - token_names: &'a HashSet, - ids_by_item_set: HashMap, StateId>, - item_set_queue: VecDeque<(ItemSet<'a>, StateId)>, - item_set_list: Vec>, -} - -impl<'a> Item<'a> { - fn next_step(&self) -> Option<&SelectorStep> { - self.selector.0.get(self.step_id as usize) - } - - fn is_done(&self) -> bool { - self.step_id as usize == self.selector.0.len() - } -} - -impl<'a> Ord for Item<'a> { - fn cmp(&self, other: &Item) -> Ordering { - self.rule_id - .cmp(&other.rule_id) - .then_with(|| self.selector.0.len().cmp(&other.selector.0.len())) - .then_with(|| { - for (i, step) in self - .selector - .0 - .iter() - .enumerate() - .skip(self.step_id as usize) - { - let result = step.cmp(&other.selector.0[i]); - if result != Ordering::Equal { - return result; - } - } - Ordering::Equal - }) - } -} - -impl<'a> PartialOrd for Item<'a> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl<'a> Hash for Item<'a> { - fn hash(&self, hasher: &mut H) { - hasher.write_u32(self.rule_id); - hasher.write_usize(self.selector.0.len()); - hasher.write_u32(self.step_id); - for step in &self.selector.0[self.step_id as usize..] { - step.hash(hasher); - } - } -} - -impl<'a> PartialEq for Item<'a> { - fn eq(&self, other: &Self) -> bool { - if self.rule_id != other.rule_id - || self.selector.0.len() != other.selector.0.len() - || self.step_id != other.step_id - { - return false; - } - - for (i, step) in self - .selector - .0 - .iter() - .enumerate() - .skip(self.step_id as usize) - { - if *step != other.selector.0[i] { - return false; - } - } - - true - } -} - -impl<'a> Eq for Item<'a> {} - -impl<'a> Hash for ItemSet<'a> { - fn hash(&self, hasher: &mut H) { - hasher.write_usize(self.0.len()); - for item in &self.0 { - item.hash(hasher); - } - } -} - -impl<'a> ItemSet<'a> { - fn new() -> Self { - ItemSet(Vec::new()) - } - - fn insert(&mut self, item: Item<'a>) { - match self.0.binary_search(&item) { - Err(i) => self.0.insert(i, item), - _ => {} - } - } -} - -impl<'a> Builder<'a> { - fn new(rules: &'a Vec, token_names: &'a HashSet) -> Self { - Builder { - rules, - start_item_set: ItemSet::new(), - item_set_list: Vec::new(), - output: PropertySheetJSON { - states: Vec::new(), - property_sets: Vec::new(), - }, - token_names, - ids_by_item_set: HashMap::new(), - item_set_queue: VecDeque::new(), - } - } - - fn build(mut self) -> PropertySheetJSON { - for (i, rule) in self.rules.iter().enumerate() { - for selector in &rule.selectors { - self.start_item_set.insert(Item { - rule_id: i as u32, - selector, - step_id: 0, - }); - } - } - - self.add_state(ItemSet::new()); - self.output.states[0].id = Some(0); - while let Some((item_set, state_id)) = self.item_set_queue.pop_front() { - self.populate_state(item_set, state_id); - } - - self.remove_duplicate_states(); - - for (i, state) in self.output.states.iter_mut().enumerate() { - state.id = Some(i); - } - - self.output - } - - fn add_state(&mut self, item_set: ItemSet<'a>) -> StateId { - match self.ids_by_item_set.entry(item_set) { - Entry::Occupied(o) => *o.get(), - Entry::Vacant(v) => { - let state_id = self.output.states.len(); - self.output.states.push(PropertyStateJSON { - id: None, - transitions: Vec::new(), - property_set_id: 0, - default_next_state_id: 0, - }); - self.item_set_queue.push_back((v.key().clone(), state_id)); - v.insert(state_id); - state_id - } - } - } - - fn add_property_set(&mut self, properties: PropertySet) -> PropertySetId { - if let Some(index) = self - .output - .property_sets - .iter() - .position(|i| *i == properties) - { - index - } else { - self.output.property_sets.push(properties); - self.output.property_sets.len() - 1 - } - } - - fn populate_state(&mut self, item_set: ItemSet<'a>, state_id: StateId) { - let is_start_state = state_id == 0; - let mut transitions: HashMap = HashMap::new(); - let mut selector_matches = Vec::new(); - - // First, compute all of the possible state transition conditions for - // this state, and all of the rules that are currently matching. - for item in item_set.0.iter().chain(self.start_item_set.0.iter()) { - // If this item has more elements remaining in its selector, then - // add a state transition based on the next step. - if let Some(step) = item.next_step() { - transitions - .entry(PropertyTransitionJSON { - kind: step.kind.clone(), - field: step.field.clone(), - named: step.is_named, - index: step.child_index, - text: step.text_pattern.clone(), - state_id: 0, - }) - .and_modify(|rule_id| { - if item.rule_id > *rule_id { - *rule_id = item.rule_id; - } - }) - .or_insert(item.rule_id); - } - // If the item has matched its entire selector, then the item's - // properties are applicable to this state. - else { - selector_matches.push(SelectorMatch { - rule_id: item.rule_id, - specificity: selector_specificity(item.selector), - }); - } - } - - // Compute the merged properties that apply in the current state. - // Sort the matching property sets by ascending specificity and by - // their order in the sheet. This way, more specific selectors and later - // rules will override less specific selectors and earlier rules. - let mut properties = PropertySet::new(); - selector_matches.sort_unstable_by(|a, b| { - (a.specificity.cmp(&b.specificity)).then_with(|| a.rule_id.cmp(&b.rule_id)) - }); - selector_matches.dedup(); - for selector_match in selector_matches { - let rule = &self.rules[selector_match.rule_id as usize]; - for (property, value) in &rule.properties { - properties.insert(property.clone(), value.clone()); - } - } - self.output.states[state_id].property_set_id = self.add_property_set(properties); - - // If there are multiple transitions that could *both* match (e.g. one based on a - // a node type and one based on a field name), then create an additional transition - // for the intersection of the two. - let mut i = 0; - let mut transition_list = transitions.into_iter().collect::>(); - while i < transition_list.len() { - for j in 0..i { - if let Some(intersection) = - self.intersect_transitions(&transition_list[j].0, &transition_list[i].0) - { - transition_list.push(( - intersection, - u32::max(transition_list[i].1, transition_list[j].1), - )); - } - } - i += 1; - } - - // Ensure that for a given node type, more specific transitions are tried - // first, and in the event of a tie, transitions corresponding to later rules - // in the cascade are tried first. Also, sort the non-intersecting transitions - // by name to guarantee a deterministic order. - transition_list.sort_by(|a, b| { - (transition_specificity(&b.0).cmp(&transition_specificity(&a.0))) - .then_with(|| b.1.cmp(&a.1)) - .then_with(|| a.0.kind.cmp(&b.0.kind)) - .then_with(|| a.0.named.cmp(&b.0.named)) - .then_with(|| a.0.field.cmp(&b.0.field)) - }); - - // For eacy possible state transition, compute the set of items in that transition's - // destination state. - i = 0; - while i < transition_list.len() { - let transition = &mut transition_list[i].0; - let transition_is_leaf = transition.named == Some(false) - || transition - .kind - .as_ref() - .map_or(false, |kind| self.token_names.contains(kind)); - - let mut next_item_set = ItemSet::new(); - let mut transition_differs_from_start_state = false; - for item in item_set.0.iter().chain(self.start_item_set.0.iter()) { - if let Some(next_step) = item.next_step() { - // If the next step of the item's selector satisfies this transition, - // advance the item to the next part of its selector and add the - // resulting item to this transition's destination state. - if step_matches_transition(next_step, transition) { - let next_item = Item { - rule_id: item.rule_id, - selector: item.selector, - step_id: item.step_id + 1, - }; - if !transition_is_leaf || next_item.is_done() { - next_item_set.insert(next_item); - if item.step_id > 0 { - transition_differs_from_start_state = true; - } - } - } - - // If the next step of the item is not an immediate child, then - // include this item in this transition's destination state, because - // the next step of the item might match a descendant node. - if !transition_is_leaf && !next_step.is_immediate && item.step_id > 0 { - next_item_set.insert(*item); - transition_differs_from_start_state = true; - } - } - } - - if (is_start_state || transition_differs_from_start_state) - && !next_item_set.0.is_empty() - { - transition.state_id = self.add_state(next_item_set); - if is_start_state || !self.output.states[0].transitions.contains(&transition) { - i += 1; - continue; - } - } - transition_list.remove(i); - } - - self.output.states[state_id] - .transitions - .extend(transition_list.into_iter().map(|i| i.0)); - - // Compute the default successor item set - the item set that - // we should advance to if the next element doesn't match any - // of the next elements in the item set's selectors. - let mut default_next_item_set = ItemSet::new(); - for item in &item_set.0 { - let next_step = item.selector.0.get(item.step_id as usize); - if let Some(step) = next_step { - if !step.is_immediate { - default_next_item_set.insert(*item); - } - } - } - self.output.states[state_id].default_next_state_id = self.add_state(default_next_item_set); - - self.item_set_list.push(item_set); - } - - fn intersect_transitions( - &self, - left: &PropertyTransitionJSON, - right: &PropertyTransitionJSON, - ) -> Option { - let mut left_contributes = false; - let mut right_contributes = false; - let mut result = left.clone(); - - if let Some(left_kind) = &left.kind { - if let Some(right_kind) = &right.kind { - if left_kind != right_kind || left.named != right.named { - return None; - } - } else { - left_contributes = true; - } - } else if let Some(right_kind) = &right.kind { - result.kind = Some(right_kind.clone()); - result.named = right.named; - right_contributes = true; - } - - if let Some(left_field) = &left.field { - if let Some(right_field) = &right.field { - if left_field != right_field { - return None; - } - } else { - left_contributes = true; - } - } else if let Some(right_field) = &right.field { - result.field = Some(right_field.clone()); - right_contributes = true; - } - - if let Some(left_text) = &left.text { - if let Some(right_text) = &right.text { - if left_text != right_text { - return None; - } - } else { - left_contributes = true; - } - } else if let Some(right_text) = &right.text { - result.text = Some(right_text.clone()); - right_contributes = true; - } - - if let Some(left_index) = &left.index { - if let Some(right_index) = &right.index { - if left_index != right_index { - return None; - } - } else { - left_contributes = true; - } - } else if let Some(right_index) = &right.index { - result.index = Some(right_index.clone()); - right_contributes = true; - } - - if left_contributes && right_contributes { - Some(result) - } else { - None - } - } - - fn remove_duplicate_states(&mut self) { - let mut state_ids_by_properties = HashMap::new(); - for (i, state) in self.output.states.iter().enumerate() { - state_ids_by_properties - .entry(state.property_set_id) - .or_insert(Vec::new()) - .push(i); - } - let mut state_ids_by_group_id = state_ids_by_properties - .into_iter() - .map(|e| e.1) - .collect::>(); - state_ids_by_group_id.sort(); - let start_group_index = state_ids_by_group_id - .iter() - .position(|g| g.contains(&0)) - .unwrap(); - state_ids_by_group_id.swap(start_group_index, 0); - - let mut group_ids_by_state_id = vec![0; self.output.states.len()]; - for (group_id, state_ids) in state_ids_by_group_id.iter().enumerate() { - for state_id in state_ids { - group_ids_by_state_id[*state_id] = group_id; - } - } - - while split_state_id_groups( - &self.output.states, - &mut state_ids_by_group_id, - &mut group_ids_by_state_id, - 0, - property_states_differ, - ) { - continue; - } - - let mut new_states = Vec::with_capacity(state_ids_by_group_id.len()); - for state_ids in state_ids_by_group_id.iter() { - let mut new_state = PropertyStateJSON::default(); - mem::swap(&mut new_state, &mut self.output.states[state_ids[0]]); - for transition in new_state.transitions.iter_mut() { - transition.state_id = group_ids_by_state_id[transition.state_id]; - } - new_state.default_next_state_id = - group_ids_by_state_id[new_state.default_next_state_id]; - new_states.push(new_state); - } - self.output.states = new_states; - } -} - -fn property_states_differ( - left: &PropertyStateJSON, - right: &PropertyStateJSON, - group_ids_by_state_id: &Vec, -) -> bool { - if group_ids_by_state_id[left.default_next_state_id] - != group_ids_by_state_id[right.default_next_state_id] - { - return true; - } - - left.transitions - .iter() - .zip(right.transitions.iter()) - .any(|(left, right)| { - left.kind != right.kind - || left.named != right.named - || left.index != right.index - || left.field != right.field - || left.text != right.text - || group_ids_by_state_id[left.state_id] != group_ids_by_state_id[right.state_id] - }) -} - -fn selector_specificity(selector: &Selector) -> u32 { - let mut result = 0; - for step in &selector.0 { - if step.kind.is_some() { - result += 1; - } - if step.field.is_some() { - result += 1; - } - if step.child_index.is_some() { - result += 1; - } - if step.text_pattern.is_some() { - result += 1; - } - } - result -} - -fn transition_specificity(transition: &PropertyTransitionJSON) -> u32 { - let mut result = 0; - if transition.kind.is_some() { - result += 1; - } - if transition.field.is_some() { - result += 1; - } - if transition.index.is_some() { - result += 1; - } - if transition.text.is_some() { - result += 1; - } - result -} - -fn step_matches_transition(step: &SelectorStep, transition: &PropertyTransitionJSON) -> bool { - step.kind - .as_ref() - .map_or(true, |kind| transition.kind.as_ref() == Some(kind)) - && step - .is_named - .map_or(true, |named| transition.named == Some(named)) - && step - .field - .as_ref() - .map_or(true, |field| transition.field.as_ref() == Some(field)) - && step - .child_index - .map_or(true, |index| transition.index == Some(index)) - && step - .text_pattern - .as_ref() - .map_or(true, |text| transition.text.as_ref() == Some(text)) -} - -impl fmt::Debug for SelectorStep { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.is_immediate { - write!(f, "> ")?; - } - write!(f, "(")?; - if let Some(kind) = &self.kind { - if self.is_named.unwrap() { - write!(f, "{}", kind)?; - } else { - write!(f, "[token='{}']", kind)?; - } - } - if let Some(field) = &self.field { - write!(f, ".{}", field)?; - } - if let Some(n) = self.child_index { - write!(f, ":nth-child({})", n)?; - } - if let Some(t) = &self.text_pattern { - write!(f, "[text='{}']", t)?; - } - write!(f, ")")?; - Ok(()) - } -} - -impl fmt::Debug for Selector { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[")?; - for (i, step) in self.0.iter().enumerate() { - if step.is_immediate { - write!(f, " > ")?; - } else if i > 0 { - write!(f, " ")?; - } - write!(f, "{:?}", step)?; - } - write!(f, " (specificity: {})]", selector_specificity(self))?; - Ok(()) - } -} - -pub(crate) fn generate_property_sheet( - path: impl AsRef, - css: &str, - token_names: &HashSet, -) -> Result { - let rules = parse_property_sheet(path.as_ref(), &css)?; - Ok(Builder::new(&rules, token_names).build()) -} - -fn parse_property_sheet(path: &Path, css: &str) -> Result> { - let mut schema_paths = Vec::new(); - let css = css.as_bytes(); - let mut items = rsass::parse_scss_data(css).map_err(|(pos, kind)| rsass::Error::ParseError { - file: path.to_string_lossy().into(), - pos: rsass::ErrPos::pos_of(pos, &css), - kind, - })?; - - process_at_rules(&mut items, &mut schema_paths, path)?; - let mut result = Vec::new(); - let selector_prefixes = vec![Vec::new()]; - parse_sass_items(items, &selector_prefixes, &mut result)?; - Ok(result) -} - -fn parse_sass_items( - items: Vec, - selector_prefixes: &Vec>, - result: &mut Vec, -) -> Result<()> { - let mut properties = PropertySet::new(); - for item in items { - match item { - rsass::Item::None | rsass::Item::Comment(_) => {} - rsass::Item::Property(name, value) => { - let value = parse_sass_value(&value)?; - match properties.entry(name.to_string()) { - btree_map::Entry::Vacant(v) => { - v.insert(value); - } - btree_map::Entry::Occupied(mut o) => { - let existing_value = o.get_mut(); - if let PropertyValue::Array(items) = existing_value { - items.push(value); - continue; - } else { - let v = existing_value.clone(); - *existing_value = PropertyValue::Array(vec![v, value]); - } - } - } - } - rsass::Item::Rule(selectors, items) => { - let mut full_selectors = Vec::new(); - for prefix in selector_prefixes { - for selector in &selectors.s { - let mut prefix = prefix.clone(); - let mut operator_was_immediate: Option = Some(false); - for part in &selector.0 { - match part { - SelectorPart::BackRef => { - operator_was_immediate = None; - } - SelectorPart::Simple(value) => { - if let Some(value) = value.single_raw() { - for (i, value) in value.split('.').enumerate() { - if value.is_empty() { - continue; - } - let value = value.to_string(); - check_node_kind(&value)?; - if i > 0 { - if let Some(immediate) = operator_was_immediate { - prefix.push(SelectorStep { - kind: None, - field: Some(value), - is_named: None, - child_index: None, - text_pattern: None, - is_immediate: immediate, - }) - } else { - prefix.last_mut().unwrap().field = Some(value); - } - } else { - if let Some(immediate) = operator_was_immediate { - prefix.push(SelectorStep { - kind: Some(value.to_string()), - field: None, - child_index: None, - text_pattern: None, - is_named: Some(true), - is_immediate: immediate, - }); - } else { - return Error::err(format!("Node type {} must be separated by whitespace or the `>` operator", value)); - } - } - operator_was_immediate = None; - } - } else { - return Err(interpolation_error()); - } - operator_was_immediate = None; - } - SelectorPart::Attribute { name, val, .. } => { - match name.single_raw() { - None => return Err(interpolation_error()), - Some("text") => { - if operator_was_immediate.is_some() { - return Error::err("The `text` attribute must be used in combination with a node type or field".to_string()); - } - if let Some(last_step) = prefix.last_mut() { - last_step.text_pattern = - Some(get_string_value(val.to_string())?) - } - } - Some("token") => { - if let Some(immediate) = operator_was_immediate { - prefix.push(SelectorStep { - kind: Some(get_string_value(val.to_string())?), - field: None, - is_named: Some(false), - child_index: None, - text_pattern: None, - is_immediate: immediate, - }); - operator_was_immediate = None; - } else { - return Error::err("The `token` attribute canot be used in combination with a node type".to_string()); - } - } - _ => { - return Error::err(format!( - "Unsupported attribute {}", - part - )); - } - } - } - SelectorPart::PseudoElement { .. } => { - return Error::err( - "Pseudo elements are not supported".to_string(), - ); - } - SelectorPart::Pseudo { name, arg } => match name.single_raw() { - None => return Err(interpolation_error()), - Some("nth-child") => { - if let Some(arg) = arg { - let mut arg_str = String::new(); - write!(&mut arg_str, "{}", arg).unwrap(); - if let Some(last_step) = prefix.last_mut() { - if let Ok(i) = usize::from_str_radix(&arg_str, 10) { - last_step.child_index = Some(i); - } else { - return Error::err(format!( - "Invalid child index {}", - arg - )); - } - } - } - } - _ => { - return Error::err(format!( - "Unsupported pseudo-class {}", - part - )); - } - }, - SelectorPart::Descendant => { - operator_was_immediate = Some(false); - } - SelectorPart::RelOp(operator) => { - let operator = *operator as char; - if operator == '>' { - operator_was_immediate = Some(true); - } else { - return Error::err(format!( - "Unsupported operator {}", - operator - )); - } - } - } - } - full_selectors.push(prefix); - } - } - parse_sass_items(items, &full_selectors, result)?; - } - _ => return Error::err(format!("Unsupported syntax type {:?}", item)), - } - } - - if !properties.is_empty() { - result.push(Rule { - selectors: selector_prefixes.iter().cloned().map(Selector).collect(), - properties, - }); - } - - Ok(()) -} - -fn process_at_rules( - items: &mut Vec, - schema_paths: &mut Vec, - path: &Path, -) -> Result<()> { - let mut i = 0; - while i < items.len() { - match &items[i] { - rsass::Item::Import(arg) => { - if let Some(s) = get_sass_string(arg) { - let import_path = resolve_path(path, s)?; - let mut imported_items = rsass::parse_scss_file(&import_path)?; - process_at_rules(&mut imported_items, schema_paths, &import_path)?; - items.splice(i..(i + 1), imported_items); - continue; - } else { - return Err(Error::new("@import arguments must be strings".to_string())); - } - } - rsass::Item::AtRule { name, args, .. } => match name.as_str() { - "schema" => { - if let Some(s) = get_sass_string(args) { - let schema_path = resolve_path(path, s)?; - schema_paths.push(schema_path); - items.remove(i); - continue; - } else { - return Error::err("@schema arguments must be strings".to_string()); - } - } - _ => return Error::err(format!("Unsupported at-rule '{}'", name)), - }, - _ => {} - } - i += 1; - } - Ok(()) -} - -fn parse_sass_value(value: &Value) -> Result { - match value { - Value::Literal(s) => { - if let Some(s) = s.single_raw() { - Ok(PropertyValue::String(s.to_string())) - } else { - Err(interpolation_error()) - } - } - Value::Call(name, raw_args) => { - if let Some(name) = name.single_raw() { - let mut args = Vec::new(); - for (_, arg) in raw_args.iter() { - args.push(parse_sass_value(arg)?); - } - let mut result = PropertySet::new(); - result.insert("name".to_string(), PropertyValue::String(name.to_string())); - result.insert("args".to_string(), PropertyValue::Array(args)); - Ok(PropertyValue::Object(result)) - } else { - Err(Error::new( - "String interpolation is not supported".to_string(), - )) - } - } - Value::List(elements, ..) => { - let mut result = Vec::new(); - for element in elements { - result.push(parse_sass_value(element)?); - } - Ok(PropertyValue::Array(result)) - } - Value::Color(_, Some(name)) => Ok(PropertyValue::String(name.clone())), - Value::Numeric(n, _) => Ok(PropertyValue::Number(n.to_integer())), - Value::True => Ok(PropertyValue::Boolean(true)), - Value::False => Ok(PropertyValue::Boolean(false)), - _ => Err(Error::new(format!( - "Property values must be strings or function calls. Got {:?}", - value - ))), - } -} - -fn get_sass_string(value: &Value) -> Option<&str> { - if let Value::Literal(s) = value { - s.single_raw() - } else { - None - } -} - -fn resolve_path(base: &Path, p: &str) -> Result { - let path = Path::new(p); - let mut base = base.to_owned(); - base.pop(); - if path.starts_with(".") { - base.push(path); - if base.exists() { - return Ok(base); - } - } else { - loop { - let mut result = base.clone(); - result.push("node_modules"); - result.push(path); - if result.exists() { - return Ok(result); - } - if !base.pop() { - break; - } - } - } - Err(Error::new(format!("Could not resolve import path `{}`", p))) -} - -fn check_node_kind(name: &String) -> Result<()> { - for c in name.chars() { - if !c.is_alphanumeric() && c != '_' { - return Err(Error::new(format!("Invalid identifier '{}'", name))); - } - } - Ok(()) -} - -fn get_string_value(mut s: String) -> Result { - if s.starts_with("'") && s.ends_with("'") || s.starts_with('"') && s.ends_with('"') { - s.pop(); - s.remove(0); - Ok(s) - } else { - Err(Error::new(format!("Unsupported string literal {}", s))) - } -} - -fn interpolation_error() -> Error { - Error::new("String interpolation is not supported".to_string()) -} - -#[cfg(test)] -mod tests { - use super::*; - use regex::Regex; - use std::fs; - use tempfile::TempDir; - - #[test] - fn test_property_sheet_with_immediate_child_and_descendant_selectors() { - let sheet = generate_property_sheet( - "foo.css", - " - f1 { - color: red; - - & > f2 { - color: green; - } - - & f3 { - color: blue; - } - } - - f2 { - color: indigo; - height: 2; - } - - f3 { - color: violet; - height: 3; - } - ", - &HashSet::new(), - ) - .unwrap(); - - // f1 single-element selector - assert_eq!( - *query_simple(&sheet, vec!["f1"]), - props(&[("color", string("red"))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f2", "f1"]), - props(&[("color", string("red"))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f2", "f3", "f1"]), - props(&[("color", string("red"))]) - ); - - // f2 single-element selector - assert_eq!( - *query_simple(&sheet, vec!["f2"]), - props(&[("color", string("indigo")), ("height", num(2))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f2", "f2"]), - props(&[("color", string("indigo")), ("height", num(2))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f1", "f3", "f2"]), - props(&[("color", string("indigo")), ("height", num(2))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f1", "f6", "f2"]), - props(&[("color", string("indigo")), ("height", num(2))]) - ); - - // f3 single-element selector - assert_eq!( - *query_simple(&sheet, vec!["f3"]), - props(&[("color", string("violet")), ("height", num(3))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f2", "f3"]), - props(&[("color", string("violet")), ("height", num(3))]) - ); - - // f2 child selector - assert_eq!( - *query_simple(&sheet, vec!["f1", "f2"]), - props(&[("color", string("green")), ("height", num(2))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f2", "f1", "f2"]), - props(&[("color", string("green")), ("height", num(2))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f3", "f1", "f2"]), - props(&[("color", string("green")), ("height", num(2))]) - ); - - // f3 descendant selector - assert_eq!( - *query_simple(&sheet, vec!["f1", "f3"]), - props(&[("color", string("blue")), ("height", num(3))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f1", "f2", "f3"]), - props(&[("color", string("blue")), ("height", num(3))]) - ); - assert_eq!( - *query_simple(&sheet, vec!["f1", "f6", "f7", "f8", "f3"]), - props(&[("color", string("blue")), ("height", num(3))]) - ); - - // no match - assert_eq!(*query_simple(&sheet, vec!["f1", "f3", "f4"]), props(&[])); - assert_eq!(*query_simple(&sheet, vec!["f1", "f2", "f5"]), props(&[])); - } - - #[test] - fn test_property_sheet_with_text_attribute() { - let sheet = generate_property_sheet( - "foo.css", - " - f1 { - color: red; - - &[text='^[A-Z]'] { - color: green; - } - - &[text='^[A-Z_]+$'] { - color: blue; - } - } - - f2[text='^[A-Z_]+$'] { - color: purple; - } - ", - &HashSet::new(), - ) - .unwrap(); - - assert_eq!( - *query(&sheet, vec![("f1", None, true, 0)], "abc"), - props(&[("color", string("red"))]) - ); - assert_eq!( - *query(&sheet, vec![("f1", None, true, 0)], "Abc"), - props(&[("color", string("green"))]) - ); - assert_eq!( - *query(&sheet, vec![("f1", None, true, 0)], "AB_CD"), - props(&[("color", string("blue"))]) - ); - assert_eq!( - *query(&sheet, vec![("f2", None, true, 0)], "Abc"), - props(&[]) - ); - assert_eq!( - *query(&sheet, vec![("f2", None, true, 0)], "ABC"), - props(&[("color", string("purple"))]) - ); - } - - #[test] - fn test_property_sheet_with_fields() { - let sheet = generate_property_sheet( - "foo.css", - " - a { - color: red; - &.x { - color: green; - b { - color: blue; - &.y { color: yellow; } - } - } - b { color: orange; } - b.y { color: indigo; } - } - .x { color: violet; } - ", - &HashSet::new(), - ) - .unwrap(); - - assert_eq!( - *query(&sheet, vec![("a", None, true, 0)], ""), - props(&[("color", string("red"))]) - ); - assert_eq!( - *query(&sheet, vec![("a", Some("x"), true, 0)], ""), - props(&[("color", string("green"))]) - ); - assert_eq!( - *query( - &sheet, - vec![("a", Some("x"), true, 0), ("b", None, true, 0)], - "" - ), - props(&[("color", string("blue"))]) - ); - assert_eq!( - *query( - &sheet, - vec![("a", Some("x"), true, 0), ("b", Some("y"), true, 0)], - "" - ), - props(&[("color", string("yellow"))]) - ); - assert_eq!( - *query(&sheet, vec![("b", Some("x"), true, 0)], ""), - props(&[("color", string("violet"))]) - ); - assert_eq!( - *query(&sheet, vec![("a", None, true, 0), ("b", None, true, 0)], ""), - props(&[("color", string("orange"))]) - ); - assert_eq!( - *query( - &sheet, - vec![("a", None, true, 0), ("b", Some("y"), true, 0)], - "" - ), - props(&[("color", string("indigo"))]) - ); - } - - #[test] - fn test_property_sheet_with_cascade_ordering_as_tie_breaker() { - let sheet = generate_property_sheet( - "foo.css", - " - f1 f2:nth-child(1) { color: red; } - f1:nth-child(1) f2 { color: green; } - f1 f2[text='a'] { color: blue; } - f1 f2[text='b'] { color: violet; } - ", - &HashSet::new(), - ) - .unwrap(); - - assert_eq!( - *query( - &sheet, - vec![("f1", None, true, 0), ("f2", None, true, 0)], - "x" - ), - props(&[]) - ); - assert_eq!( - *query( - &sheet, - vec![("f1", None, true, 0), ("f2", None, true, 1)], - "x" - ), - props(&[("color", string("red"))]) - ); - assert_eq!( - *query( - &sheet, - vec![("f1", None, true, 1), ("f2", None, true, 1)], - "x" - ), - props(&[("color", string("green"))]) - ); - assert_eq!( - *query( - &sheet, - vec![("f1", None, true, 1), ("f2", None, true, 1)], - "a" - ), - props(&[("color", string("blue"))]) - ); - assert_eq!( - *query( - &sheet, - vec![("f1", None, true, 1), ("f2", None, true, 1)], - "ab" - ), - props(&[("color", string("violet"))]) - ); - } - - #[test] - fn test_property_sheet_with_css_function_calls() { - let sheet = generate_property_sheet( - "foo.css", - " - a { - b: f(); - c: f(g(h), i, \"j\", 10); - } - ", - &HashSet::new(), - ) - .unwrap(); - - let p = query_simple(&sheet, vec!["a"]); - - assert_eq!( - p["b"], - object(&[("name", string("f")), ("args", array(vec![])),]) - ); - - assert_eq!( - p["c"], - object(&[ - ("name", string("f")), - ( - "args", - array(vec![ - object(&[("name", string("g")), ("args", array(vec![string("h"),]))]), - string("i"), - string("j"), - num(10), - ]) - ), - ]) - ); - - // Handle differently-formatted calls - let sheet2 = generate_property_sheet( - "foo.css", - " - a { - b: f(); - c: f( - g(h), - i, - \"j\", - 10 - ); - } - ", - &HashSet::new(), - ) - .unwrap(); - - assert_eq!( - query_simple(&sheet2, vec!["a"])["c"], - query_simple(&sheet, vec!["a"])["c"] - ); - } - - #[test] - fn test_property_sheet_with_array_by_declaring_property_multiple_times() { - let sheet = generate_property_sheet( - "foo.css", - " - a { - b: 'foo'; - b: 'bar'; - b: 'baz'; - c: f(g()); - c: h(); - } - ", - &HashSet::new(), - ) - .unwrap(); - - let p = query_simple(&sheet, vec!["a"]); - - assert_eq!( - p["b"], - array(vec![string("foo"), string("bar"), string("baz"),]) - ); - - assert_eq!( - p["c"], - array(vec![ - object(&[ - ("name", string("f")), - ( - "args", - array(vec![object(&[ - ("name", string("g")), - ("args", array(vec![])), - ])]) - ) - ]), - object(&[("name", string("h")), ("args", array(vec![])),]), - ]), - ); - } - - #[test] - fn test_property_sheet_with_imports() { - let repo_dir = TempDir::new().unwrap(); - let properties_dir = repo_dir.path().join("properties"); - let dependency_properties_dir = repo_dir - .path() - .join("node_modules") - .join("the-dependency") - .join("properties"); - fs::create_dir_all(&properties_dir).unwrap(); - fs::create_dir_all(&dependency_properties_dir).unwrap(); - let sheet_path1 = properties_dir.join("sheet1.css"); - let sheet_path2 = properties_dir.join("sheet2.css"); - let dependency_sheet_path1 = dependency_properties_dir.join("dependency-sheet1.css"); - let dependency_sheet_path2 = dependency_properties_dir.join("dependency-sheet2.css"); - - fs::write( - sheet_path2, - r#" - a { x: '1'; } - "#, - ) - .unwrap(); - fs::write( - dependency_sheet_path1, - r#" - @import "./dependency-sheet2.css"; - a { y: '2'; } - "#, - ) - .unwrap(); - fs::write( - dependency_sheet_path2, - r#" - b { x: '3'; } - "#, - ) - .unwrap(); - let sheet = generate_property_sheet( - sheet_path1, - r#" - @import "./sheet2.css"; - @import "the-dependency/properties/dependency-sheet1.css"; - b { y: '4'; } - "#, - &HashSet::new(), - ) - .unwrap(); - - let a = query_simple(&sheet, vec!["a"]); - assert_eq!(a["x"], string("1"),); - assert_eq!(a["y"], string("2"),); - let b = query_simple(&sheet, vec!["b"]); - assert_eq!(b["x"], string("3"),); - assert_eq!(b["y"], string("4"),); - } - - fn query_simple<'a>( - sheet: &'a PropertySheetJSON, - node_stack: Vec<&'static str>, - ) -> &'a PropertySet { - query( - sheet, - node_stack.into_iter().map(|s| (s, None, true, 0)).collect(), - "", - ) - } - - fn query<'a>( - sheet: &'a PropertySheetJSON, - node_stack: Vec<(&'static str, Option<&'static str>, bool, usize)>, - leaf_text: &str, - ) -> &'a PropertySet { - let mut state_id = 0; - for (kind, field, is_named, child_index) in node_stack { - let state = &sheet.states[state_id]; - state_id = state - .transitions - .iter() - .chain(sheet.states[0].transitions.iter()) - .find(|transition| { - transition.kind.as_ref().map_or(true, |k| k == kind) - && transition.named.map_or(true, |n| n == is_named) - && transition.field.as_ref().map_or(true, |f| field == Some(f)) - && transition.index.map_or(true, |index| index == child_index) - && (transition - .text - .as_ref() - .map_or(true, |text| Regex::new(text).unwrap().is_match(leaf_text))) - }) - .map_or(state.default_next_state_id, |t| t.state_id); - } - &sheet.property_sets[sheet.states[state_id].property_set_id] - } - - fn array(s: Vec) -> PropertyValue { - PropertyValue::Array(s) - } - - fn object<'a>(s: &'a [(&'a str, PropertyValue)]) -> PropertyValue { - PropertyValue::Object( - s.into_iter() - .map(|(a, b)| (a.to_string(), b.clone())) - .collect(), - ) - } - - fn string(s: &str) -> PropertyValue { - PropertyValue::String(s.to_string()) - } - - fn num(n: isize) -> PropertyValue { - PropertyValue::Number(n) - } - - fn props<'a>(s: &'a [(&'a str, PropertyValue)]) -> PropertySet { - s.into_iter() - .map(|(a, b)| (a.to_string(), b.clone())) - .collect() - } -} diff --git a/cli/src/main.rs b/cli/src/main.rs index 832cd92c..8f5ac503 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -40,7 +40,6 @@ fn run() -> error::Result<()> { .arg(Arg::with_name("grammar-path").index(1)) .arg(Arg::with_name("log").long("log")) .arg(Arg::with_name("next-abi").long("next-abi")) - .arg(Arg::with_name("properties-only").long("properties")) .arg( Arg::with_name("report-states-for-rule") .long("report-states-for-rule") @@ -142,7 +141,6 @@ fn run() -> error::Result<()> { config.save(&home_dir)?; } else if let Some(matches) = matches.subcommand_matches("generate") { let grammar_path = matches.value_of("grammar-path"); - let properties_only = matches.is_present("properties-only"); let report_symbol_name = matches.value_of("report-states-for-rule").or_else(|| { if matches.is_present("report-states") { Some("") @@ -157,7 +155,6 @@ fn run() -> error::Result<()> { generate::generate_parser_in_directory( ¤t_dir, grammar_path, - properties_only, next_abi, report_symbol_name, )?; diff --git a/cli/src/tests/mod.rs b/cli/src/tests/mod.rs index 1a2a71ff..cc1d5967 100644 --- a/cli/src/tests/mod.rs +++ b/cli/src/tests/mod.rs @@ -3,6 +3,5 @@ mod helpers; mod highlight_test; mod node_test; mod parser_test; -mod properties_test; mod query_test; mod tree_test; diff --git a/cli/src/tests/properties_test.rs b/cli/src/tests/properties_test.rs deleted file mode 100644 index 51f0e820..00000000 --- a/cli/src/tests/properties_test.rs +++ /dev/null @@ -1,265 +0,0 @@ -use super::helpers::fixtures::get_language; -use crate::generate::properties; -use serde_derive::Deserialize; -use serde_json; - -use std::collections::HashSet; -use tree_sitter::{Parser, PropertySheet}; -#[derive(Debug, Default, Deserialize, PartialEq, Eq)] -struct Properties { - a: Option, - b: Option, -} - -#[test] -fn test_walk_with_properties_with_nth_child() { - let language = get_language("javascript"); - let property_sheet = PropertySheet::::new( - language, - &generate_property_sheet_string( - "/some/path.css", - " - binary_expression > identifier:nth-child(2) { - a: x; - } - - binary_expression > identifier { - a: y; - } - - identifier { - a: z; - } - ", - ), - ) - .unwrap(); - - let source_code = "a = b || c;"; - - let mut parser = Parser::new(); - parser.set_language(language).unwrap(); - let tree = parser.parse(source_code, None).unwrap(); - - let mut cursor = tree.walk_with_properties(&property_sheet, source_code.as_bytes()); - assert_eq!(cursor.node().kind(), "program"); - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "expression_statement"); - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "assignment_expression"); - - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("z".to_string()), - b: None - } - ); - - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "="); - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "binary_expression"); - - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("y".to_string()), - b: None - } - ); - - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "||"); - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("x".to_string()), - b: None - } - ); -} - -#[test] -fn test_walk_with_properties_with_regexes() { - let language = get_language("javascript"); - let property_sheet = PropertySheet::::new( - language, - &generate_property_sheet_string( - "/some/path.css", - " - identifier { - &[text='^[A-Z]'] { - a: y; - } - - &[text='^[A-Z_]+$'] { - a: z; - } - - a: x; - } - ", - ), - ) - .unwrap(); - - let source_code = "const ABC = Def(ghi);"; - - let mut parser = Parser::new(); - parser.set_language(language).unwrap(); - let tree = parser.parse(source_code, None).unwrap(); - - let mut cursor = tree.walk_with_properties(&property_sheet, source_code.as_bytes()); - assert_eq!(cursor.node().kind(), "program"); - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "lexical_declaration"); - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "const"); - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "variable_declarator"); - - // The later selector with a text regex overrides the earlier one. - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("z".to_string()), - b: None - } - ); - - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "="); - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "call_expression"); - - // The selectors with text regexes override the selector without one. - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("y".to_string()), - b: None - } - ); - - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "arguments"); - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "("); - - // This node doesn't match either of the regexes. - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("x".to_string()), - b: None - } - ); -} - -#[test] -fn test_walk_with_properties_based_on_fields() { - let language = get_language("javascript"); - let property_sheet = PropertySheet::::new( - language, - &generate_property_sheet_string( - "/some/path.css", - " - arrow_function > .parameter { - a: x; - } - - function_declaration { - & > .parameters > identifier { - a: y; - } - - & > .name { - b: z; - } - } - - identifier { - a: w; - } - ", - ), - ) - .unwrap(); - - let source_code = "function a(b) { return c => c + b; }"; - - let mut parser = Parser::new(); - parser.set_language(language).unwrap(); - let tree = parser.parse(source_code, None).unwrap(); - let mut cursor = tree.walk_with_properties(&property_sheet, source_code.as_bytes()); - - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "function_declaration"); - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "function"); - assert_eq!(*cursor.node_properties(), Properties::default()); - - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("w".to_string()), - b: Some("z".to_string()) - } - ); - - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "formal_parameters"); - assert_eq!(*cursor.node_properties(), Properties::default()); - - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "("); - assert_eq!(*cursor.node_properties(), Properties::default()); - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("y".to_string()), - b: None, - } - ); - - assert!(cursor.goto_parent()); - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "statement_block"); - assert!(cursor.goto_first_child()); - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "return_statement"); - assert!(cursor.goto_first_child()); - assert!(cursor.goto_next_sibling()); - assert_eq!(cursor.node().kind(), "arrow_function"); - assert!(cursor.goto_first_child()); - assert_eq!(cursor.node().kind(), "identifier"); - assert_eq!( - *cursor.node_properties(), - Properties { - a: Some("x".to_string()), - b: None, - } - ); -} - -fn generate_property_sheet_string(path: &str, css: &str) -> String { - serde_json::to_string(&properties::generate_property_sheet(path, css, &HashSet::new()).unwrap()) - .unwrap() -} diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index 491c7db2..1688dbd3 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -1,18 +1,14 @@ mod ffi; mod util; -#[macro_use] -extern crate serde_derive; extern crate regex; extern crate serde; +extern crate serde_derive; extern crate serde_json; #[cfg(unix)] use std::os::unix::io::AsRawFd; -use regex::Regex; -use serde::de::DeserializeOwned; -use std::collections::HashMap; use std::ffi::CStr; use std::marker::PhantomData; use std::mem::MaybeUninit; @@ -65,62 +61,6 @@ pub struct InputEdit { pub new_end_position: Point, } -struct PropertyTransition { - state_id: u16, - child_index: Option, - text_regex_index: Option, - node_kind_id: Option, -} - -struct PropertyState { - field_transitions: HashMap>, - kind_transitions: HashMap>, - property_set_id: usize, - default_next_state_id: usize, -} - -#[derive(Debug)] -pub enum PropertySheetError { - InvalidJSON(serde_json::Error), - InvalidRegex(regex::Error), -} - -pub struct PropertySheet

> { - states: Vec, - property_sets: Vec

, - text_regexes: Vec, -} - -#[derive(Clone, Debug, Deserialize, Serialize, Hash, PartialEq, Eq)] -pub struct PropertyTransitionJSON { - #[serde(rename = "type")] - #[serde(skip_serializing_if = "Option::is_none")] - pub kind: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub named: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub index: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub field: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub text: Option, - pub state_id: usize, -} - -#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)] -pub struct PropertyStateJSON { - pub id: Option, - pub property_set_id: usize, - pub transitions: Vec, - pub default_next_state_id: usize, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct PropertySheetJSON

{ - pub states: Vec, - pub property_sets: Vec

, -} - #[derive(Clone, Copy)] #[repr(transparent)] pub struct Node<'a>(ffi::TSNode, PhantomData<&'a ()>); @@ -131,14 +71,6 @@ pub struct Tree(NonNull); pub struct TreeCursor<'a>(ffi::TSTreeCursor, PhantomData<&'a ()>); -pub struct TreePropertyCursor<'a, P> { - cursor: TreeCursor<'a>, - state_stack: Vec, - child_index_stack: Vec, - property_sheet: &'a PropertySheet

, - source: &'a [u8], -} - #[derive(Debug)] enum TextPredicate { CaptureEqString(u32, String), @@ -252,6 +184,7 @@ impl fmt::Display for LanguageError { } impl Parser { + /// Create a new parser. pub fn new() -> Parser { unsafe { let parser = ffi::ts_parser_new(); @@ -259,6 +192,14 @@ impl Parser { } } + /// Set the language that the parser should use for parsing. + /// + /// Returns a Result indicating whether or not the language was successfully + /// assigned. True means assignment succeeded. False means there was a version + /// mismatch: the language was generated with an incompatible version of the + /// Tree-sitter CLI. Check the language's version using `ts_language_version` + /// and compare it to this library's `TREE_SITTER_LANGUAGE_VERSION` and + /// `TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION` constants. pub fn set_language(&mut self, language: Language) -> Result<(), LanguageError> { let version = language.version(); if version < ffi::TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION @@ -554,14 +495,6 @@ impl Tree { self.root_node().walk() } - pub fn walk_with_properties<'a, P>( - &'a self, - property_sheet: &'a PropertySheet

, - source: &'a [u8], - ) -> TreePropertyCursor<'a, P> { - TreePropertyCursor::new(self, property_sheet, source) - } - pub fn changed_ranges(&self, other: &Tree) -> impl ExactSizeIterator { let mut count = 0; unsafe { @@ -858,125 +791,6 @@ impl<'a> Drop for TreeCursor<'a> { } } -impl<'a, P> TreePropertyCursor<'a, P> { - fn new(tree: &'a Tree, property_sheet: &'a PropertySheet

, source: &'a [u8]) -> Self { - let mut result = Self { - cursor: tree.root_node().walk(), - child_index_stack: vec![0], - state_stack: vec![0], - property_sheet, - source, - }; - let state = result.next_state(0); - result.state_stack.push(state); - result - } - - pub fn node(&self) -> Node<'a> { - self.cursor.node() - } - - pub fn node_properties(&self) -> &'a P { - &self.property_sheet.property_sets[self.current_state().property_set_id] - } - - pub fn goto_first_child(&mut self) -> bool { - if self.cursor.goto_first_child() { - let next_state_id = self.next_state(0); - self.state_stack.push(next_state_id); - self.child_index_stack.push(0); - true - } else { - false - } - } - - pub fn goto_next_sibling(&mut self) -> bool { - if self.cursor.goto_next_sibling() { - let child_index = self.child_index_stack.pop().unwrap() + 1; - self.state_stack.pop(); - let next_state_id = self.next_state(child_index); - self.state_stack.push(next_state_id); - self.child_index_stack.push(child_index); - true - } else { - false - } - } - - pub fn goto_parent(&mut self) -> bool { - if self.cursor.goto_parent() { - self.state_stack.pop(); - self.child_index_stack.pop(); - true - } else { - false - } - } - - pub fn source(&self) -> &'a [u8] { - &self.source - } - - fn next_state(&self, node_child_index: usize) -> usize { - let current_state = self.current_state(); - let default_state = self.default_state(); - - for state in [current_state, default_state].iter() { - let node_field_id = self.cursor.field_id(); - let node_kind_id = self.cursor.node().kind_id(); - let transitions = node_field_id - .and_then(|field_id| state.field_transitions.get(&field_id)) - .or_else(|| state.kind_transitions.get(&node_kind_id)); - - if let Some(transitions) = transitions { - for transition in transitions.iter() { - if transition - .node_kind_id - .map_or(false, |id| id != node_kind_id) - { - continue; - } - - if let Some(text_regex_index) = transition.text_regex_index { - let node = self.cursor.node(); - let text = &self.source[node.start_byte()..node.end_byte()]; - if let Ok(text) = str::from_utf8(text) { - if !self.property_sheet.text_regexes[text_regex_index as usize] - .is_match(text) - { - continue; - } - } - } - - if let Some(child_index) = transition.child_index { - if child_index != node_child_index as u16 { - continue; - } - } - - return transition.state_id as usize; - } - } - - if current_state as *const PropertyState == default_state as *const PropertyState { - break; - } - } - - current_state.default_next_state_id - } - - fn current_state(&self) -> &PropertyState { - &self.property_sheet.states[*self.state_stack.last().unwrap()] - } - - fn default_state(&self) -> &PropertyState { - &self.property_sheet.states.first().unwrap() - } -} - impl Query { pub fn new(language: Language, source: &str) -> Result { let mut error_offset = 0u32; @@ -1513,154 +1327,6 @@ impl<'a> Into for &'a InputEdit { } } -impl

PropertySheet

{ - pub fn new(language: Language, json: &str) -> Result - where - P: DeserializeOwned, - { - let input: PropertySheetJSON

= - serde_json::from_str(json).map_err(PropertySheetError::InvalidJSON)?; - let mut states = Vec::new(); - let mut text_regexes = Vec::new(); - let mut text_regex_patterns = Vec::new(); - - for state in input.states.iter() { - let node_kind_count = language.node_kind_count(); - let mut kind_transitions = HashMap::new(); - let mut field_transitions = HashMap::new(); - - for transition in state.transitions.iter() { - let field_id = transition - .field - .as_ref() - .and_then(|field| language.field_id_for_name(&field)); - if let Some(field_id) = field_id { - field_transitions.entry(field_id).or_insert(Vec::new()); - } - } - - for transition in state.transitions.iter() { - let text_regex_index = if let Some(regex_pattern) = transition.text.as_ref() { - if let Some(index) = - text_regex_patterns.iter().position(|r| *r == regex_pattern) - { - Some(index as u16) - } else { - text_regex_patterns.push(regex_pattern); - text_regexes.push( - Regex::new(®ex_pattern).map_err(PropertySheetError::InvalidRegex)?, - ); - Some(text_regexes.len() as u16 - 1) - } - } else { - None - }; - - let state_id = transition.state_id as u16; - let child_index = transition.index.map(|i| i as u16); - let field_id = transition - .field - .as_ref() - .and_then(|field| language.field_id_for_name(&field)); - - if let Some(kind) = transition.kind.as_ref() { - for kind_id in 0..(node_kind_count as u16) { - if kind != language.node_kind_for_id(kind_id) - || transition.named != Some(language.node_kind_is_named(kind_id)) - { - continue; - } - - if let Some(field_id) = field_id { - field_transitions - .entry(field_id) - .or_insert(Vec::new()) - .push(PropertyTransition { - node_kind_id: Some(kind_id), - state_id, - child_index, - text_regex_index, - }); - } else { - for (_, entries) in field_transitions.iter_mut() { - entries.push(PropertyTransition { - node_kind_id: Some(kind_id), - state_id, - child_index, - text_regex_index, - }); - } - - kind_transitions.entry(kind_id).or_insert(Vec::new()).push( - PropertyTransition { - node_kind_id: None, - state_id, - child_index, - text_regex_index, - }, - ); - } - } - } else if let Some(field_id) = field_id { - field_transitions - .entry(field_id) - .or_insert(Vec::new()) - .push(PropertyTransition { - node_kind_id: None, - state_id, - child_index, - text_regex_index, - }); - } - } - states.push(PropertyState { - field_transitions, - kind_transitions, - default_next_state_id: state.default_next_state_id, - property_set_id: state.property_set_id, - }); - } - Ok(Self { - property_sets: input.property_sets, - states, - text_regexes, - }) - } - - pub fn map(self, mut f: F) -> Result, E> - where - F: FnMut(P) -> Result, - { - let mut property_sets = Vec::with_capacity(self.property_sets.len()); - for set in self.property_sets { - property_sets.push(f(set)?); - } - Ok(PropertySheet { - states: self.states, - text_regexes: self.text_regexes, - property_sets, - }) - } -} - -impl fmt::Display for PropertySheetError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - PropertySheetError::InvalidJSON(e) => write!(f, "Invalid JSON: {}", e), - PropertySheetError::InvalidRegex(e) => write!(f, "Invalid Regex: {}", e), - } - } -} - -impl std::error::Error for PropertySheetError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - PropertySheetError::InvalidJSON(e) => Some(e), - PropertySheetError::InvalidRegex(e) => Some(e), - } - } -} - unsafe impl Send for Language {} unsafe impl Send for Parser {} unsafe impl Send for Query {}