Optimizations and refactors - #67
Open
hugopl wants to merge 23 commits into
Open
Conversation
Code from nodes `to_s` methods were just moved to respective `visit` methods.
Buffer static string fragments in @pending_static and flush them as a single io << before any dynamic write or control-flow statement. Reduces generated code for basic.slang from 324 to 132 lines (~59% fewer io << calls) and yields ~7% throughput improvement at runtime.
For Text-type tokens (| and ' syntax) whose value is a plain string
literal with no escape sequences and no #{} interpolation, resolve the
content and any HTML escaping at codegen time via emit_static instead
of emitting a runtime HTML.escape(...).to_s call. This folds literal
text directly into the surrounding static io << buffer.
Introduce Token::AttributeValue record to carry the raw value and a
literal flag alongside each non-class attribute. An attribute is
literal when its value was a quoted template string with no #{} or
backslash escape sequences.
In codegen, literal attributes skip the unless-false/unless-true guards
and the runtime .gsub call entirely: the " escaping is done at
codegen time and the result folded into the static io << buffer.
Dynamic attributes (variables, expressions, #{} interpolation) keep
the existing runtime path unchanged.
…mment
The pending_static buffer only needs flushing immediately before a direct
write to str. Every such write already calls flush_static itself, so the
explicit flush after emit_static(">") in visit(Element) and before
visit_children in visit(Comment) was redundant. Removing them lets
consecutive all-static subtrees accumulate into a single io << call.
Generated io << call count for basic.slang: 324 → 57.
…codegen Improves readability of generated code when debugging: instead of a single long inspect string, each HTML line appears on its own line in the output.
Runs Ameba linter on Linux and tests on Linux, Windows, and macOS using the latest Crystal version, triggered on every push and pull request.
Author
Fix consume_text to strip exactly one separator space (Slim-compatible) instead of all leading whitespace, and use rstrip instead of strip. This preserves relative indentation in multi-line | text blocks, which matters for content like JSON inside <script type="importmap">. Add spec/importmap_spec.cr (was untracked) and fix its expected output to match the corrected whitespace behavior. Add 9 new tests covering previously untested features: comments (invisible, visible, children, conditional IE), anonymous div shorthands (#id, .class, #id.class), trailing-space ' text prefix, and css: alias.
p prints to stdout during tests; passthrough returns the argument unchanged without printing.
Owner
|
Looks like your GHA workflow doesn't pass: https://github.com/hugopl/slang/actions/runs/25576449648 Can you fix that first? Either your code doesn't work or there's something wrong with the workflow. |
- Fixes GitHub Actions lint job by replacing the removed `ameba` input inside `crystal-lang/install-crystal` with the official, self-contained `crystal-ameba/github-action`. - Fixes Windows spec failures caused by trailing carriage returns (`\r`) in templates by adding explicit CRLF checks/breaks inside `consume_string`, `consume_line`, and `consume_value` lexer routines. - Adds spec coverage for Windows CRLF line endings. Co-developed-by: Gemini AI <renich+gemini@woralelandia.com> Signed-off-by: Rénich Bon Ćirić <renich@woralelandia.com>
Co-developed-by: Gemini AI <renich+gemini@woralelandia.com> Signed-off-by: Rénich Bon Ćirić <renich@woralelandia.com>
This ensures that the test suite does not fail on Windows due to line ending conversion of spec files and templates by the Git checkouts. Co-developed-by: Gemini AI <renich+gemini@woralelandia.com> Signed-off-by: Rénich Bon Ćirić <renich@woralelandia.com>
- Re-enables the `Lint/NotNil` and `Metrics/CyclomaticComplexity` rules in `.ameba.yml`. - Replaces unsafe `.not_nil!` usages in `Nodes::Control` with a safe `control_value` helper method. - Refactors `visit(node : Nodes::Element)` and `visit(node : Nodes::Text)` inside `src/slang/codegen.cr` by extracting private rendering helpers. - Refactors `next_token`, `consume_string`, and `consume_value` inside `src/slang/lexer.cr` by extracting character dispatch and parsing helpers. - Declares `ameba` as a development dependency in `shard.yml`. Co-developed-by: Gemini AI <renich+gemini@woralelandia.com> Signed-off-by: Rénich Bon Ćirić <renich@woralelandia.com>
Author
|
I guess it passes now? I merged the work of @renich on my |
Slang::Extractor walks the AST and lists translatable tag text and allowlisted literal attributes (placeholder/title/aria-label/alt) as gettext .pot entries, with line references and translator comments via bare `/` comments. pre/code/script/style content and letter-less text (separators like "·"/"—") are excluded automatically. Codegen gains an opt-in `translate` flag (default false, so existing behavior and consumers without a t() are unaffected) that swaps the same literals for t() calls instead of baking English into the binary. process.cr exposes both via --extract and --translate. Implements Phase 0 of rpm5's I18N.md.
Reads locales/*.po directly at codegen time and, per locale, resolves every translatable literal to its msgstr (falling back to the source string for missing/empty/fuzzy entries) right there — folding the result into the same static buffer as ordinary text, no t() call, no runtime hash lookup. Slang.process_string_i18n/process_file_i18n wrap one such pass per locale in a single `case lang` dispatch, so a render call does exactly one string compare total instead of one per string. Adds Slang::Po (minimal msgid/msgstr reader), Slang.embed_i18n/file_i18n macros, and a --i18n mode for process.cr. Extractor/.pot generation is unchanged.
Compiling with -Dslang_i18n_disabled makes embed_i18n/file_i18n fall back to plain embed/file (source-language template, lang unused), skipping the per-locale codegen pass so dev builds compile faster. spec/i18n_flag_spec.cr only has real examples when compiled with the flag; CI now runs it as a separate crystal spec -Dslang_i18n_disabled step. Exercising it requires this shard to be requirable as slang against itself, because embed's run() macro shells out to slang/slang/process, which only resolves via the shard-style lib path. A self path development dependency makes shards install symlink lib/slang, so nothing under lib/ needs to be committed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I plan to use this shard in a project, so last year I started doing some refactoring to prepare the land for my final target: Be able to use Slang at compile time or at runtime (like Liquid).
2025 patches done by humans, 2026 patches done by AI under supervision of humans. Slang still only working with compile time rendering, but the output now is optimized and do a lot less allocations.
Results rendering the
basic.slangfile found in test fixtures:io <<call count for basic.slang: 324 → 57.io <<calls).HTML.escapeand.gsubdone at compile time.