diff --git a/.clang-format-ignore b/.clang-format-ignore index 7052180e6..4223febaa 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -5,5 +5,6 @@ src/ast_pretty_print.c src/errors.c src/include/ast_nodes.h src/include/ast_pretty_print.h +src/include/css_parser.h src/include/errors.h src/visitor.c diff --git a/.github/workflows/build-gems.yml b/.github/workflows/build-gems.yml index df6338b44..dc3cbf95f 100644 --- a/.github/workflows/build-gems.yml +++ b/.github/workflows/build-gems.yml @@ -75,6 +75,14 @@ jobs: with: bundler-cache: false + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Build CSS Parser + run: cd src/css && cargo build --release + - name: bundle install run: bundle install diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86b9f824a..b93787b09 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,12 +34,26 @@ jobs: with: bundler-cache: true + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Install Rust WASM target + run: rustup target add wasm32-unknown-emscripten + + - name: Build CSS Parser + run: cd src/css && cargo build --release + - name: bundle install run: bundle install - name: Render Templates run: bundle exec rake templates + - name: make all + run: make all + - name: Compile Herb run: bundle exec rake make diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3be067faf..5c3966e0a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -53,6 +53,17 @@ jobs: with: bundler-cache: true + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Install Rust WASM target + run: rustup target add wasm32-unknown-emscripten + + - name: Build CSS Parser + run: cd src/css && cargo build --release + - name: bundle install run: bundle install diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index f7bd70a60..d577afbc1 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -41,6 +41,17 @@ jobs: with: bundler-cache: true + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Install Rust WASM target + run: rustup target add wasm32-unknown-emscripten + + - name: Build CSS Parser + run: cd src/css && cargo build --release + - name: bundle install run: bundle install diff --git a/.gitignore b/.gitignore index 8261787ab..5d0d055d2 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,7 @@ docs/docs/public/c-reference # NPM **/node_modules/**/* + +# Rust (CSS Parser) +src/css/target/ +src/include/css_parser.h diff --git a/Makefile b/Makefile index 137f736d9..bf2e90fbc 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,10 @@ prism_build = $(prism_path)/build prism_flags = -I$(prism_include) prism_ldflags = $(prism_build)/libprism.a +css_parser_dir = src/css +css_parser_lib = $(css_parser_dir)/target/release/libherb_css_parser.a +css_parser_flags = -I$(css_parser_dir) + # Enable strict warnings warning_flags = -Wall -Wextra -Werror -pedantic @@ -50,42 +54,44 @@ production_flags = $(warning_flags) -O3 -march=native -flto shared_library_flags = -fPIC # Default build mode (change this as needed) -flags = $(warning_flags) $(debug_flags) $(prism_flags) -std=c99 +flags = $(warning_flags) $(debug_flags) $(prism_flags) $(css_parser_flags) -std=c99 # Separate test compilation flags -test_flags = $(debug_flags) $(prism_flags) -std=gnu99 +test_flags = $(debug_flags) $(prism_flags) $(css_parser_flags) -std=gnu99 # Shared library build (if needed) -shared_flags = $(production_flags) $(shared_library_flags) $(prism_flags) +shared_flags = $(production_flags) $(shared_library_flags) $(prism_flags) $(css_parser_flags) ifeq ($(os),Linux) + css_parser_ldflags = $(css_parser_lib) -ldl -lpthread -lm test_cflags = $(test_flags) -I/usr/include/check - test_ldflags = -L/usr/lib/x86_64-linux-gnu -lcheck -lm -lsubunit $(prism_ldflags) + test_ldflags = -L/usr/lib/x86_64-linux-gnu -lcheck -lm -lsubunit $(prism_ldflags) $(css_parser_ldflags) cc = clang-21 clang_format = clang-format-21 clang_tidy = clang-tidy-21 endif ifeq ($(os),Darwin) + css_parser_ldflags = $(css_parser_lib) -lresolv -framework Security -framework CoreFoundation brew_prefix := $(shell brew --prefix check) test_cflags = $(test_flags) -I$(brew_prefix)/include - test_ldflags = -L$(brew_prefix)/lib -lcheck -lm $(prism_ldflags) + test_ldflags = -L$(brew_prefix)/lib -lcheck -lm $(prism_ldflags) $(css_parser_ldflags) llvm_path = $(shell brew --prefix llvm@21) cc = $(llvm_path)/bin/clang clang_format = $(llvm_path)/bin/clang-format clang_tidy = $(llvm_path)/bin/clang-tidy endif -all: templates prism $(exec) $(lib_name) $(static_lib_name) test wasm +all: templates prism css_parser $(exec) $(lib_name) $(static_lib_name) test wasm -$(exec): $(objects) - $(cc) $(objects) $(flags) $(ldflags) $(prism_ldflags) -o $(exec) +$(exec): $(objects) $(css_parser_lib) + $(cc) $(objects) $(flags) $(ldflags) $(prism_ldflags) $(css_parser_ldflags) -o $(exec) -$(lib_name): $(objects) - $(cc) -shared $(objects) $(shared_flags) $(ldflags) $(prism_ldflags) -o $(lib_name) +$(lib_name): $(objects) $(css_parser_lib) + $(cc) -shared $(objects) $(shared_flags) $(ldflags) $(prism_ldflags) $(css_parser_ldflags) -o $(lib_name) # cp $(lib_name) $(ruby_extension) -$(static_lib_name): $(objects) +$(static_lib_name): $(objects) $(css_parser_lib) ar rcs $(static_lib_name) $(objects) src/%.o: src/%.c templates @@ -102,6 +108,7 @@ clean: rm -rf $(objects) $(test_objects) $(extension_objects) lib/herb/*.bundle tmp rm -rf $(prism_path) rake prism:clean + cd $(css_parser_dir) && cargo clean bundle_install: bundle install @@ -113,6 +120,12 @@ prism: bundle_install cd $(prism_path) && ruby templates/template.rb && make static && cd - rake prism:vendor +css_parser: $(css_parser_lib) + +$(css_parser_lib): + cd $(css_parser_dir) && cargo build --release + cbindgen --config $(css_parser_dir)/cbindgen.toml --output src/include/css_parser.h $(css_parser_dir) + format: $(clang_format) -i $(project_and_extension_files) diff --git a/config.yml b/config.yml index 83b8e1d04..297a3d9e5 100644 --- a/config.yml +++ b/config.yml @@ -712,3 +712,35 @@ nodes: - name: statements type: array kind: Node + + - name: CSSDeclarationNode + fields: + - name: property + type: string + + - name: value + type: string + + - name: CSSRuleNode + fields: + - name: selector + type: string + + - name: declarations + type: array + kind: CSSDeclarationNode + + - name: CSSStyleNode + fields: + - name: content + type: string + + - name: rules + type: array + kind: CSSRuleNode + + - name: valid + type: boolean + + - name: parse_error + type: string diff --git a/ext/herb/extconf.rb b/ext/herb/extconf.rb index cb215e568..5be4b4e2a 100644 --- a/ext/herb/extconf.rb +++ b/ext/herb/extconf.rb @@ -10,10 +10,13 @@ include_path = File.expand_path("../../src/include", __dir__) prism_path = File.expand_path("../../vendor/prism", __dir__) +css_parser_path = File.expand_path("../../src/css", __dir__) prism_src_path = "#{prism_path}/src" prism_include_path = "#{prism_path}/include" +css_parser_lib = "#{css_parser_path}/target/release/libherb_css_parser.a" + $VPATH << "$(srcdir)/../../src" $VPATH << "$(srcdir)/../../src/util" $VPATH << prism_src_path @@ -23,9 +26,17 @@ $INCFLAGS << " -I#{include_path}" $INCFLAGS << " -I#{prism_src_path}" $INCFLAGS << " -I#{prism_src_path}/util" +$INCFLAGS << " -I#{css_parser_path}" $CFLAGS << " -DPRISM_EXPORT_SYMBOLS=static " +$LDFLAGS << " #{css_parser_lib}" +if RUBY_PLATFORM.match?(/darwin/) + $LDFLAGS << " -lresolv -framework Security -framework CoreFoundation" +elsif RUBY_PLATFORM.match?(/linux/) + $LDFLAGS << " -ldl -lpthread -lm" +end + herb_src_files = Dir.glob("#{$srcdir}/../../src/**/*.c").map { |file| file.delete_prefix("../../../../ext/herb/") }.sort prism_main_files = %w[ diff --git a/javascript/packages/formatter/src/format-printer.ts b/javascript/packages/formatter/src/format-printer.ts index 327fef82c..47fd02f21 100644 --- a/javascript/packages/formatter/src/format-printer.ts +++ b/javascript/packages/formatter/src/format-printer.ts @@ -91,6 +91,9 @@ import { ERBInNode, XMLDeclarationNode, CDATANode, + CSSStyleNode, + CSSRuleNode, + CSSDeclarationNode, Token } from "@herb-tools/core" @@ -1192,6 +1195,20 @@ export class FormatPrinter extends Printer { if (node.end_node) this.visit(node.end_node) } + visitCSSStyleNode(node: CSSStyleNode) { + if (node.content) { + this.push(node.content) + } + } + + visitCSSRuleNode(_node: CSSRuleNode) { + // CSS rules are contained within CSSStyleNode, not rendered separately + } + + visitCSSDeclarationNode(_node: CSSDeclarationNode) { + // CSS declarations are contained within CSSRuleNode, not rendered separately + } + // --- Element Formatting Analysis Helpers --- /** diff --git a/javascript/packages/printer/scripts/generate-node-tests.mjs b/javascript/packages/printer/scripts/generate-node-tests.mjs index f85e8a189..664a17648 100644 --- a/javascript/packages/printer/scripts/generate-node-tests.mjs +++ b/javascript/packages/printer/scripts/generate-node-tests.mjs @@ -52,6 +52,8 @@ function generateNodeTest(nodeInfo) { astType = astType.replace(/^HTML/, 'HTML_') } else if (astType.startsWith('ERB')) { astType = astType.replace(/^ERB/, 'ERB_') + } else if (astType.startsWith('CSS')) { + astType = astType.replace(/^CSS/, 'C_S_S_') } astType = astType @@ -148,6 +150,7 @@ async function main() { const kebabCase = nodeTypeName .replace(/^HTML/, 'html-') .replace(/^ERB/, 'erb-') + .replace(/^CSS/, 'css-') .replace(/([A-Z])/g, '-$1') .toLowerCase() .replace(/^-/, '') diff --git a/javascript/packages/printer/src/identity-printer.ts b/javascript/packages/printer/src/identity-printer.ts index ad592cc2d..c0ee60e77 100644 --- a/javascript/packages/printer/src/identity-printer.ts +++ b/javascript/packages/printer/src/identity-printer.ts @@ -354,6 +354,20 @@ export class IdentityPrinter extends Printer { } } + visitCSSStyleNode(node: Nodes.CSSStyleNode): void { + if (node.content) { + this.write(node.content) + } + } + + visitCSSRuleNode(node: Nodes.CSSRuleNode): void { + // CSS rules are contained within CSSStyleNode, not rendered separately + } + + visitCSSDeclarationNode(node: Nodes.CSSDeclarationNode): void { + // CSS declarations are contained within CSSRuleNode, not rendered separately + } + visitERBCaseMatchNode(node: Nodes.ERBCaseMatchNode): void { this.printERBNode(node) diff --git a/javascript/packages/printer/test/nodes/css-declaration-node.test.ts b/javascript/packages/printer/test/nodes/css-declaration-node.test.ts new file mode 100644 index 000000000..84c63f2d7 --- /dev/null +++ b/javascript/packages/printer/test/nodes/css-declaration-node.test.ts @@ -0,0 +1,36 @@ +import dedent from "dedent" +import { describe, test, beforeAll } from "vitest" + +import { Herb } from "@herb-tools/node-wasm" +import { CSSDeclarationNode } from "@herb-tools/core" + +import { expectNodeToPrint, expectPrintRoundTrip, createLocation } from "../helpers/printer-test-helpers.js" + +describe("CSSDeclarationNode Printing", () => { + beforeAll(async () => { + await Herb.load() + }) + + test("CSS declarations are not printed independently", () => { + const node = CSSDeclarationNode.from({ + type: "AST_CSS_DECLARATION_NODE", + location: createLocation(), + errors: [], + property: "color", + value: "red" + }) + + expectNodeToPrint(node, "") + }) + + test("CSS declarations are part of style tag content", () => { + expectPrintRoundTrip(dedent` + + `) + }) +}) diff --git a/javascript/packages/printer/test/nodes/css-rule-node.test.ts b/javascript/packages/printer/test/nodes/css-rule-node.test.ts new file mode 100644 index 000000000..e2582c1d5 --- /dev/null +++ b/javascript/packages/printer/test/nodes/css-rule-node.test.ts @@ -0,0 +1,35 @@ +import dedent from "dedent" +import { describe, test, beforeAll } from "vitest" + +import { Herb } from "@herb-tools/node-wasm" +import { CSSRuleNode } from "@herb-tools/core" + +import { expectNodeToPrint, expectPrintRoundTrip, createLocation } from "../helpers/printer-test-helpers.js" + +describe("CSSRuleNode Printing", () => { + beforeAll(async () => { + await Herb.load() + }) + + test("CSS rules are not printed independently", () => { + const node = CSSRuleNode.from({ + type: "AST_CSS_RULE_NODE", + location: createLocation(), + errors: [], + selector: "body", + declarations: [] + }) + + expectNodeToPrint(node, "") + }) + + test("CSS rules are part of style tag content", () => { + expectPrintRoundTrip(dedent` + + `) + }) +}) diff --git a/javascript/packages/printer/test/nodes/css-style-node.test.ts b/javascript/packages/printer/test/nodes/css-style-node.test.ts new file mode 100644 index 000000000..987e60134 --- /dev/null +++ b/javascript/packages/printer/test/nodes/css-style-node.test.ts @@ -0,0 +1,98 @@ +import dedent from "dedent" +import { describe, test, beforeAll } from "vitest" + +import { Herb } from "@herb-tools/node-wasm" +import { CSSStyleNode } from "@herb-tools/core" + +import { expectNodeToPrint, expectPrintRoundTrip, createLocation } from "../helpers/printer-test-helpers.js" + +describe("CSSStyleNode Printing", () => { + beforeAll(async () => { + await Herb.load() + }) + + test("can print from node with simple CSS", () => { + const cssContent = dedent` + body { + background: white; + color: black; + } + ` + + const node = CSSStyleNode.from({ + type: "AST_CSS_STYLE_NODE", + location: createLocation(), + errors: [], + content: cssContent, + rules: [], + valid: true, + parse_error: "" + }) + + expectNodeToPrint(node, cssContent) + }) + + test("can print from node with complex CSS", () => { + const cssContent = dedent` + .container { + display: flex; + gap: 1rem; + } + + @media (max-width: 768px) { + .container { + flex-direction: column; + } + } + ` + + const node = CSSStyleNode.from({ + type: "AST_CSS_STYLE_NODE", + location: createLocation(), + errors: [], + content: cssContent, + rules: [], + valid: true, + parse_error: "" + }) + + expectNodeToPrint(node, cssContent) + }) + + test("can print from source with simple style tag", () => { + expectPrintRoundTrip(dedent` + + `) + }) + + test("can print from source with media query", () => { + expectPrintRoundTrip(dedent` + + `) + }) + + test("can print from source with CSS custom properties", () => { + expectPrintRoundTrip(dedent` + + `) + }) + + test("can print empty style tag", () => { + expectPrintRoundTrip("") + }) +}) diff --git a/lib/herb/engine/compiler.rb b/lib/herb/engine/compiler.rb index 3ed9124a7..92a1fe266 100644 --- a/lib/herb/engine/compiler.rb +++ b/lib/herb/engine/compiler.rb @@ -137,6 +137,10 @@ def visit_whitespace_node(node) add_text(node.value.value) if node.value end + def visit_css_style_node(node) + add_text(node.content) + end + def visit_html_comment_node(node) add_text(node.comment_start.value) visit_all(node.children) diff --git a/sig/herb/ast/nodes.rbs b/sig/herb/ast/nodes.rbs index bc41595ad..9b95a1b70 100644 --- a/sig/herb/ast/nodes.rbs +++ b/sig/herb/ast/nodes.rbs @@ -966,5 +966,90 @@ module Herb # : (?Integer) -> String def tree_inspect: (?Integer) -> String end + + class CSSDeclarationNode < Node + attr_reader property: String + + attr_reader value: String + + # : (String, Location, Array[Herb::Errors::Error], String, String) -> void + def initialize: (String, Location, Array[Herb::Errors::Error], String, String) -> void + + # : () -> serialized_css_declaration_node + def to_hash: () -> serialized_css_declaration_node + + # : (Visitor) -> void + def accept: (Visitor) -> void + + # : () -> Array[Herb::AST::Node?] + def child_nodes: () -> Array[Herb::AST::Node?] + + # : () -> Array[Herb::AST::Node] + def compact_child_nodes: () -> Array[Herb::AST::Node] + + # : () -> String + def inspect: () -> String + + # : (?Integer) -> String + def tree_inspect: (?Integer) -> String + end + + class CSSRuleNode < Node + attr_reader selector: String + + attr_reader declarations: Array[Herb::AST::CSSDeclarationNode] + + # : (String, Location, Array[Herb::Errors::Error], String, Array[Herb::AST::CSSDeclarationNode]) -> void + def initialize: (String, Location, Array[Herb::Errors::Error], String, Array[Herb::AST::CSSDeclarationNode]) -> void + + # : () -> serialized_css_rule_node + def to_hash: () -> serialized_css_rule_node + + # : (Visitor) -> void + def accept: (Visitor) -> void + + # : () -> Array[Herb::AST::Node?] + def child_nodes: () -> Array[Herb::AST::Node?] + + # : () -> Array[Herb::AST::Node] + def compact_child_nodes: () -> Array[Herb::AST::Node] + + # : () -> String + def inspect: () -> String + + # : (?Integer) -> String + def tree_inspect: (?Integer) -> String + end + + class CSSStyleNode < Node + attr_reader content: String + + attr_reader rules: Array[Herb::AST::CSSRuleNode] + + attr_reader valid: bool + + attr_reader parse_error: String + + # : (String, Location, Array[Herb::Errors::Error], String, Array[Herb::AST::CSSRuleNode], bool, String) -> void + def initialize: (String, Location, Array[Herb::Errors::Error], String, Array[Herb::AST::CSSRuleNode], bool, String) -> void + + # : () -> serialized_css_style_node + def to_hash: () -> serialized_css_style_node + + # : (Visitor) -> void + def accept: (Visitor) -> void + + # : () -> Array[Herb::AST::Node?] + def child_nodes: () -> Array[Herb::AST::Node?] + + # : () -> Array[Herb::AST::Node] + def compact_child_nodes: () -> Array[Herb::AST::Node] + + # : () -> String + def inspect: () -> String + + # : (?Integer) -> String + def tree_inspect: (?Integer) -> String + end end end diff --git a/sig/herb/engine/compiler.rbs b/sig/herb/engine/compiler.rbs index 926dd9e3d..e839f1a1d 100644 --- a/sig/herb/engine/compiler.rbs +++ b/sig/herb/engine/compiler.rbs @@ -29,6 +29,8 @@ module Herb def visit_whitespace_node: (untyped node) -> untyped + def visit_css_style_node: (untyped node) -> untyped + def visit_html_comment_node: (untyped node) -> untyped def visit_html_doctype_node: (untyped node) -> untyped diff --git a/sig/herb/visitor.rbs b/sig/herb/visitor.rbs index 768f810b4..1d66f302f 100644 --- a/sig/herb/visitor.rbs +++ b/sig/herb/visitor.rbs @@ -105,5 +105,14 @@ module Herb # : (Herb::AST::ERBInNode) -> void def visit_erb_in_node: (Herb::AST::ERBInNode) -> void + + # : (Herb::AST::CSSDeclarationNode) -> void + def visit_css_declaration_node: (Herb::AST::CSSDeclarationNode) -> void + + # : (Herb::AST::CSSRuleNode) -> void + def visit_css_rule_node: (Herb::AST::CSSRuleNode) -> void + + # : (Herb::AST::CSSStyleNode) -> void + def visit_css_style_node: (Herb::AST::CSSStyleNode) -> void end end diff --git a/src/css/Cargo.lock b/src/css/Cargo.lock new file mode 100644 index 000000000..ad9697dd7 --- /dev/null +++ b/src/css/Cargo.lock @@ -0,0 +1,1289 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "base64-simd" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" +dependencies = [ + "simd-abstraction", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cbindgen" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.108", + "tempfile", + "toml", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "const-str" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21077772762a1002bb421c3af42ac1725fa56066bfc53d9a55bb79905df2aaf3" +dependencies = [ + "const-str-proc-macro", +] + +[[package]] +name = "const-str-proc-macro" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1e0fdd2e5d3041e530e1b21158aeeef8b5d0e306bc5c1e3d6cf0930d10e25a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "cssparser" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be934d936a0fbed5bcdc01042b770de1398bf79d0e192f49fa7faea0e99281e" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-color" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556c099a61d85989d7af52b692e35a8d68a57e7df8c6d07563dc0778b3960c9f" +dependencies = [ + "cssparser", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.108", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "data-url" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "herb_css_parser" +version = "0.7.5" +dependencies = [ + "cbindgen", + "lightningcss", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "lightningcss" +version = "1.0.0-alpha.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b407ca668368d1d5a86cea58ac82d9f9f9ca4bac1e9dce6f16f875f0f081a911" +dependencies = [ + "ahash 0.8.12", + "bitflags", + "const-str", + "cssparser", + "cssparser-color", + "dashmap", + "data-encoding", + "getrandom 0.3.4", + "indexmap", + "itertools", + "lazy_static", + "lightningcss-derive", + "parcel_selectors", + "parcel_sourcemap", + "pastey", + "pathdiff", + "rayon", + "serde", + "serde-content", + "smallvec", +] + +[[package]] +name = "lightningcss-derive" +version = "1.0.0-alpha.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "outref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" + +[[package]] +name = "parcel_selectors" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54fd03f1ad26cb6b3ec1b7414fa78a3bd639e7dbb421b1a60513c96ce886a196" +dependencies = [ + "bitflags", + "cssparser", + "log", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "parcel_sourcemap" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" +dependencies = [ + "base64-simd", + "data-url", + "rkyv", + "serde", + "serde_json", + "vlq", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-content" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "simd-abstraction" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" +dependencies = [ + "outref", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vlq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.108", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[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.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] diff --git a/src/css/Cargo.toml b/src/css/Cargo.toml new file mode 100644 index 000000000..62afba42b --- /dev/null +++ b/src/css/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "herb_css_parser" +version = "0.7.5" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +lightningcss = "1.0.0-alpha.59" + +[build-dependencies] +cbindgen = "0.27" diff --git a/src/css/build.rs b/src/css/build.rs new file mode 100644 index 000000000..4076048b7 --- /dev/null +++ b/src/css/build.rs @@ -0,0 +1,14 @@ +extern crate cbindgen; + +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_config(cbindgen::Config::from_file("cbindgen.toml").unwrap()) + .generate() + .expect("Unable to generate bindings") + .write_to_file("../include/css_parser.h"); +} diff --git a/src/css/cbindgen.toml b/src/css/cbindgen.toml new file mode 100644 index 000000000..459b576e2 --- /dev/null +++ b/src/css/cbindgen.toml @@ -0,0 +1,13 @@ +language = "C" +include_guard = "HERB_CSS_H" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +header = "/* Herb CSS Parser - Lightning CSS Integration */" +include_version = true +namespace = "herb" +cpp_compat = true + +[export] +include = ["CSSParseResult"] + +[export.rename] +"CSSParseResult" = "css_parse_result_T" diff --git a/src/css/src/lib.rs b/src/css/src/lib.rs new file mode 100644 index 000000000..871ce6dbf --- /dev/null +++ b/src/css/src/lib.rs @@ -0,0 +1,189 @@ +use lightningcss::stylesheet::{ParserOptions, StyleSheet}; +use lightningcss::rules::CssRule; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::ptr; + +#[repr(C)] +pub struct CSSDeclaration { + pub property: *mut c_char, + pub value: *mut c_char, +} + +#[repr(C)] +pub struct CSSRule { + pub selector: *mut c_char, + pub declarations: *mut *mut CSSDeclaration, + pub declaration_count: usize, +} + +#[repr(C)] +pub struct CSSParseResult { + pub success: bool, + pub error_message: *mut c_char, + pub rules: *mut *mut CSSRule, + pub rule_count: usize, +} + +#[no_mangle] +pub unsafe extern "C" fn herb_css_parse(css_input: *const c_char) -> *mut CSSParseResult { + if css_input.is_null() { + return create_error_result_ptr("Input CSS is null"); + } + + let c_str = unsafe { CStr::from_ptr(css_input) }; + let css_str = match c_str.to_str() { + Ok(s) => s, + Err(_) => { + return create_error_result_ptr("Invalid UTF-8 in CSS input"); + } + }; + + let result = Box::new(parse_css_to_rules(css_str)); + Box::into_raw(result) +} + +#[no_mangle] +pub unsafe extern "C" fn herb_css_validate(css_input: *const c_char) -> bool { + if css_input.is_null() { + return false; + } + + let c_str = unsafe { CStr::from_ptr(css_input) }; + let css_str = match c_str.to_str() { + Ok(s) => s, + Err(_) => return false, + }; + + StyleSheet::parse(css_str, ParserOptions::default()).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn herb_css_free_result(result: *mut CSSParseResult) { + if result.is_null() { + return; + } + + let result = unsafe { Box::from_raw(result) }; + + if !result.error_message.is_null() { + unsafe { + let _ = CString::from_raw(result.error_message); + } + } + + if !result.rules.is_null() { + unsafe { + for i in 0..result.rule_count { + let rule_ptr = *result.rules.offset(i as isize); + if !rule_ptr.is_null() { + let rule = Box::from_raw(rule_ptr); + if !rule.selector.is_null() { + let _ = CString::from_raw(rule.selector); + } + if !rule.declarations.is_null() { + for j in 0..rule.declaration_count { + let decl_ptr = *rule.declarations.offset(j as isize); + if !decl_ptr.is_null() { + let decl = Box::from_raw(decl_ptr); + if !decl.property.is_null() { + let _ = CString::from_raw(decl.property); + } + if !decl.value.is_null() { + let _ = CString::from_raw(decl.value); + } + } + } + let _ = Vec::from_raw_parts(rule.declarations, rule.declaration_count, rule.declaration_count); + } + } + } + let _ = Vec::from_raw_parts(result.rules, result.rule_count, result.rule_count); + } + } +} + +fn parse_css_to_rules(css_str: &str) -> CSSParseResult { + match StyleSheet::parse(css_str, ParserOptions::default()) { + Ok(stylesheet) => { + let mut rules_vec: Vec<*mut CSSRule> = Vec::new(); + + for rule in &stylesheet.rules.0 { + if let CssRule::Style(style_rule) = rule { + let selector_str = format!("{:?}", style_rule.selectors); + let selector = CString::new(selector_str.trim()).unwrap_or_else(|_| CString::new("").unwrap()); + + let mut decls_vec: Vec<*mut CSSDeclaration> = Vec::new(); + + for decl in &style_rule.declarations.declarations { + let prop_name = decl.property_id().name().to_string(); + let prop_value = format!("{:?}", decl); + + let property = CString::new(prop_name).unwrap_or_else(|_| CString::new("").unwrap()); + let value = CString::new(prop_value).unwrap_or_else(|_| CString::new("").unwrap()); + + let css_decl = Box::new(CSSDeclaration { + property: property.into_raw(), + value: value.into_raw(), + }); + + decls_vec.push(Box::into_raw(css_decl)); + } + + let decl_count = decls_vec.len(); + + let decls_ptr = if decl_count > 0 { + let mut boxed = decls_vec.into_boxed_slice(); + let ptr = boxed.as_mut_ptr(); + std::mem::forget(boxed); + ptr + } else { + ptr::null_mut() + }; + + let css_rule = Box::new(CSSRule { + selector: selector.into_raw(), + declarations: decls_ptr, + declaration_count: decl_count, + }); + + rules_vec.push(Box::into_raw(css_rule)); + } + } + + let rule_count = rules_vec.len(); + let rules_ptr = if rule_count > 0 { + let mut boxed = rules_vec.into_boxed_slice(); + let ptr = boxed.as_mut_ptr(); + std::mem::forget(boxed); + ptr + } else { + ptr::null_mut() + }; + + CSSParseResult { + success: true, + error_message: ptr::null_mut(), + rules: rules_ptr, + rule_count, + } + } + Err(e) => create_error_result(&format!("CSS parse error: {:?}", e)), + } +} + +fn create_error_result(error: &str) -> CSSParseResult { + let error_message = CString::new(error).unwrap_or_else(|_| CString::new("Unknown error").unwrap()); + + CSSParseResult { + success: false, + error_message: error_message.into_raw(), + rules: ptr::null_mut(), + rule_count: 0, + } +} + +fn create_error_result_ptr(error: &str) -> *mut CSSParseResult { + let result = Box::new(create_error_result(error)); + Box::into_raw(result) +} diff --git a/src/css_node_helpers.c b/src/css_node_helpers.c new file mode 100644 index 000000000..d6503d472 --- /dev/null +++ b/src/css_node_helpers.c @@ -0,0 +1,100 @@ +#include "include/css_node_helpers.h" +#include "include/ast_nodes.h" +#include "include/css_parser.h" +#include "include/util.h" +#include "include/util/hb_array.h" + +#include +#include + +AST_CSS_STYLE_NODE_T* create_css_style_node( + const char* css_content, + position_T start_position, + position_T end_position +) { + if (!css_content) { + return ast_css_style_node_init( + "", + hb_array_init(0), + false, + "No CSS content provided", + start_position, + end_position, + hb_array_init(0) + ); + } + + struct css_parse_result_T* result = herb_css_parse(css_content); + + if (!result) { + return ast_css_style_node_init( + css_content, + hb_array_init(0), + false, + "Failed to parse CSS", + start_position, + end_position, + hb_array_init(0) + ); + } + + AST_CSS_STYLE_NODE_T* node; + + if (result->success) { + hb_array_T* rules = hb_array_init(result->rule_count); + + for (size_t i = 0; i < result->rule_count; i++) { + struct CSSRule* css_rule = result->rules[i]; + + hb_array_T* declarations = hb_array_init(css_rule->declaration_count); + + for (size_t j = 0; j < css_rule->declaration_count; j++) { + struct CSSDeclaration* css_decl = css_rule->declarations[j]; + + AST_CSS_DECLARATION_NODE_T* decl_node = ast_css_declaration_node_init( + herb_strdup(css_decl->property), + herb_strdup(css_decl->value), + start_position, + end_position, + hb_array_init(0) + ); + + hb_array_append(declarations, decl_node); + } + + AST_CSS_RULE_NODE_T* rule_node = ast_css_rule_node_init( + herb_strdup(css_rule->selector), + declarations, + start_position, + end_position, + hb_array_init(0) + ); + + hb_array_append(rules, rule_node); + } + + node = ast_css_style_node_init( + herb_strdup(css_content), + rules, + true, + "", + start_position, + end_position, + hb_array_init(0) + ); + } else { + node = ast_css_style_node_init( + herb_strdup(css_content), + hb_array_init(0), + false, + result->error_message ? herb_strdup(result->error_message) : herb_strdup("Unknown CSS parse error"), + start_position, + end_position, + hb_array_init(0) + ); + } + + herb_css_free_result(result); + + return node; +} diff --git a/src/include/css_node_helpers.h b/src/include/css_node_helpers.h new file mode 100644 index 000000000..9063ab330 --- /dev/null +++ b/src/include/css_node_helpers.h @@ -0,0 +1,21 @@ +#ifndef HERB_CSS_NODE_HELPERS_H +#define HERB_CSS_NODE_HELPERS_H + +#include "ast_nodes.h" +#include "position.h" + +#ifdef __cplusplus +extern "C" { +#endif + +AST_CSS_STYLE_NODE_T* create_css_style_node( + const char* css_content, + position_T start_position, + position_T end_position +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/include/parser_helpers.h b/src/include/parser_helpers.h index b3ab98300..996dd0811 100644 --- a/src/include/parser_helpers.h +++ b/src/include/parser_helpers.h @@ -28,6 +28,13 @@ void parser_append_literal_node_from_buffer( position_T start ); +void parser_append_css_node_from_buffer( + const parser_T* parser, + hb_buffer_T* buffer, + hb_array_T* children, + position_T start +); + bool parser_in_svg_context(const parser_T* parser); foreign_content_type_T parser_get_foreign_content_type(hb_string_T tag_name); diff --git a/src/main.c b/src/main.c index 52b1f370c..3c8f6a522 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include "include/ast_node.h" #include "include/ast_nodes.h" #include "include/ast_pretty_print.h" +#include "include/css_parser.h" #include "include/extract.h" #include "include/herb.h" #include "include/io.h" @@ -11,6 +12,7 @@ #include "include/util/hb_buffer.h" #include +#include #include #include @@ -43,6 +45,7 @@ int main(const int argc, char* argv[]) { printf("./herb ruby [file] - Extract Ruby from a file\n"); printf("./herb html [file] - Extract HTML from a file\n"); printf("./herb prism [file] - Extract Ruby from a file and parse the Ruby source with Prism\n"); + printf("./herb css [file] - Parse CSS using Lightning CSS\n"); return 1; } @@ -153,6 +156,40 @@ int main(const int argc, char* argv[]) { return 0; } + if (strcmp(argv[1], "css") == 0) { + printf("CSS Input:\n%s\n\n", source); + + struct css_parse_result_T* result = herb_css_parse(source); + clock_gettime(CLOCK_MONOTONIC, &end); + + if (result->success) { + printf("CSS parsed successfully!\n"); + printf("Found %zu CSS rule(s):\n\n", result->rule_count); + + for (size_t i = 0; i < result->rule_count; i++) { + struct CSSRule* rule = result->rules[i]; + printf("Rule %zu:\n", i + 1); + printf(" Selector: %s\n", rule->selector); + printf(" Declarations (%zu):\n", rule->declaration_count); + + for (size_t j = 0; j < rule->declaration_count; j++) { + struct CSSDeclaration* decl = rule->declarations[j]; + printf(" %s: %s\n", decl->property, decl->value); + } + printf("\n"); + } + } else { + printf("CSS Parse Error:\n%s\n\n", result->error_message); + } + + print_time_diff(start, end, "parsing CSS"); + + herb_css_free_result(result); + free(source); + + return result->success ? 0 : 1; + } + printf("Unknown Command: %s\n", argv[1]); return 1; } diff --git a/src/parser.c b/src/parser.c index 3cc25791a..297d07212 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1021,6 +1021,7 @@ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, hb_buffer_init(&content, 1024); position_T start = parser->current_token->location.start; hb_string_T expected_closing_tag = parser_get_foreign_content_closing_tag(parser->foreign_content_type); + bool is_style_tag = (parser->foreign_content_type == FOREIGN_CONTENT_STYLE); if (hb_string_is_empty(expected_closing_tag)) { parser_exit_foreign_content(parser); @@ -1031,7 +1032,11 @@ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, while (!token_is(parser, TOKEN_EOF)) { if (token_is(parser, TOKEN_ERB_START)) { - parser_append_literal_node_from_buffer(parser, &content, children, start); + if (is_style_tag) { + parser_append_css_node_from_buffer(parser, &content, children, start); + } else { + parser_append_literal_node_from_buffer(parser, &content, children, start); + } AST_ERB_CONTENT_NODE_T* erb_node = parser_parse_erb_tag(parser); hb_array_append(children, erb_node); @@ -1057,7 +1062,12 @@ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, if (next_token) { token_free(next_token); } if (is_potential_match) { - parser_append_literal_node_from_buffer(parser, &content, children, start); + if (is_style_tag) { + parser_append_css_node_from_buffer(parser, &content, children, start); + } else { + parser_append_literal_node_from_buffer(parser, &content, children, start); + } + parser_exit_foreign_content(parser); free(content.value); @@ -1071,7 +1081,12 @@ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, token_free(token); } - parser_append_literal_node_from_buffer(parser, &content, children, start); + if (is_style_tag) { + parser_append_css_node_from_buffer(parser, &content, children, start); + } else { + parser_append_literal_node_from_buffer(parser, &content, children, start); + } + parser_exit_foreign_content(parser); free(content.value); } diff --git a/src/parser_helpers.c b/src/parser_helpers.c index f34d97864..2b6dbd950 100644 --- a/src/parser_helpers.c +++ b/src/parser_helpers.c @@ -1,6 +1,7 @@ #include "include/parser_helpers.h" #include "include/ast_node.h" #include "include/ast_nodes.h" +#include "include/css_node_helpers.h" #include "include/errors.h" #include "include/html_util.h" #include "include/lexer.h" @@ -138,6 +139,21 @@ void parser_append_literal_node_from_buffer( hb_buffer_clear(buffer); } +void parser_append_css_node_from_buffer( + const parser_T* parser, + hb_buffer_T* buffer, + hb_array_T* children, + position_T start +) { + if (hb_buffer_length(buffer) == 0) { return; } + + AST_CSS_STYLE_NODE_T* css_node = + create_css_style_node(hb_buffer_value(buffer), start, parser->current_token->location.start); + + if (children != NULL) { hb_array_append(children, css_node); } + hb_buffer_clear(buffer); +} + token_T* parser_advance(parser_T* parser) { token_T* token = parser->current_token; parser->current_token = lexer_next_token(parser->lexer); diff --git a/test/c/main.c b/test/c/main.c index ef4238806..e3d6422b4 100644 --- a/test/c/main.c +++ b/test/c/main.c @@ -11,6 +11,7 @@ TCase *io_tests(void); TCase *lex_tests(void); TCase *token_tests(void); TCase *util_tests(void); +TCase *css_tests(void); Suite *herb_suite(void) { Suite *suite = suite_create("Herb Suite"); @@ -25,6 +26,7 @@ Suite *herb_suite(void) { suite_add_tcase(suite, lex_tests()); suite_add_tcase(suite, token_tests()); suite_add_tcase(suite, util_tests()); + suite_add_tcase(suite, css_tests()); return suite; } diff --git a/test/c/test_css.c b/test/c/test_css.c new file mode 100644 index 000000000..7b1891e50 --- /dev/null +++ b/test/c/test_css.c @@ -0,0 +1,104 @@ +#include "../../src/include/css_parser.h" +#include "include/test.h" + +#include +#include +#include +#include + +TEST(test_css_parse_valid) + const char* css = "body { color: red; padding: 10px; }"; + struct css_parse_result_T* result = herb_css_parse(css); + + ck_assert_ptr_nonnull(result); + ck_assert(result->success); + ck_assert_ptr_null(result->error_message); + ck_assert_ptr_nonnull(result->rules); + ck_assert_uint_gt(result->rule_count, 0); + + ck_assert_uint_eq(result->rule_count, 1); + + struct CSSRule* rule = result->rules[0]; + ck_assert_ptr_nonnull(rule->selector); + + ck_assert_uint_gt(rule->declaration_count, 0); + + herb_css_free_result(result); +END + +TEST(test_css_parse_invalid) + const char* css = "body { @@@invalid }"; // Invalid CSS + struct css_parse_result_T* result = herb_css_parse(css); + + ck_assert_ptr_nonnull(result); + ck_assert(!result->success); + ck_assert_ptr_nonnull(result->error_message); + ck_assert_ptr_null(result->rules); + + herb_css_free_result(result); +END + +TEST(test_css_parse_declarations) + const char* css = "body {\n color: red;\n padding: 10px;\n}"; + struct css_parse_result_T* result = herb_css_parse(css); + + ck_assert_ptr_nonnull(result); + ck_assert(result->success); + ck_assert_ptr_nonnull(result->rules); + ck_assert_uint_eq(result->rule_count, 1); + + struct CSSRule* rule = result->rules[0]; + ck_assert_uint_eq(rule->declaration_count, 2); + + herb_css_free_result(result); +END + +TEST(test_css_validate_valid) + const char* css = "body { color: red; }"; + bool valid = herb_css_validate(css); + + ck_assert(valid); +END + +TEST(test_css_validate_invalid) + const char* css = "body { @@@invalid }"; + bool valid = herb_css_validate(css); + + ck_assert(!valid); +END + +TEST(test_css_parse_multiple_rules) + const char* css = "body { color: red; } p { margin: 5px; }"; + struct css_parse_result_T* result = herb_css_parse(css); + + ck_assert_ptr_nonnull(result); + ck_assert(result->success); + ck_assert_uint_eq(result->rule_count, 2); + + herb_css_free_result(result); +END + +TEST(test_css_parse_empty) + const char* css = ""; + struct css_parse_result_T* result = herb_css_parse(css); + + ck_assert_ptr_nonnull(result); + ck_assert(result->success); + ck_assert_uint_eq(result->rule_count, 0); + + herb_css_free_result(result); +END + +TCase* css_tests(void) { + TCase *css = tcase_create("CSS Parser (Lightning CSS)"); + + tcase_add_test(css, test_css_parse_valid); + tcase_add_test(css, test_css_parse_invalid); + tcase_add_test(css, test_css_parse_declarations); + tcase_add_test(css, test_css_parse_multiple_rules); + tcase_add_test(css, test_css_parse_empty); + tcase_add_test(css, test_css_validate_valid); + tcase_add_test(css, test_css_validate_invalid); + + return css; +} diff --git a/test/snapshots/parser/script_style_test/test_0013_style_tag_with_child_selector_3675980b06d0732a42d3e3d4da50428f.txt b/test/snapshots/parser/script_style_test/test_0013_style_tag_with_child_selector_3675980b06d0732a42d3e3d4da50428f.txt index a979c4e5f..4e2a43805 100644 --- a/test/snapshots/parser/script_style_test/test_0013_style_tag_with_child_selector_3675980b06d0732a42d3e3d4da50428f.txt +++ b/test/snapshots/parser/script_style_test/test_0013_style_tag_with_child_selector_3675980b06d0732a42d3e3d4da50428f.txt @@ -11,8 +11,19 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (1 item) - │ └── @ LiteralNode (location: (1:7)-(1:39)) - │ └── content: ".parent > .child { color: red; }" + │ └── @ CSSStyleNode (location: (1:7)-(1:39)) + │ ├── content: ".parent > .child { color: red; }" + │ ├── rules: (1 item) + │ │ └── @ CSSRuleNode (location: (1:7)-(1:39)) + │ │ ├── selector: "SelectorList([Selector(.parent > .child, specificity = 0x800)])" + │ │ └── declarations: (1 item) + │ │ └── @ CSSDeclarationNode (location: (1:7)-(1:39)) + │ │ ├── property: "color" + │ │ └── value: "Color(RGBA(RGBA { red: 255, green: 0, blue: 0, alpha: 255 }))" + │ │ + │ │ + │ ├── valid: true + │ └── parse_error: "" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:39)-(1:47)) diff --git a/test/snapshots/parser/script_style_test/test_0014_style_tag_with_attribute_selector_containing_HTML_6024cebc93b5103ba789a52ba13c2f01.txt b/test/snapshots/parser/script_style_test/test_0014_style_tag_with_attribute_selector_containing_HTML_6024cebc93b5103ba789a52ba13c2f01.txt index 2e48b66e5..b03caf942 100644 --- a/test/snapshots/parser/script_style_test/test_0014_style_tag_with_attribute_selector_containing_HTML_6024cebc93b5103ba789a52ba13c2f01.txt +++ b/test/snapshots/parser/script_style_test/test_0014_style_tag_with_attribute_selector_containing_HTML_6024cebc93b5103ba789a52ba13c2f01.txt @@ -11,8 +11,19 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (1 item) - │ └── @ LiteralNode (location: (1:7)-(1:57)) - │ └── content: "input[placeholder=\"\"] { color: blue; }" + │ └── @ CSSStyleNode (location: (1:7)-(1:57)) + │ ├── content: "input[placeholder=\"\"] { color: blue; }" + │ ├── rules: (1 item) + │ │ └── @ CSSRuleNode (location: (1:7)-(1:57)) + │ │ ├── selector: "SelectorList([Selector(input[placeholder=\"\"], specificity = 0x401)])" + │ │ └── declarations: (1 item) + │ │ └── @ CSSDeclarationNode (location: (1:7)-(1:57)) + │ │ ├── property: "color" + │ │ └── value: "Color(RGBA(RGBA { red: 0, green: 0, blue: 255, alpha: 255 }))" + │ │ + │ │ + │ ├── valid: true + │ └── parse_error: "" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:57)-(1:65)) diff --git a/test/snapshots/parser/script_style_test/test_0015_style_tag_with_content_property_containing_HTML_aa75bbfce10aaa99576a8041bc62e42a.txt b/test/snapshots/parser/script_style_test/test_0015_style_tag_with_content_property_containing_HTML_aa75bbfce10aaa99576a8041bc62e42a.txt index 6b8bcbe04..9bae5773a 100644 --- a/test/snapshots/parser/script_style_test/test_0015_style_tag_with_content_property_containing_HTML_aa75bbfce10aaa99576a8041bc62e42a.txt +++ b/test/snapshots/parser/script_style_test/test_0015_style_tag_with_content_property_containing_HTML_aa75bbfce10aaa99576a8041bc62e42a.txt @@ -11,8 +11,19 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (1 item) - │ └── @ LiteralNode (location: (1:7)-(1:51)) - │ └── content: ".icon::before { content: \"\"; }" + │ └── @ CSSStyleNode (location: (1:7)-(1:51)) + │ ├── content: ".icon::before { content: \"\"; }" + │ ├── rules: (1 item) + │ │ └── @ CSSRuleNode (location: (1:7)-(1:51)) + │ │ ├── selector: "SelectorList([Selector(.icon:before, specificity = 0x401)])" + │ │ └── declarations: (1 item) + │ │ └── @ CSSDeclarationNode (location: (1:7)-(1:51)) + │ │ ├── property: "content" + │ │ └── value: "Custom(CustomProperty { name: Unknown(Ident(\"content\")), value: TokenList([Token(String(\"\"))]) })" + │ │ + │ │ + │ ├── valid: true + │ └── parse_error: "" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:51)-(1:59)) diff --git a/test/snapshots/parser/script_style_test/test_0016_style_tag_with_ERB_interpolation_e7608ec944a2eed2de2082144b6ccbff.txt b/test/snapshots/parser/script_style_test/test_0016_style_tag_with_ERB_interpolation_e7608ec944a2eed2de2082144b6ccbff.txt index b7105dc8a..a6802964f 100644 --- a/test/snapshots/parser/script_style_test/test_0016_style_tag_with_ERB_interpolation_e7608ec944a2eed2de2082144b6ccbff.txt +++ b/test/snapshots/parser/script_style_test/test_0016_style_tag_with_ERB_interpolation_e7608ec944a2eed2de2082144b6ccbff.txt @@ -11,8 +11,11 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (5 items) - │ ├── @ LiteralNode (location: (1:7)-(1:13)) - │ │ └── content: ".user-" + │ ├── @ CSSStyleNode (location: (1:7)-(1:13)) + │ │ ├── content: ".user-" + │ │ ├── rules: [] + │ │ ├── valid: false + │ │ └── parse_error: "CSS parse error: Error { kind: EndOfInput, loc: Some(ErrorLocation { filename: \"\", line: 0, column: 7 }) }" │ │ │ ├── @ ERBContentNode (location: (1:13)-(1:28)) │ │ ├── tag_opening: "<%=" (location: (1:13)-(1:16)) @@ -21,8 +24,11 @@ │ │ ├── parsed: true │ │ └── valid: true │ │ - │ ├── @ LiteralNode (location: (1:28)-(1:38)) - │ │ └── content: " { color: " + │ ├── @ CSSStyleNode (location: (1:28)-(1:38)) + │ │ ├── content: " { color: " + │ │ ├── rules: [] + │ │ ├── valid: false + │ │ └── parse_error: "CSS parse error: Error { kind: SelectorError(EmptySelector), loc: Some(ErrorLocation { filename: \"\", line: 0, column: 2 }) }" │ │ │ ├── @ ERBContentNode (location: (1:38)-(1:57)) │ │ ├── tag_opening: "<%=" (location: (1:38)-(1:41)) @@ -31,8 +37,11 @@ │ │ ├── parsed: true │ │ └── valid: true │ │ - │ └── @ LiteralNode (location: (1:57)-(1:60)) - │ └── content: "; }" + │ └── @ CSSStyleNode (location: (1:57)-(1:60)) + │ ├── content: "; }" + │ ├── rules: [] + │ ├── valid: false + │ └── parse_error: "CSS parse error: Error { kind: EndOfInput, loc: Some(ErrorLocation { filename: \"\", line: 0, column: 4 }) }" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:60)-(1:68)) diff --git a/test/snapshots/parser/script_style_test/test_0017_style_tag_with_complex_CSS_including_HTML-like_content_2029b62a2d67c13cdb6a6109966d1952.txt b/test/snapshots/parser/script_style_test/test_0017_style_tag_with_complex_CSS_including_HTML-like_content_2029b62a2d67c13cdb6a6109966d1952.txt index b2aed1e72..c9c95e1a2 100644 --- a/test/snapshots/parser/script_style_test/test_0017_style_tag_with_complex_CSS_including_HTML-like_content_2029b62a2d67c13cdb6a6109966d1952.txt +++ b/test/snapshots/parser/script_style_test/test_0017_style_tag_with_complex_CSS_including_HTML-like_content_2029b62a2d67c13cdb6a6109966d1952.txt @@ -11,8 +11,27 @@ │ │ │ ├── tag_name: "style" (location: (1:1)-(1:6)) │ ├── body: (1 item) - │ │ └── @ LiteralNode (location: (1:7)-(9:0)) - │ │ └── content: "\n /*
Comment with HTML
*/\n .tooltip::after {\n content: \"
\";\n }\n .container > .item:first-child {\n background: url(\"data:image/svg+xml,\");\n }\n" + │ │ └── @ CSSStyleNode (location: (1:7)-(9:0)) + │ │ ├── content: "\n /*
Comment with HTML
*/\n .tooltip::after {\n content: \"
\";\n }\n .container > .item:first-child {\n background: url(\"data:image/svg+xml,\");\n }\n" + │ │ ├── rules: (2 items) + │ │ │ ├── @ CSSRuleNode (location: (1:7)-(9:0)) + │ │ │ │ ├── selector: "SelectorList([Selector(.tooltip:after, specificity = 0x401)])" + │ │ │ │ └── declarations: (1 item) + │ │ │ │ └── @ CSSDeclarationNode (location: (1:7)-(9:0)) + │ │ │ │ ├── property: "content" + │ │ │ │ └── value: "Custom(CustomProperty { name: Unknown(Ident(\"content\")), value: TokenList([Token(String(\"
\"))]) })" + │ │ │ │ + │ │ │ │ + │ │ │ └── @ CSSRuleNode (location: (1:7)-(9:0)) + │ │ │ ├── selector: "SelectorList([Selector(.container > .item:first-child, specificity = 0xc00)])" + │ │ │ └── declarations: (1 item) + │ │ │ └── @ CSSDeclarationNode (location: (1:7)-(9:0)) + │ │ │ ├── property: "background" + │ │ │ └── value: "Background([Background { image: Url(Url { url: \"data:image/svg+xml,\", loc: Location { line: 7, column: 17 } }), color: RGBA(RGBA { red: 0, green: 0, blue: 0, alpha: 0 }), position: BackgroundPosition { x: Length(Percentage(Percentage(0.0))), y: Length(Percentage(Percentage(0.0))) }, repeat: BackgroundRepeat { x: Repeat, y: Repeat }, size: Explicit { width: Auto, height: Auto }, attachment: Scroll, origin: PaddingBox, clip: BorderBox }])" + │ │ │ + │ │ │ + │ │ ├── valid: true + │ │ └── parse_error: "" │ │ │ ├── close_tag: │ │ └── @ HTMLCloseTagNode (location: (9:0)-(9:8)) diff --git a/test/snapshots/parser/script_style_test/test_0020_style_tag_with_media_query_and_nested_selectors_85e3c3a8a155fa6bba717f6f71bd20d9.txt b/test/snapshots/parser/script_style_test/test_0020_style_tag_with_media_query_and_nested_selectors_85e3c3a8a155fa6bba717f6f71bd20d9.txt index 70941b845..0412c6658 100644 --- a/test/snapshots/parser/script_style_test/test_0020_style_tag_with_media_query_and_nested_selectors_85e3c3a8a155fa6bba717f6f71bd20d9.txt +++ b/test/snapshots/parser/script_style_test/test_0020_style_tag_with_media_query_and_nested_selectors_85e3c3a8a155fa6bba717f6f71bd20d9.txt @@ -11,8 +11,11 @@ │ │ │ ├── tag_name: "style" (location: (1:1)-(1:6)) │ ├── body: (1 item) - │ │ └── @ LiteralNode (location: (1:7)-(7:0)) - │ │ └── content: "\n @media (max-width: 768px) {\n .nav > ul > li {\n display: block;\n }\n }\n" + │ │ └── @ CSSStyleNode (location: (1:7)-(7:0)) + │ │ ├── content: "\n @media (max-width: 768px) {\n .nav > ul > li {\n display: block;\n }\n }\n" + │ │ ├── rules: [] + │ │ ├── valid: true + │ │ └── parse_error: "" │ │ │ ├── close_tag: │ │ └── @ HTMLCloseTagNode (location: (7:0)-(7:8)) diff --git a/test/snapshots/parser/script_style_test/test_0024_script_tag_followed_by_style_tag_with_complex_content_d81018eb4e7c7c6fc05c8b35a8cfd494.txt b/test/snapshots/parser/script_style_test/test_0024_script_tag_followed_by_style_tag_with_complex_content_d81018eb4e7c7c6fc05c8b35a8cfd494.txt index 58162c8ce..3058d83fd 100644 --- a/test/snapshots/parser/script_style_test/test_0024_script_tag_followed_by_style_tag_with_complex_content_d81018eb4e7c7c6fc05c8b35a8cfd494.txt +++ b/test/snapshots/parser/script_style_test/test_0024_script_tag_followed_by_style_tag_with_complex_content_d81018eb4e7c7c6fc05c8b35a8cfd494.txt @@ -38,8 +38,19 @@ │ │ │ ├── tag_name: "style" (location: (6:1)-(6:6)) │ ├── body: (1 item) - │ │ └── @ LiteralNode (location: (6:7)-(10:0)) - │ │ └── content: "\n .mobile > .header {\n font-size: 14px;\n }\n" + │ │ └── @ CSSStyleNode (location: (6:7)-(10:0)) + │ │ ├── content: "\n .mobile > .header {\n font-size: 14px;\n }\n" + │ │ ├── rules: (1 item) + │ │ │ └── @ CSSRuleNode (location: (6:7)-(10:0)) + │ │ │ ├── selector: "SelectorList([Selector(.mobile > .header, specificity = 0x800)])" + │ │ │ └── declarations: (1 item) + │ │ │ └── @ CSSDeclarationNode (location: (6:7)-(10:0)) + │ │ │ ├── property: "font-size" + │ │ │ └── value: "FontSize(Length(Dimension(Px(14.0))))" + │ │ │ + │ │ │ + │ │ ├── valid: true + │ │ └── parse_error: "" │ │ │ ├── close_tag: │ │ └── @ HTMLCloseTagNode (location: (10:0)-(10:8)) diff --git a/test/snapshots/parser/script_style_test/test_0027_style_tag_with_CSS_custom_properties_and_calc_5b9a8e819022c7cd704610880a30afd3.txt b/test/snapshots/parser/script_style_test/test_0027_style_tag_with_CSS_custom_properties_and_calc_5b9a8e819022c7cd704610880a30afd3.txt index 7aee9ffab..685ff83b0 100644 --- a/test/snapshots/parser/script_style_test/test_0027_style_tag_with_CSS_custom_properties_and_calc_5b9a8e819022c7cd704610880a30afd3.txt +++ b/test/snapshots/parser/script_style_test/test_0027_style_tag_with_CSS_custom_properties_and_calc_5b9a8e819022c7cd704610880a30afd3.txt @@ -11,8 +11,31 @@ │ │ │ ├── tag_name: "style" (location: (1:1)-(1:6)) │ ├── body: (1 item) - │ │ └── @ LiteralNode (location: (1:7)-(9:0)) - │ │ └── content: "\n :root {\n --arrow: \"<\";\n --content: \">\";\n }\n .element {\n width: calc(100% - 20px);\n }\n" + │ │ └── @ CSSStyleNode (location: (1:7)-(9:0)) + │ │ ├── content: "\n :root {\n --arrow: \"<\";\n --content: \">\";\n }\n .element {\n width: calc(100% - 20px);\n }\n" + │ │ ├── rules: (2 items) + │ │ │ ├── @ CSSRuleNode (location: (1:7)-(9:0)) + │ │ │ │ ├── selector: "SelectorList([Selector(:root, specificity = 0x400)])" + │ │ │ │ └── declarations: (2 items) + │ │ │ │ ├── @ CSSDeclarationNode (location: (1:7)-(9:0)) + │ │ │ │ │ ├── property: "--arrow" + │ │ │ │ │ └── value: "Custom(CustomProperty { name: Custom(DashedIdent(\"--arrow\")), value: TokenList([Token(String(\"<\"))]) })" + │ │ │ │ │ + │ │ │ │ └── @ CSSDeclarationNode (location: (1:7)-(9:0)) + │ │ │ │ ├── property: "--content" + │ │ │ │ └── value: "Custom(CustomProperty { name: Custom(DashedIdent(\"--content\")), value: TokenList([Token(String(\">\"))]) })" + │ │ │ │ + │ │ │ │ + │ │ │ └── @ CSSRuleNode (location: (1:7)-(9:0)) + │ │ │ ├── selector: "SelectorList([Selector(.element, specificity = 0x400)])" + │ │ │ └── declarations: (1 item) + │ │ │ └── @ CSSDeclarationNode (location: (1:7)-(9:0)) + │ │ │ ├── property: "width" + │ │ │ └── value: "Width(LengthPercentage(Calc(Function(Calc(Sum(Value(Percentage(Percentage(1.0))), Value(Dimension(Px(-20.0)))))))))" + │ │ │ + │ │ │ + │ │ ├── valid: true + │ │ └── parse_error: "" │ │ │ ├── close_tag: │ │ └── @ HTMLCloseTagNode (location: (9:0)-(9:8)) diff --git a/test/snapshots/parser/script_style_test/test_0036_style_tag_with_quotes_containing_HTML-like_content_e270b9ac006614d91dd6f0ac8fa94fa0.txt b/test/snapshots/parser/script_style_test/test_0036_style_tag_with_quotes_containing_HTML-like_content_e270b9ac006614d91dd6f0ac8fa94fa0.txt index ec5e6efcc..192ddf3bf 100644 --- a/test/snapshots/parser/script_style_test/test_0036_style_tag_with_quotes_containing_HTML-like_content_e270b9ac006614d91dd6f0ac8fa94fa0.txt +++ b/test/snapshots/parser/script_style_test/test_0036_style_tag_with_quotes_containing_HTML-like_content_e270b9ac006614d91dd6f0ac8fa94fa0.txt @@ -11,8 +11,19 @@ │ │ │ ├── tag_name: "style" (location: (1:1)-(1:6)) │ ├── body: (1 item) - │ │ └── @ LiteralNode (location: (1:7)-(1:33)) - │ │ └── content: ".icon::before { content: \"" + │ │ └── @ CSSStyleNode (location: (1:7)-(1:33)) + │ │ ├── content: ".icon::before { content: \"" + │ │ ├── rules: (1 item) + │ │ │ └── @ CSSRuleNode (location: (1:7)-(1:33)) + │ │ │ ├── selector: "SelectorList([Selector(.icon:before, specificity = 0x401)])" + │ │ │ └── declarations: (1 item) + │ │ │ └── @ CSSDeclarationNode (location: (1:7)-(1:33)) + │ │ │ ├── property: "content" + │ │ │ └── value: "Custom(CustomProperty { name: Unknown(Ident(\"content\")), value: TokenList([Token(String(\"\"))]) })" + │ │ │ + │ │ │ + │ │ ├── valid: true + │ │ └── parse_error: "" │ │ │ ├── close_tag: │ │ └── @ HTMLCloseTagNode (location: (1:33)-(1:41)) diff --git a/test/snapshots/parser/script_style_test/test_0045_style_tag_with_ERB_in_CSS_strings_a74d4a4f4488689aceb2070a2901e6d2.txt b/test/snapshots/parser/script_style_test/test_0045_style_tag_with_ERB_in_CSS_strings_a74d4a4f4488689aceb2070a2901e6d2.txt index d6bbfa970..74fd32efd 100644 --- a/test/snapshots/parser/script_style_test/test_0045_style_tag_with_ERB_in_CSS_strings_a74d4a4f4488689aceb2070a2901e6d2.txt +++ b/test/snapshots/parser/script_style_test/test_0045_style_tag_with_ERB_in_CSS_strings_a74d4a4f4488689aceb2070a2901e6d2.txt @@ -11,8 +11,19 @@ │ │ │ ├── tag_name: "style" (location: (1:1)-(1:6)) │ ├── body: (5 items) - │ │ ├── @ LiteralNode (location: (1:7)-(3:25)) - │ │ │ └── content: "\n .dynamic::before {\n content: \"Generated: " + │ │ ├── @ CSSStyleNode (location: (1:7)-(3:25)) + │ │ │ ├── content: "\n .dynamic::before {\n content: \"Generated: " + │ │ │ ├── rules: (1 item) + │ │ │ │ └── @ CSSRuleNode (location: (1:7)-(3:25)) + │ │ │ │ ├── selector: "SelectorList([Selector(.dynamic:before, specificity = 0x401)])" + │ │ │ │ └── declarations: (1 item) + │ │ │ │ └── @ CSSDeclarationNode (location: (1:7)-(3:25)) + │ │ │ │ ├── property: "content" + │ │ │ │ └── value: "Custom(CustomProperty { name: Unknown(Ident(\"content\")), value: TokenList([Token(String(\"Generated: \"))]) })" + │ │ │ │ + │ │ │ │ + │ │ │ ├── valid: true + │ │ │ └── parse_error: "" │ │ │ │ │ ├── @ ERBContentNode (location: (3:25)-(3:42)) │ │ │ ├── tag_opening: "<%=" (location: (3:25)-(3:28)) @@ -21,8 +32,11 @@ │ │ │ ├── parsed: true │ │ │ └── valid: true │ │ │ - │ │ ├── @ LiteralNode (location: (3:42)-(4:21)) - │ │ │ └── content: "\";\n background: url('" + │ │ ├── @ CSSStyleNode (location: (3:42)-(4:21)) + │ │ │ ├── content: "\";\n background: url('" + │ │ │ ├── rules: [] + │ │ │ ├── valid: false + │ │ │ └── parse_error: "CSS parse error: Error { kind: EndOfInput, loc: Some(ErrorLocation { filename: \"\", line: 1, column: 22 }) }" │ │ │ │ │ ├── @ ERBContentNode (location: (4:21)-(4:39)) │ │ │ ├── tag_opening: "<%=" (location: (4:21)-(4:24)) @@ -31,8 +45,11 @@ │ │ │ ├── parsed: true │ │ │ └── valid: true │ │ │ - │ │ └── @ LiteralNode (location: (4:39)-(6:0)) - │ │ └── content: "');\n }\n" + │ │ └── @ CSSStyleNode (location: (4:39)-(6:0)) + │ │ ├── content: "');\n }\n" + │ │ ├── rules: [] + │ │ ├── valid: false + │ │ └── parse_error: "CSS parse error: Error { kind: EndOfInput, loc: Some(ErrorLocation { filename: \"\", line: 2, column: 1 }) }" │ │ │ ├── close_tag: │ │ └── @ HTMLCloseTagNode (location: (6:0)-(6:8)) diff --git a/test/snapshots/parser/tags_test/test_0034_style_tag_with_nested_div_and_CSS_selectors_78cfabdfd368bd486b01a414bd09cead.txt b/test/snapshots/parser/tags_test/test_0034_style_tag_with_nested_div_and_CSS_selectors_78cfabdfd368bd486b01a414bd09cead.txt index c2ddc704e..e1807e9b5 100644 --- a/test/snapshots/parser/tags_test/test_0034_style_tag_with_nested_div_and_CSS_selectors_78cfabdfd368bd486b01a414bd09cead.txt +++ b/test/snapshots/parser/tags_test/test_0034_style_tag_with_nested_div_and_CSS_selectors_78cfabdfd368bd486b01a414bd09cead.txt @@ -11,8 +11,11 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (1 item) - │ └── @ LiteralNode (location: (1:7)-(1:40)) - │ └── content: "
.class { color: red; }
" + │ └── @ CSSStyleNode (location: (1:7)-(1:40)) + │ ├── content: "
.class { color: red; }
" + │ ├── rules: [] + │ ├── valid: false + │ └── parse_error: "CSS parse error: Error { kind: SelectorError(EmptySelector), loc: Some(ErrorLocation { filename: \"\", line: 0, column: 1 }) }" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:40)-(1:48)) diff --git a/test/snapshots/parser/tags_test/test_0035_style_tag_with_CSS_greater_than_selector_aeb1512ea553ab369a3129d25e6677b6.txt b/test/snapshots/parser/tags_test/test_0035_style_tag_with_CSS_greater_than_selector_aeb1512ea553ab369a3129d25e6677b6.txt index 9d351cb89..ad3db7aad 100644 --- a/test/snapshots/parser/tags_test/test_0035_style_tag_with_CSS_greater_than_selector_aeb1512ea553ab369a3129d25e6677b6.txt +++ b/test/snapshots/parser/tags_test/test_0035_style_tag_with_CSS_greater_than_selector_aeb1512ea553ab369a3129d25e6677b6.txt @@ -11,8 +11,19 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (1 item) - │ └── @ LiteralNode (location: (1:7)-(1:38)) - │ └── content: ".parent > .child { margin: 0; }" + │ └── @ CSSStyleNode (location: (1:7)-(1:38)) + │ ├── content: ".parent > .child { margin: 0; }" + │ ├── rules: (1 item) + │ │ └── @ CSSRuleNode (location: (1:7)-(1:38)) + │ │ ├── selector: "SelectorList([Selector(.parent > .child, specificity = 0x800)])" + │ │ └── declarations: (1 item) + │ │ └── @ CSSDeclarationNode (location: (1:7)-(1:38)) + │ │ ├── property: "margin" + │ │ └── value: "Margin(Margin { top: LengthPercentage(Dimension(Px(0.0))), right: LengthPercentage(Dimension(Px(0.0))), bottom: LengthPercentage(Dimension(Px(0.0))), left: LengthPercentage(Dimension(Px(0.0))) })" + │ │ + │ │ + │ ├── valid: true + │ └── parse_error: "" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:38)-(1:46)) diff --git a/test/snapshots/parser/tags_test/test_0036_style_tag_with_CSS_attribute_selectors_containing_HTML-like_content_6024cebc93b5103ba789a52ba13c2f01.txt b/test/snapshots/parser/tags_test/test_0036_style_tag_with_CSS_attribute_selectors_containing_HTML-like_content_6024cebc93b5103ba789a52ba13c2f01.txt index 2e48b66e5..b03caf942 100644 --- a/test/snapshots/parser/tags_test/test_0036_style_tag_with_CSS_attribute_selectors_containing_HTML-like_content_6024cebc93b5103ba789a52ba13c2f01.txt +++ b/test/snapshots/parser/tags_test/test_0036_style_tag_with_CSS_attribute_selectors_containing_HTML-like_content_6024cebc93b5103ba789a52ba13c2f01.txt @@ -11,8 +11,19 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (1 item) - │ └── @ LiteralNode (location: (1:7)-(1:57)) - │ └── content: "input[placeholder=\"\"] { color: blue; }" + │ └── @ CSSStyleNode (location: (1:7)-(1:57)) + │ ├── content: "input[placeholder=\"\"] { color: blue; }" + │ ├── rules: (1 item) + │ │ └── @ CSSRuleNode (location: (1:7)-(1:57)) + │ │ ├── selector: "SelectorList([Selector(input[placeholder=\"\"], specificity = 0x401)])" + │ │ └── declarations: (1 item) + │ │ └── @ CSSDeclarationNode (location: (1:7)-(1:57)) + │ │ ├── property: "color" + │ │ └── value: "Color(RGBA(RGBA { red: 0, green: 0, blue: 255, alpha: 255 }))" + │ │ + │ │ + │ ├── valid: true + │ └── parse_error: "" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:57)-(1:65)) diff --git a/test/snapshots/parser/tags_test/test_0037_style_tag_with_CSS_content_property_containing_HTML_818730ef059d97de51c35b83d28174da.txt b/test/snapshots/parser/tags_test/test_0037_style_tag_with_CSS_content_property_containing_HTML_818730ef059d97de51c35b83d28174da.txt index 64be156a9..da1f0f1ba 100644 --- a/test/snapshots/parser/tags_test/test_0037_style_tag_with_CSS_content_property_containing_HTML_818730ef059d97de51c35b83d28174da.txt +++ b/test/snapshots/parser/tags_test/test_0037_style_tag_with_CSS_content_property_containing_HTML_818730ef059d97de51c35b83d28174da.txt @@ -11,8 +11,19 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (1 item) - │ └── @ LiteralNode (location: (1:7)-(1:60)) - │ └── content: ".element::before { content: \"
Generated
\"; }" + │ └── @ CSSStyleNode (location: (1:7)-(1:60)) + │ ├── content: ".element::before { content: \"
Generated
\"; }" + │ ├── rules: (1 item) + │ │ └── @ CSSRuleNode (location: (1:7)-(1:60)) + │ │ ├── selector: "SelectorList([Selector(.element:before, specificity = 0x401)])" + │ │ └── declarations: (1 item) + │ │ └── @ CSSDeclarationNode (location: (1:7)-(1:60)) + │ │ ├── property: "content" + │ │ └── value: "Custom(CustomProperty { name: Unknown(Ident(\"content\")), value: TokenList([Token(String(\"
Generated
\"))]) })" + │ │ + │ │ + │ ├── valid: true + │ └── parse_error: "" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:60)-(1:68)) diff --git a/test/snapshots/parser/tags_test/test_0038_style_tag_with_media_queries_and_nested_rules_d3ae213f84137865eff551bb3d76bd9d.txt b/test/snapshots/parser/tags_test/test_0038_style_tag_with_media_queries_and_nested_rules_d3ae213f84137865eff551bb3d76bd9d.txt index d0bd6586a..423971807 100644 --- a/test/snapshots/parser/tags_test/test_0038_style_tag_with_media_queries_and_nested_rules_d3ae213f84137865eff551bb3d76bd9d.txt +++ b/test/snapshots/parser/tags_test/test_0038_style_tag_with_media_queries_and_nested_rules_d3ae213f84137865eff551bb3d76bd9d.txt @@ -11,8 +11,11 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (1 item) - │ └── @ LiteralNode (location: (1:7)-(1:72)) - │ └── content: "@media (max-width: 768px) { .class > .nested { display: none; } }" + │ └── @ CSSStyleNode (location: (1:7)-(1:72)) + │ ├── content: "@media (max-width: 768px) { .class > .nested { display: none; } }" + │ ├── rules: [] + │ ├── valid: true + │ └── parse_error: "" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:72)-(1:80)) diff --git a/test/snapshots/parser/tags_test/test_0040_style_tag_with_ERB_interpolation_bba0dcc89d67e474bb211be3f0d2409c.txt b/test/snapshots/parser/tags_test/test_0040_style_tag_with_ERB_interpolation_bba0dcc89d67e474bb211be3f0d2409c.txt index c8eebaa24..c22666c93 100644 --- a/test/snapshots/parser/tags_test/test_0040_style_tag_with_ERB_interpolation_bba0dcc89d67e474bb211be3f0d2409c.txt +++ b/test/snapshots/parser/tags_test/test_0040_style_tag_with_ERB_interpolation_bba0dcc89d67e474bb211be3f0d2409c.txt @@ -11,8 +11,11 @@ │ ├── tag_name: "style" (location: (1:1)-(1:6)) ├── body: (5 items) - │ ├── @ LiteralNode (location: (1:7)-(1:13)) - │ │ └── content: ".user-" + │ ├── @ CSSStyleNode (location: (1:7)-(1:13)) + │ │ ├── content: ".user-" + │ │ ├── rules: [] + │ │ ├── valid: false + │ │ └── parse_error: "CSS parse error: Error { kind: EndOfInput, loc: Some(ErrorLocation { filename: \"\", line: 0, column: 7 }) }" │ │ │ ├── @ ERBContentNode (location: (1:13)-(1:27)) │ │ ├── tag_opening: "<%=" (location: (1:13)-(1:16)) @@ -21,8 +24,11 @@ │ │ ├── parsed: true │ │ └── valid: true │ │ - │ ├── @ LiteralNode (location: (1:27)-(1:48)) - │ │ └── content: " > .content { color: " + │ ├── @ CSSStyleNode (location: (1:27)-(1:48)) + │ │ ├── content: " > .content { color: " + │ │ ├── rules: [] + │ │ ├── valid: false + │ │ └── parse_error: "CSS parse error: Error { kind: SelectorError(EmptySelector), loc: Some(ErrorLocation { filename: \"\", line: 0, column: 2 }) }" │ │ │ ├── @ ERBContentNode (location: (1:48)-(1:66)) │ │ ├── tag_opening: "<%=" (location: (1:48)-(1:51)) @@ -31,8 +37,11 @@ │ │ ├── parsed: true │ │ └── valid: true │ │ - │ └── @ LiteralNode (location: (1:66)-(1:69)) - │ └── content: "; }" + │ └── @ CSSStyleNode (location: (1:66)-(1:69)) + │ ├── content: "; }" + │ ├── rules: [] + │ ├── valid: false + │ └── parse_error: "CSS parse error: Error { kind: EndOfInput, loc: Some(ErrorLocation { filename: \"\", line: 0, column: 4 }) }" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:69)-(1:77)) diff --git a/test/snapshots/parser/tags_test/test_0046_style_tag_with_complex_CSS_containing_HTML-like_selectors_72f0d1b607c34d65ded310921c9faed9.txt b/test/snapshots/parser/tags_test/test_0046_style_tag_with_complex_CSS_containing_HTML-like_selectors_72f0d1b607c34d65ded310921c9faed9.txt index 2d3ee73d3..3849a661a 100644 --- a/test/snapshots/parser/tags_test/test_0046_style_tag_with_complex_CSS_containing_HTML-like_selectors_72f0d1b607c34d65ded310921c9faed9.txt +++ b/test/snapshots/parser/tags_test/test_0046_style_tag_with_complex_CSS_containing_HTML-like_selectors_72f0d1b607c34d65ded310921c9faed9.txt @@ -14,8 +14,27 @@ │ │ │ ├── tag_name: "style" (location: (1:9)-(1:14)) │ ├── body: (1 item) - │ │ └── @ LiteralNode (location: (1:15)-(11:8)) - │ │ └── content: "\n /* CSS comment */\n\n .component > .header {\n content: \"\";\n }\n\n .component[data-type=\"\"] {\n background: url(\"data:image/svg+xml,\");\n }\n " + │ │ └── @ CSSStyleNode (location: (1:15)-(11:8)) + │ │ ├── content: "\n /* CSS comment */\n\n .component > .header {\n content: \"\";\n }\n\n .component[data-type=\"\"] {\n background: url(\"data:image/svg+xml,\");\n }\n " + │ │ ├── rules: (2 items) + │ │ │ ├── @ CSSRuleNode (location: (1:15)-(11:8)) + │ │ │ │ ├── selector: "SelectorList([Selector(.component > .header, specificity = 0x800)])" + │ │ │ │ └── declarations: (1 item) + │ │ │ │ └── @ CSSDeclarationNode (location: (1:15)-(11:8)) + │ │ │ │ ├── property: "content" + │ │ │ │ └── value: "Custom(CustomProperty { name: Unknown(Ident(\"content\")), value: TokenList([Token(String(\"\"))]) })" + │ │ │ │ + │ │ │ │ + │ │ │ └── @ CSSRuleNode (location: (1:15)-(11:8)) + │ │ │ ├── selector: "SelectorList([Selector(.component[data-type=\"\"], specificity = 0x800)])" + │ │ │ └── declarations: (1 item) + │ │ │ └── @ CSSDeclarationNode (location: (1:15)-(11:8)) + │ │ │ ├── property: "background" + │ │ │ └── value: "Background([Background { image: Url(Url { url: \"data:image/svg+xml,\", loc: Location { line: 9, column: 25 } }), color: RGBA(RGBA { red: 0, green: 0, blue: 0, alpha: 0 }), position: BackgroundPosition { x: Length(Percentage(Percentage(0.0))), y: Length(Percentage(Percentage(0.0))) }, repeat: BackgroundRepeat { x: Repeat, y: Repeat }, size: Explicit { width: Auto, height: Auto }, attachment: Scroll, origin: PaddingBox, clip: BorderBox }])" + │ │ │ + │ │ │ + │ │ ├── valid: true + │ │ └── parse_error: "" │ │ │ ├── close_tag: │ │ └── @ HTMLCloseTagNode (location: (11:8)-(11:16)) diff --git a/wasm/Makefile b/wasm/Makefile index c3720907c..acb3bbdb4 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -14,6 +14,9 @@ PRISM_MAIN_SOURCES = $(wildcard $(PRISM_PATH)/src/*.c) PRISM_UTIL_SOURCES = $(wildcard $(PRISM_PATH)/src/util/*.c) ALL_C_SOURCES = $(C_SOURCES) $(PRISM_MAIN_SOURCES) $(PRISM_UTIL_SOURCES) +CSS_PARSER_DIR = ../src/css +CSS_PARSER_LIB = $(CSS_PARSER_DIR)/target/wasm32-unknown-emscripten/release/libherb_css_parser.a + OBJ_DIR = obj C_OBJECTS = $(patsubst ../src/%.c,$(OBJ_DIR)/herb/%.o,$(C_SOURCES)) CPP_OBJECTS = $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(CPP_SOURCES)) @@ -27,7 +30,9 @@ PRISM_INCLUDE = $(PRISM_PATH)/include PRISM_SRC = $(PRISM_PATH)/src PRISM_UTIL = $(PRISM_PATH)/src/util -CFLAGS = -I$(INCLUDE_DIR) -I$(PRISM_INCLUDE) -I$(PRISM_SRC) -I$(PRISM_UTIL) -DPRISM_STATIC=1 -DPRISM_EXPORT_SYMBOLS=static +CSS_PARSER_INCLUDE = $(CSS_PARSER_DIR) + +CFLAGS = -I$(INCLUDE_DIR) -I$(PRISM_INCLUDE) -I$(PRISM_SRC) -I$(PRISM_UTIL) -I$(CSS_PARSER_INCLUDE) -DPRISM_STATIC=1 -DPRISM_EXPORT_SYMBOLS=static WASM_FLAGS = -s WASM=1 \ -s SINGLE_FILE=1 \ -s EXPORT_ES6=1 \ @@ -37,7 +42,11 @@ WASM_FLAGS = -s WASM=1 \ -s ERROR_ON_UNDEFINED_SYMBOLS=0 \ --bind -all: $(BROWSER_WASM_OUTPUT) $(NODE_WASM_OUTPUT) +all: css_parser $(BROWSER_WASM_OUTPUT) $(NODE_WASM_OUTPUT) + +.PHONY: css_parser +css_parser: + cd $(CSS_PARSER_DIR) && cargo build --release --target wasm32-unknown-emscripten $(OBJ_DIR): mkdir -p $(OBJ_DIR)/herb @@ -59,13 +68,13 @@ $(OBJ_DIR)/prism/util/%.o: $(PRISM_PATH)/src/util/%.c | $(OBJ_DIR) @mkdir -p $(@D) emcc -c $< -o $@ $(CFLAGS) -$(BROWSER_WASM_OUTPUT): $(ALL_OBJECTS) +$(BROWSER_WASM_OUTPUT): $(ALL_OBJECTS) $(CSS_PARSER_LIB) mkdir -p $(BROWSER_BUILD_DIR) - em++ $(ALL_OBJECTS) $(CFLAGS) $(WASM_FLAGS) -s ENVIRONMENT='web' -o $(BROWSER_WASM_OUTPUT) + em++ $(ALL_OBJECTS) $(CSS_PARSER_LIB) $(CFLAGS) $(WASM_FLAGS) -s ENVIRONMENT='web' -o $(BROWSER_WASM_OUTPUT) -$(NODE_WASM_OUTPUT): $(ALL_OBJECTS) +$(NODE_WASM_OUTPUT): $(ALL_OBJECTS) $(CSS_PARSER_LIB) mkdir -p $(NODE_BUILD_DIR) - em++ $(ALL_OBJECTS) $(CFLAGS) $(WASM_FLAGS) -s ENVIRONMENT='node' -o $(NODE_WASM_OUTPUT) + em++ $(ALL_OBJECTS) $(CSS_PARSER_LIB) $(CFLAGS) $(WASM_FLAGS) -s ENVIRONMENT='node' -o $(NODE_WASM_OUTPUT) clean: clean_objects clean_browser clean_node