From b7275519fa82b39cf6e5cc8def77237870c1f632 Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Sun, 28 Jun 2026 17:28:53 +0200 Subject: [PATCH] Herb: Generate Parser Options from `config.yml` --- .gitignore | 12 ++ config.yml | 92 ++++++++++ ext/herb/extconf.rb | 3 +- ext/herb/extension.c | 73 +------- ext/herb/extension_helpers.c | 21 +-- java/Makefile | 2 +- java/herb_jni.c | 130 +------------- java/org/herb/ParserOptions.java | 150 ---------------- .../packages/core/src/parser-options.ts | 103 ----------- lib/herb/parser_options.rb | 82 --------- rust/src/herb.rs | 39 +---- rust/src/lib.rs | 1 + sig/herb/parser_options.rbs | 22 ++- src/include/parser/parser.h | 26 +-- src/parser.c | 18 -- .../ext/herb/parser_options_helpers.c.erb | 63 +++++++ .../ext/herb/parser_options_helpers.h.erb | 10 ++ templates/java/nodes.c.erb | 4 + .../java/org/herb/ParserOptions.java.erb | 24 +++ templates/java/parser_options_helpers.c.erb | 48 ++++++ templates/java/parser_options_helpers.h.erb | 9 + .../packages/core/src/parser-options.ts.erb | 38 ++++ templates/lib/herb/parser_options.rb.erb | 41 +++++ templates/rust/src/parser_options.rs.erb | 16 ++ .../src/include/parser/parser_options.h.erb | 15 ++ templates/src/parser_options_defaults.c.erb | 13 ++ templates/template.rb | 162 +++++++++++++++++- templates/wasm/parser_options_helpers.cpp.erb | 25 +++ templates/wasm/parser_options_helpers.h.erb | 12 ++ wasm/herb-wasm.cpp | 67 +------- 30 files changed, 611 insertions(+), 710 deletions(-) delete mode 100644 java/org/herb/ParserOptions.java delete mode 100644 javascript/packages/core/src/parser-options.ts delete mode 100644 lib/herb/parser_options.rb create mode 100644 templates/ext/herb/parser_options_helpers.c.erb create mode 100644 templates/ext/herb/parser_options_helpers.h.erb create mode 100644 templates/java/org/herb/ParserOptions.java.erb create mode 100644 templates/java/parser_options_helpers.c.erb create mode 100644 templates/java/parser_options_helpers.h.erb create mode 100644 templates/javascript/packages/core/src/parser-options.ts.erb create mode 100644 templates/lib/herb/parser_options.rb.erb create mode 100644 templates/rust/src/parser_options.rs.erb create mode 100644 templates/src/include/parser/parser_options.h.erb create mode 100644 templates/src/parser_options_defaults.c.erb create mode 100644 templates/wasm/parser_options_helpers.cpp.erb create mode 100644 templates/wasm/parser_options_helpers.h.erb diff --git a/.gitignore b/.gitignore index c405e8daa..da7f02dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -91,19 +91,25 @@ pkg/* # Templated Files ext/herb/error_helpers.c +ext/herb/parser_options_helpers.c +ext/herb/parser_options_helpers.h ext/herb/error_helpers.h ext/herb/nodes.c ext/herb/nodes.h java/error_helpers.c +java/parser_options_helpers.c +java/parser_options_helpers.h java/error_helpers.h java/nodes.c java/nodes.h java/org/herb/ast/Errors.java +java/org/herb/ParserOptions.java java/org/herb/ast/Nodes.java java/org/herb/ast/NodeVisitor.java java/org/herb/ast/HelperRegistry.java java/org/herb/ast/Visitor.java javascript/packages/core/src/action-view-helpers.ts +javascript/packages/core/src/parser-options.ts javascript/packages/core/src/errors.ts javascript/packages/core/src/html-entities.json javascript/packages/core/src/node-type-guards.ts @@ -117,11 +123,13 @@ javascript/packages/node/extension/nodes.h lib/herb/action_view/helper_registry.rb lib/herb/ast/nodes.rb lib/herb/errors.rb +lib/herb/parser_options.rb lib/herb/visitor.rb rust/src/action_view_helpers.rs rust/src/ast/nodes.rs rust/src/config.rs rust/src/errors.rs +rust/src/parser_options.rs rust/src/nodes.rs rust/src/union_types.rs rust/src/visitor.rs @@ -135,10 +143,12 @@ src/analyze/transform.c src/ast/ast_nodes.c src/ast/ast_pretty_print.c src/errors.c +src/parser_options_defaults.c src/include/analyze/action_view/helper_registry.h src/include/ast/ast_nodes.h src/include/ast/ast_pretty_print.h src/include/errors.h +src/include/parser/parser_options.h src/include/lib/hb_foreach.h src/parser/match_tags.c src/diff/herb_diff_helpers.c @@ -146,6 +156,8 @@ src/diff/herb_diff_nodes.c src/diff/herb_hash_tree.c src/visitor.c wasm/error_helpers.cpp +wasm/parser_options_helpers.cpp +wasm/parser_options_helpers.h wasm/error_helpers.h wasm/nodes.cpp wasm/nodes.h diff --git a/config.yml b/config.yml index 693013f25..0446c1f6b 100644 --- a/config.yml +++ b/config.yml @@ -500,6 +500,98 @@ warnings: fields: [] types: [] +parser_options: + fields: + - name: track_whitespace + type: boolean + default: false + description: "Whether to track whitespace tokens during parsing." + + - name: analyze + type: boolean + default: true + description: "Whether analysis is performed during parsing." + + - name: strict + type: boolean + default: true + description: "Whether strict mode is enabled during parsing." + + - name: action_view_helpers + type: boolean + default: false + description: "Whether ActionView tag helper transformation is enabled during parsing." + + - name: transform_conditionals + type: boolean + default: false + description: "Whether postfix conditional transformation is enabled during parsing." + + - name: render_nodes + type: boolean + default: false + description: "Whether ActionView render call detection is enabled during parsing." + + - name: strict_locals + type: boolean + default: false + description: "Whether strict locals analysis is enabled during parsing." + + - name: prism_program + type: boolean + default: false + description: "Whether the full Prism ProgramNode is serialized on the DocumentNode." + + - name: prism_nodes + type: boolean + default: false + description: "Whether Prism node serialization is enabled during parsing." + + - name: prism_nodes_deep + type: boolean + default: false + description: "Whether deep Prism node serialization is enabled during parsing." + + - name: dot_notation_tags + type: boolean + default: false + description: "Whether dot-notation component tags (e.g. Dialog.Button) are parsed as HTML elements." + + - name: html + type: boolean + default: true + description: "Whether HTML tag parsing is enabled. When false, HTML-like content is treated as literal text." + + - name: timeout + type: uint32 + c_name: timeout_ms + default_ms: 1000 + ruby_unit: seconds + description: "Parse timeout." + + - name: max_errors + type: uint32 + default: 25 + nullable: true + null_sentinel: 0 + description: "Maximum number of errors to report. nil/null means unlimited." + + internal_fields: + - name: start_line + type: uint32 + default: 0 + + - name: start_column + type: uint32 + default: 0 + + - name: error_count + type: pointer_uint32 + + - name: deadline_ms + type: uint64 + default: 0 + nodes: # fields: # - name: type diff --git a/ext/herb/extconf.rb b/ext/herb/extconf.rb index 968d560df..17a492da8 100644 --- a/ext/herb/extconf.rb +++ b/ext/herb/extconf.rb @@ -101,7 +101,8 @@ "extension.c", "nodes.c", "error_helpers.c", - "extension_helpers.c" + "extension_helpers.c", + "parser_options_helpers.c" ] $srcs = core_src_files + herb_src_files + prism_main_files + prism_util_files diff --git a/ext/herb/extension.c b/ext/herb/extension.c index 0d59d68a6..e9be886a3 100644 --- a/ext/herb/extension.c +++ b/ext/herb/extension.c @@ -7,6 +7,7 @@ #include "extension.h" #include "extension_helpers.h" #include "nodes.h" +#include "parser_options_helpers.h" VALUE mHerb; VALUE cPosition; @@ -116,79 +117,9 @@ static VALUE Herb_parse(int argc, VALUE* argv, VALUE self) { bool print_arena_stats = false; parser_options_T parser_options = HERB_DEFAULT_PARSER_OPTIONS; + herb_extract_parser_options(options, &parser_options); if (!NIL_P(options)) { - VALUE track_whitespace = rb_hash_lookup(options, rb_utf8_str_new_cstr("track_whitespace")); - if (NIL_P(track_whitespace)) { track_whitespace = rb_hash_lookup(options, ID2SYM(rb_intern("track_whitespace"))); } - if (!NIL_P(track_whitespace) && RTEST(track_whitespace)) { parser_options.track_whitespace = true; } - - VALUE analyze = rb_hash_lookup(options, rb_utf8_str_new_cstr("analyze")); - if (NIL_P(analyze)) { analyze = rb_hash_lookup(options, ID2SYM(rb_intern("analyze"))); } - if (!NIL_P(analyze) && !RTEST(analyze)) { parser_options.analyze = false; } - - VALUE strict = rb_hash_lookup(options, rb_utf8_str_new_cstr("strict")); - if (NIL_P(strict)) { strict = rb_hash_lookup(options, ID2SYM(rb_intern("strict"))); } - if (!NIL_P(strict)) { parser_options.strict = RTEST(strict); } - - VALUE action_view_helpers = rb_hash_lookup(options, rb_utf8_str_new_cstr("action_view_helpers")); - if (NIL_P(action_view_helpers)) { - action_view_helpers = rb_hash_lookup(options, ID2SYM(rb_intern("action_view_helpers"))); - } - if (!NIL_P(action_view_helpers) && RTEST(action_view_helpers)) { parser_options.action_view_helpers = true; } - - VALUE transform_conditionals = rb_hash_lookup(options, rb_utf8_str_new_cstr("transform_conditionals")); - if (NIL_P(transform_conditionals)) { - transform_conditionals = rb_hash_lookup(options, ID2SYM(rb_intern("transform_conditionals"))); - } - if (!NIL_P(transform_conditionals) && RTEST(transform_conditionals)) { - parser_options.transform_conditionals = true; - } - - VALUE dot_notation_tags = rb_hash_lookup(options, rb_utf8_str_new_cstr("dot_notation_tags")); - if (NIL_P(dot_notation_tags)) { - dot_notation_tags = rb_hash_lookup(options, ID2SYM(rb_intern("dot_notation_tags"))); - } - if (!NIL_P(dot_notation_tags) && RTEST(dot_notation_tags)) { parser_options.dot_notation_tags = true; } - - VALUE render_nodes = rb_hash_lookup(options, rb_utf8_str_new_cstr("render_nodes")); - if (NIL_P(render_nodes)) { render_nodes = rb_hash_lookup(options, ID2SYM(rb_intern("render_nodes"))); } - if (!NIL_P(render_nodes) && RTEST(render_nodes)) { parser_options.render_nodes = true; } - - VALUE strict_locals = rb_hash_lookup(options, rb_utf8_str_new_cstr("strict_locals")); - if (NIL_P(strict_locals)) { strict_locals = rb_hash_lookup(options, ID2SYM(rb_intern("strict_locals"))); } - if (!NIL_P(strict_locals) && RTEST(strict_locals)) { parser_options.strict_locals = true; } - - VALUE prism_nodes = rb_hash_lookup(options, rb_utf8_str_new_cstr("prism_nodes")); - if (NIL_P(prism_nodes)) { prism_nodes = rb_hash_lookup(options, ID2SYM(rb_intern("prism_nodes"))); } - if (!NIL_P(prism_nodes) && RTEST(prism_nodes)) { parser_options.prism_nodes = true; } - - VALUE prism_nodes_deep = rb_hash_lookup(options, rb_utf8_str_new_cstr("prism_nodes_deep")); - if (NIL_P(prism_nodes_deep)) { prism_nodes_deep = rb_hash_lookup(options, ID2SYM(rb_intern("prism_nodes_deep"))); } - if (!NIL_P(prism_nodes_deep) && RTEST(prism_nodes_deep)) { parser_options.prism_nodes_deep = true; } - - VALUE prism_program = rb_hash_lookup(options, rb_utf8_str_new_cstr("prism_program")); - if (NIL_P(prism_program)) { prism_program = rb_hash_lookup(options, ID2SYM(rb_intern("prism_program"))); } - if (!NIL_P(prism_program) && RTEST(prism_program)) { parser_options.prism_program = true; } - - VALUE html = rb_hash_lookup(options, rb_utf8_str_new_cstr("html")); - if (NIL_P(html)) { html = rb_hash_lookup(options, ID2SYM(rb_intern("html"))); } - if (!NIL_P(html) && !RTEST(html)) { parser_options.html = false; } - - VALUE timeout = rb_hash_lookup(options, rb_utf8_str_new_cstr("timeout")); - if (NIL_P(timeout)) { timeout = rb_hash_lookup(options, ID2SYM(rb_intern("timeout"))); } - if (!NIL_P(timeout)) { parser_options.timeout_ms = (uint32_t) (NUM2DBL(timeout) * 1000); } - - VALUE max_errors_sentinel = ID2SYM(rb_intern("__not_set__")); - VALUE max_errors = rb_hash_lookup2(options, rb_utf8_str_new_cstr("max_errors"), max_errors_sentinel); - - if (max_errors == max_errors_sentinel) { - max_errors = rb_hash_lookup2(options, ID2SYM(rb_intern("max_errors")), max_errors_sentinel); - } - - if (max_errors != max_errors_sentinel) { - parser_options.max_errors = NIL_P(max_errors) ? 0 : (uint32_t) NUM2UINT(max_errors); - } - VALUE arena_stats = rb_hash_lookup(options, rb_utf8_str_new_cstr("arena_stats")); if (NIL_P(arena_stats)) { arena_stats = rb_hash_lookup(options, ID2SYM(rb_intern("arena_stats"))); } if (!NIL_P(arena_stats) && RTEST(arena_stats)) { print_arena_stats = true; } diff --git a/ext/herb/extension_helpers.c b/ext/herb/extension_helpers.c index eb0663020..c38556af6 100644 --- a/ext/herb/extension_helpers.c +++ b/ext/herb/extension_helpers.c @@ -3,6 +3,7 @@ #include "extension.h" #include "extension_helpers.h" #include "nodes.h" +#include "parser_options_helpers.h" #include "../../src/include/herb.h" #include "../../src/include/lexer/token.h" @@ -83,25 +84,7 @@ VALUE create_parse_result(AST_DOCUMENT_NODE_T* root, VALUE source, const parser_ VALUE value = rb_node_from_c_struct((AST_NODE_T*) root); VALUE warnings = rb_ary_new(); VALUE errors = rb_ary_new(); - - VALUE kwargs = rb_hash_new(); - rb_hash_aset(kwargs, ID2SYM(rb_intern("strict")), options->strict ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("track_whitespace")), options->track_whitespace ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("analyze")), options->analyze ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("action_view_helpers")), options->action_view_helpers ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("transform_conditionals")), options->transform_conditionals ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("render_nodes")), options->render_nodes ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("strict_locals")), options->strict_locals ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_nodes")), options->prism_nodes ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_nodes_deep")), options->prism_nodes_deep ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_program")), options->prism_program ? Qtrue : Qfalse); - rb_hash_aset(kwargs, ID2SYM(rb_intern("timeout")), DBL2NUM((double) options->timeout_ms / 1000.0)); - - rb_hash_aset( - kwargs, - ID2SYM(rb_intern("max_errors")), - options->max_errors == 0 ? Qnil : UINT2NUM(options->max_errors) - ); + VALUE kwargs = herb_build_parser_options_hash(options); VALUE parser_options_args[1] = { kwargs }; VALUE parser_options = rb_class_new_instance_kw(1, parser_options_args, cParserOptions, RB_PASS_KEYWORDS); diff --git a/java/Makefile b/java/Makefile index 06032dc94..06619789d 100644 --- a/java/Makefile +++ b/java/Makefile @@ -39,7 +39,7 @@ HERB_SOURCES = $(shell find $(SRC_DIR) -name '*.c') HERB_OBJECTS = $(filter-out $(SRC_DIR)/main.o, $(HERB_SOURCES:.c=.o)) JNI_SOURCES = herb_jni.c extension_helpers.c -JNI_GENERATED_SOURCES = nodes.c error_helpers.c +JNI_GENERATED_SOURCES = nodes.c error_helpers.c parser_options_helpers.c JNI_OBJECTS = $(JNI_SOURCES:.c=.o) $(JNI_GENERATED_SOURCES:.c=.o) diff --git a/java/herb_jni.c b/java/herb_jni.c index d81deee59..dbcd8c31f 100644 --- a/java/herb_jni.c +++ b/java/herb_jni.c @@ -1,6 +1,7 @@ #include "herb_jni.h" #include "extension_helpers.h" #include "nodes.h" +#include "parser_options_helpers.h" #include "../../src/include/extract.h" #include "../../src/include/herb.h" @@ -30,134 +31,7 @@ Java_org_herb_Herb_parse(JNIEnv* env, jclass clazz, jstring source, jobject opti const char* src = (*env)->GetStringUTFChars(env, source, 0); parser_options_T parser_options = HERB_DEFAULT_PARSER_OPTIONS; - - if (options != NULL) { - jclass optionsClass = (*env)->GetObjectClass(env, options); - jmethodID getTrackWhitespace = - (*env)->GetMethodID(env, optionsClass, "isTrackWhitespace", "()Z"); - - if (getTrackWhitespace != NULL) { - jboolean trackWhitespace = (*env)->CallBooleanMethod(env, options, getTrackWhitespace); - - if (trackWhitespace == JNI_TRUE) { - parser_options.track_whitespace = true; - } - } - - jmethodID getAnalyze = - (*env)->GetMethodID(env, optionsClass, "isAnalyze", "()Z"); - - if (getAnalyze != NULL) { - jboolean analyze = (*env)->CallBooleanMethod(env, options, getAnalyze); - - if (analyze == JNI_FALSE) { - parser_options.analyze = false; - } - } - - jmethodID getStrict = - (*env)->GetMethodID(env, optionsClass, "isStrict", "()Z"); - - if (getStrict != NULL) { - jboolean strict = (*env)->CallBooleanMethod(env, options, getStrict); - parser_options.strict = (strict == JNI_TRUE); - } - - jmethodID getActionViewHelpers = - (*env)->GetMethodID(env, optionsClass, "isActionViewHelpers", "()Z"); - - if (getActionViewHelpers != NULL) { - jboolean actionViewHelpers = (*env)->CallBooleanMethod(env, options, getActionViewHelpers); - parser_options.action_view_helpers = (actionViewHelpers == JNI_TRUE); - } - - jmethodID getTransformConditionals = - (*env)->GetMethodID(env, optionsClass, "isTransformConditionals", "()Z"); - - if (getTransformConditionals != NULL) { - jboolean transformConditionals = (*env)->CallBooleanMethod(env, options, getTransformConditionals); - parser_options.transform_conditionals = (transformConditionals == JNI_TRUE); - } - - jmethodID getRenderNodes = - (*env)->GetMethodID(env, optionsClass, "isRenderNodes", "()Z"); - - if (getRenderNodes != NULL) { - jboolean renderNodes = (*env)->CallBooleanMethod(env, options, getRenderNodes); - parser_options.render_nodes = (renderNodes == JNI_TRUE); - } - - jmethodID getStrictLocals = - (*env)->GetMethodID(env, optionsClass, "isStrictLocals", "()Z"); - - if (getStrictLocals != NULL) { - jboolean strictLocals = (*env)->CallBooleanMethod(env, options, getStrictLocals); - parser_options.strict_locals = (strictLocals == JNI_TRUE); - } - - jmethodID getPrismNodes = - (*env)->GetMethodID(env, optionsClass, "isPrismNodes", "()Z"); - - if (getPrismNodes != NULL) { - jboolean prismNodes = (*env)->CallBooleanMethod(env, options, getPrismNodes); - parser_options.prism_nodes = (prismNodes == JNI_TRUE); - } - - jmethodID getPrismNodesDeep = - (*env)->GetMethodID(env, optionsClass, "isPrismNodesDeep", "()Z"); - - if (getPrismNodesDeep != NULL) { - jboolean prismNodesDeep = (*env)->CallBooleanMethod(env, options, getPrismNodesDeep); - parser_options.prism_nodes_deep = (prismNodesDeep == JNI_TRUE); - } - - jmethodID getPrismProgram = - (*env)->GetMethodID(env, optionsClass, "isPrismProgram", "()Z"); - - if (getPrismProgram != NULL) { - jboolean prismProgram = (*env)->CallBooleanMethod(env, options, getPrismProgram); - parser_options.prism_program = (prismProgram == JNI_TRUE); - } - - jmethodID getDotNotationTags = - (*env)->GetMethodID(env, optionsClass, "isDotNotationTags", "()Z"); - - if (getDotNotationTags != NULL) { - jboolean dotNotationTags = (*env)->CallBooleanMethod(env, options, getDotNotationTags); - parser_options.dot_notation_tags = (dotNotationTags == JNI_TRUE); - } - - jmethodID getHtml = - (*env)->GetMethodID(env, optionsClass, "isHtml", "()Z"); - - if (getHtml != NULL) { - jboolean html = (*env)->CallBooleanMethod(env, options, getHtml); - parser_options.html = (html == JNI_TRUE); - } - - jmethodID getTimeout = - (*env)->GetMethodID(env, optionsClass, "getTimeout", "()I"); - - if (getTimeout != NULL) { - jint timeout = (*env)->CallIntMethod(env, options, getTimeout); - parser_options.timeout_ms = (uint32_t) timeout; - } - - jmethodID getMaxErrors = - (*env)->GetMethodID(env, optionsClass, "getMaxErrors", "()Ljava/lang/Integer;"); - - if (getMaxErrors != NULL) { - jobject maxErrorsObj = (*env)->CallObjectMethod(env, options, getMaxErrors); - - if (maxErrorsObj == NULL) { - parser_options.max_errors = 0; - } else { - jclass integerClass = (*env)->FindClass(env, "java/lang/Integer"); - jmethodID intValue = (*env)->GetMethodID(env, integerClass, "intValue", "()I"); - parser_options.max_errors = (uint32_t) (*env)->CallIntMethod(env, maxErrorsObj, intValue); - } - } - } + herb_extract_parser_options(env, options, &parser_options); hb_allocator_T allocator; if (!hb_allocator_init(&allocator, HB_ALLOCATOR_ARENA)) { diff --git a/java/org/herb/ParserOptions.java b/java/org/herb/ParserOptions.java deleted file mode 100644 index 47a3cc461..000000000 --- a/java/org/herb/ParserOptions.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.herb; - -public class ParserOptions { - private boolean trackWhitespace = false; - private boolean analyze = true; - private boolean strict = true; - private boolean actionViewHelpers = false; - private boolean transformConditionals = false; - private boolean renderNodes = false; - private boolean strictLocals = false; - private boolean prismNodes = false; - private boolean prismNodesDeep = false; - private boolean prismProgram = false; - private boolean dotNotationTags = false; - private boolean html = true; - private int timeout = 1000; - private Integer maxErrors = 25; - - public ParserOptions() {} - - public ParserOptions trackWhitespace(boolean value) { - this.trackWhitespace = value; - return this; - } - - public boolean isTrackWhitespace() { - return trackWhitespace; - } - - public ParserOptions analyze(boolean value) { - this.analyze = value; - return this; - } - - public boolean isAnalyze() { - return analyze; - } - - public ParserOptions strict(boolean value) { - this.strict = value; - return this; - } - - public boolean isStrict() { - return strict; - } - - public ParserOptions actionViewHelpers(boolean value) { - this.actionViewHelpers = value; - return this; - } - - public boolean isActionViewHelpers() { - return actionViewHelpers; - } - - public ParserOptions transformConditionals(boolean value) { - this.transformConditionals = value; - return this; - } - - public boolean isTransformConditionals() { - return transformConditionals; - } - - public ParserOptions renderNodes(boolean value) { - this.renderNodes = value; - return this; - } - - public boolean isRenderNodes() { - return renderNodes; - } - - public ParserOptions strictLocals(boolean value) { - this.strictLocals = value; - return this; - } - - public boolean isStrictLocals() { - return strictLocals; - } - - public ParserOptions prismNodes(boolean value) { - this.prismNodes = value; - return this; - } - - public boolean isPrismNodes() { - return prismNodes; - } - - public ParserOptions prismNodesDeep(boolean value) { - this.prismNodesDeep = value; - return this; - } - - public boolean isPrismNodesDeep() { - return prismNodesDeep; - } - - public ParserOptions prismProgram(boolean value) { - this.prismProgram = value; - return this; - } - - public boolean isPrismProgram() { - return prismProgram; - } - - public ParserOptions dotNotationTags(boolean value) { - this.dotNotationTags = value; - return this; - } - - public boolean isDotNotationTags() { - return dotNotationTags; - } - - public ParserOptions html(boolean value) { - this.html = value; - return this; - } - - public boolean isHtml() { - return html; - } - - public ParserOptions timeout(int value) { - this.timeout = value; - return this; - } - - public int getTimeout() { - return timeout; - } - - public ParserOptions maxErrors(Integer value) { - this.maxErrors = value; - return this; - } - - public Integer getMaxErrors() { - return maxErrors; - } - - public static ParserOptions create() { - return new ParserOptions(); - } -} diff --git a/javascript/packages/core/src/parser-options.ts b/javascript/packages/core/src/parser-options.ts deleted file mode 100644 index 38abbd830..000000000 --- a/javascript/packages/core/src/parser-options.ts +++ /dev/null @@ -1,103 +0,0 @@ -export interface ParseOptions { - track_whitespace?: boolean - analyze?: boolean - strict?: boolean - action_view_helpers?: boolean - transform_conditionals?: boolean - render_nodes?: boolean - strict_locals?: boolean - prism_nodes?: boolean - prism_nodes_deep?: boolean - prism_program?: boolean - dot_notation_tags?: boolean - html?: boolean - timeout?: number - max_errors?: number | null -} - -export type SerializedParserOptions = Required - -export const DEFAULT_PARSER_OPTIONS: SerializedParserOptions = { - track_whitespace: false, - analyze: true, - strict: true, - action_view_helpers: false, - transform_conditionals: false, - render_nodes: false, - strict_locals: false, - prism_nodes: false, - prism_nodes_deep: false, - prism_program: false, - dot_notation_tags: false, - html: true, - timeout: 1000, - max_errors: 25, -} - -/** - * Represents the parser options used during parsing. - */ -export class ParserOptions { - /** Whether strict mode was enabled during parsing. */ - readonly strict: boolean - - /** Whether whitespace tracking was enabled during parsing. */ - readonly track_whitespace: boolean - - /** Whether analysis was performed during parsing. */ - readonly analyze: boolean - - /** Whether ActionView tag helper transformation was enabled during parsing. */ - readonly action_view_helpers: boolean - - /** Whether postfix conditional transformation was enabled during parsing. */ - readonly transform_conditionals: boolean - - /** Whether ActionView render call detection was enabled during parsing. */ - readonly render_nodes: boolean - - /** Whether strict locals analysis was enabled during parsing. */ - readonly strict_locals: boolean - - /** Whether Prism node serialization was enabled during parsing. */ - readonly prism_nodes: boolean - - /** Whether deep Prism node serialization was enabled during parsing. */ - readonly prism_nodes_deep: boolean - - /** Whether the full Prism ProgramNode was serialized on the DocumentNode. */ - readonly prism_program: boolean - - /** Whether dot-notation component tags (e.g. Dialog.Button) are parsed as HTML elements. */ - readonly dot_notation_tags: boolean - - /** Whether HTML tag parsing is enabled during parsing. When false, HTML-like content is treated as literal text. */ - readonly html: boolean - - /** Parse timeout in milliseconds. 0 disables the timeout. */ - readonly timeout: number - - /** Maximum number of errors to report. null means unlimited. */ - readonly max_errors: number | null - - static from(options: SerializedParserOptions): ParserOptions { - return new ParserOptions(options) - } - - constructor(options: ParseOptions = {}) { - this.strict = options.strict ?? DEFAULT_PARSER_OPTIONS.strict - this.track_whitespace = options.track_whitespace ?? DEFAULT_PARSER_OPTIONS.track_whitespace - this.analyze = options.analyze ?? DEFAULT_PARSER_OPTIONS.analyze - this.action_view_helpers = options.action_view_helpers ?? DEFAULT_PARSER_OPTIONS.action_view_helpers - this.transform_conditionals = options.transform_conditionals ?? DEFAULT_PARSER_OPTIONS.transform_conditionals - this.render_nodes = options.render_nodes ?? DEFAULT_PARSER_OPTIONS.render_nodes - this.strict_locals = options.strict_locals ?? DEFAULT_PARSER_OPTIONS.strict_locals - this.prism_nodes = options.prism_nodes ?? DEFAULT_PARSER_OPTIONS.prism_nodes - this.prism_nodes_deep = options.prism_nodes_deep ?? DEFAULT_PARSER_OPTIONS.prism_nodes_deep - this.prism_program = options.prism_program ?? DEFAULT_PARSER_OPTIONS.prism_program - this.dot_notation_tags = options.dot_notation_tags ?? DEFAULT_PARSER_OPTIONS.dot_notation_tags - this.html = options.html ?? DEFAULT_PARSER_OPTIONS.html - this.timeout = options.timeout ?? DEFAULT_PARSER_OPTIONS.timeout - this.max_errors = options.max_errors ?? DEFAULT_PARSER_OPTIONS.max_errors - } -} diff --git a/lib/herb/parser_options.rb b/lib/herb/parser_options.rb deleted file mode 100644 index ebf576256..000000000 --- a/lib/herb/parser_options.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -module Herb - class ParserOptions - attr_reader :strict #: bool - attr_reader :track_whitespace #: bool - attr_reader :analyze #: bool - attr_reader :action_view_helpers #: bool - attr_reader :transform_conditionals #: bool - attr_reader :render_nodes #: bool - attr_reader :strict_locals #: bool - attr_reader :prism_program #: bool - attr_reader :prism_nodes #: bool - attr_reader :prism_nodes_deep #: bool - attr_reader :timeout #: Numeric - attr_reader :max_errors #: Integer? - - DEFAULT_STRICT = true #: bool - DEFAULT_TRACK_WHITESPACE = false #: bool - DEFAULT_ANALYZE = true #: bool - DEFAULT_ACTION_VIEW_HELPERS = false #: bool - DEFAULT_TRANSFORM_CONDITIONALS = false #: bool - DEFAULT_RENDER_NODES = false #: bool - DEFAULT_STRICT_LOCALS = false #: bool - DEFAULT_PRISM_PROGRAM = false #: bool - DEFAULT_PRISM_NODES = false #: bool - DEFAULT_PRISM_NODES_DEEP = false #: bool - DEFAULT_TIMEOUT = 1 #: Numeric - DEFAULT_MAX_ERRORS = 25 #: Integer - - #: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?timeout: Numeric) -> void - def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPACE, analyze: DEFAULT_ANALYZE, action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS, render_nodes: DEFAULT_RENDER_NODES, strict_locals: DEFAULT_STRICT_LOCALS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM, timeout: DEFAULT_TIMEOUT, max_errors: DEFAULT_MAX_ERRORS) - @strict = strict - @track_whitespace = track_whitespace - @analyze = analyze - @action_view_helpers = action_view_helpers - @transform_conditionals = transform_conditionals - @render_nodes = render_nodes - @strict_locals = strict_locals - @prism_nodes = prism_nodes - @prism_nodes_deep = prism_nodes_deep - @prism_program = prism_program - @timeout = timeout - @max_errors = max_errors - end - - #: () -> Hash[Symbol, (bool | Numeric | nil)] - def to_h - { - strict: @strict, - track_whitespace: @track_whitespace, - analyze: @analyze, - action_view_helpers: @action_view_helpers, - transform_conditionals: @transform_conditionals, - render_nodes: @render_nodes, - strict_locals: @strict_locals, - prism_nodes: @prism_nodes, - prism_nodes_deep: @prism_nodes_deep, - prism_program: @prism_program, - timeout: @timeout, - max_errors: @max_errors, - } - end - - #: () -> String - def inspect - "#<#{self.class.name}\n " \ - "strict=#{@strict}\n " \ - "track_whitespace=#{@track_whitespace}\n " \ - "analyze=#{@analyze}\n " \ - "action_view_helpers=#{@action_view_helpers}\n " \ - "transform_conditionals=#{@transform_conditionals}\n " \ - "render_nodes=#{@render_nodes}\n " \ - "strict_locals=#{@strict_locals}\n " \ - "prism_nodes=#{@prism_nodes}\n " \ - "prism_nodes_deep=#{@prism_nodes_deep}\n " \ - "prism_program=#{@prism_program}\n " \ - "timeout=#{@timeout}\n " \ - "max_errors=#{@max_errors}>" - end - end -end diff --git a/rust/src/herb.rs b/rust/src/herb.rs index 678c47d4a..fdfc74dab 100644 --- a/rust/src/herb.rs +++ b/rust/src/herb.rs @@ -3,44 +3,7 @@ use crate::convert::token_from_c; use crate::{LexResult, ParseResult}; use std::ffi::{CStr, CString}; -#[derive(Debug, Clone)] -pub struct ParserOptions { - pub track_whitespace: bool, - pub analyze: bool, - pub strict: bool, - pub action_view_helpers: bool, - pub transform_conditionals: bool, - pub render_nodes: bool, - pub strict_locals: bool, - pub prism_nodes: bool, - pub prism_nodes_deep: bool, - pub prism_program: bool, - pub dot_notation_tags: bool, - pub html: bool, - pub timeout: u32, - pub max_errors: Option, -} - -impl Default for ParserOptions { - fn default() -> Self { - Self { - track_whitespace: false, - analyze: true, - strict: true, - action_view_helpers: false, - transform_conditionals: false, - render_nodes: false, - strict_locals: false, - prism_nodes: false, - prism_nodes_deep: false, - prism_program: false, - dot_notation_tags: false, - html: true, - timeout: 1000, - max_errors: Some(25), - } - } -} +pub use crate::parser_options::ParserOptions; #[derive(Debug, Clone)] pub struct ExtractRubyOptions { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 683bcb419..dd7e8c1b4 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -5,6 +5,7 @@ pub mod errors; pub mod ffi; pub mod herb; pub mod lex_result; +pub mod parser_options; pub mod location; pub mod nodes; pub mod parse_result; diff --git a/sig/herb/parser_options.rbs b/sig/herb/parser_options.rbs index e1b7c5064..3ea8ba68f 100644 --- a/sig/herb/parser_options.rbs +++ b/sig/herb/parser_options.rbs @@ -2,12 +2,12 @@ module Herb class ParserOptions - attr_reader strict: bool - attr_reader track_whitespace: bool attr_reader analyze: bool + attr_reader strict: bool + attr_reader action_view_helpers: bool attr_reader transform_conditionals: bool @@ -22,16 +22,20 @@ module Herb attr_reader prism_nodes_deep: bool + attr_reader dot_notation_tags: bool + + attr_reader html: bool + attr_reader timeout: Numeric attr_reader max_errors: Integer? - DEFAULT_STRICT: bool - DEFAULT_TRACK_WHITESPACE: bool DEFAULT_ANALYZE: bool + DEFAULT_STRICT: bool + DEFAULT_ACTION_VIEW_HELPERS: bool DEFAULT_TRANSFORM_CONDITIONALS: bool @@ -46,12 +50,16 @@ module Herb DEFAULT_PRISM_NODES_DEEP: bool + DEFAULT_DOT_NOTATION_TAGS: bool + + DEFAULT_HTML: bool + DEFAULT_TIMEOUT: Numeric - DEFAULT_MAX_ERRORS: Integer + DEFAULT_MAX_ERRORS: Integer? - # : (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?timeout: Numeric) -> void - def initialize: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?timeout: Numeric) -> void + # : (?track_whitespace: bool, ?analyze: bool, ?strict: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_program: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?dot_notation_tags: bool, ?html: bool, ?timeout: Numeric, ?max_errors: Integer?) -> void + def initialize: (?track_whitespace: bool, ?analyze: bool, ?strict: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_program: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?dot_notation_tags: bool, ?html: bool, ?timeout: Numeric, ?max_errors: Integer?) -> void # : () -> Hash[Symbol, (bool | Numeric | nil)] def to_h: () -> Hash[Symbol, bool | Numeric | nil] diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h index b0c7c7d0a..52e43e4dc 100644 --- a/src/include/parser/parser.h +++ b/src/include/parser/parser.h @@ -6,8 +6,7 @@ #include "../lib/hb_allocator.h" #include "../lib/hb_array.h" #include "../lib/hb_clock.h" - -#include +#include "parser_options.h" typedef enum { FOREIGN_CONTENT_UNKNOWN = 0, @@ -19,35 +18,12 @@ typedef enum { typedef enum { PARSER_STATE_DATA, PARSER_STATE_FOREIGN_CONTENT } parser_state_T; -typedef struct PARSER_OPTIONS_STRUCT { - bool track_whitespace; - bool analyze; - bool strict; - bool action_view_helpers; - bool transform_conditionals; - bool render_nodes; - bool strict_locals; - bool prism_program; - bool prism_nodes; - bool prism_nodes_deep; - bool dot_notation_tags; - bool html; - uint32_t start_line; - uint32_t start_column; - uint32_t timeout_ms; - uint32_t max_errors; - uint32_t* error_count; - uint64_t deadline_ms; -} parser_options_T; - typedef struct MATCH_TAGS_CONTEXT_STRUCT { hb_array_T* errors; const parser_options_T* options; hb_allocator_T* allocator; } match_tags_context_T; -extern const parser_options_T HERB_DEFAULT_PARSER_OPTIONS; - static inline bool parser_options_past_deadline(const parser_options_T* options) { if (options == NULL || options->timeout_ms == 0) { return false; } diff --git a/src/parser.c b/src/parser.c index bf8b6bf4c..7d7b53fc9 100644 --- a/src/parser.c +++ b/src/parser.c @@ -34,24 +34,6 @@ static bool parser_lookahead_erb_is_control_flow(parser_T* parser); static void parser_handle_erb_in_open_tag(parser_T* parser, hb_array_T* children); static void parser_handle_whitespace_in_open_tag(parser_T* parser, hb_array_T* children); -const parser_options_T HERB_DEFAULT_PARSER_OPTIONS = { .track_whitespace = false, - .analyze = true, - .strict = true, - .action_view_helpers = false, - .transform_conditionals = false, - .render_nodes = false, - .strict_locals = false, - .prism_nodes_deep = false, - .prism_nodes = false, - .prism_program = false, - .dot_notation_tags = false, - .html = true, - .start_line = 0, - .start_column = 0, - .timeout_ms = 1000, - .max_errors = 25, - .deadline_ms = 0 }; - size_t parser_sizeof(void) { return sizeof(struct PARSER_STRUCT); } diff --git a/templates/ext/herb/parser_options_helpers.c.erb b/templates/ext/herb/parser_options_helpers.c.erb new file mode 100644 index 000000000..4422aaf0b --- /dev/null +++ b/templates/ext/herb/parser_options_helpers.c.erb @@ -0,0 +1,63 @@ +#include "parser_options_helpers.h" + +void herb_extract_parser_options(VALUE options, parser_options_T* parser_options) { + if (NIL_P(options)) { return; } + +<%- parser_options.fields.each do |field| -%> +<%- if field.boolean? -%> + { + VALUE val = rb_hash_lookup(options, rb_utf8_str_new_cstr("<%= field.snake_case %>")); + if (NIL_P(val)) { val = rb_hash_lookup(options, ID2SYM(rb_intern("<%= field.snake_case %>"))); } +<%- if field.default == true -%> + if (!NIL_P(val) && !RTEST(val)) { parser_options-><%= field.c_name %> = false; } +<%- else -%> + if (!NIL_P(val) && RTEST(val)) { parser_options-><%= field.c_name %> = true; } +<%- end -%> + } + +<%- elsif field.ruby_unit == "seconds" -%> + { + VALUE val = rb_hash_lookup(options, rb_utf8_str_new_cstr("<%= field.snake_case %>")); + if (NIL_P(val)) { val = rb_hash_lookup(options, ID2SYM(rb_intern("<%= field.snake_case %>"))); } + if (!NIL_P(val)) { parser_options-><%= field.c_name %> = (uint32_t) (NUM2DBL(val) * 1000); } + } + +<%- elsif field.nullable? -%> + { + VALUE sentinel = ID2SYM(rb_intern("__not_set__")); + VALUE val = rb_hash_lookup2(options, rb_utf8_str_new_cstr("<%= field.snake_case %>"), sentinel); + if (val == sentinel) { val = rb_hash_lookup2(options, ID2SYM(rb_intern("<%= field.snake_case %>")), sentinel); } + + if (val != sentinel) { + parser_options-><%= field.c_name %> = NIL_P(val) ? <%= field.null_sentinel %> : (uint32_t) NUM2UINT(val); + } + } + +<%- else -%> + { + VALUE val = rb_hash_lookup(options, rb_utf8_str_new_cstr("<%= field.snake_case %>")); + if (NIL_P(val)) { val = rb_hash_lookup(options, ID2SYM(rb_intern("<%= field.snake_case %>"))); } + if (!NIL_P(val)) { parser_options-><%= field.c_name %> = (uint32_t) NUM2UINT(val); } + } + +<%- end -%> +<%- end -%> +} + +VALUE herb_build_parser_options_hash(const parser_options_T* options) { + VALUE kwargs = rb_hash_new(); + +<%- parser_options.fields.each do |field| -%> +<%- if field.boolean? -%> + rb_hash_aset(kwargs, ID2SYM(rb_intern("<%= field.snake_case %>")), options-><%= field.c_name %> ? Qtrue : Qfalse); +<%- elsif field.ruby_unit == "seconds" -%> + rb_hash_aset(kwargs, ID2SYM(rb_intern("<%= field.snake_case %>")), DBL2NUM((double) options-><%= field.c_name %> / 1000.0)); +<%- elsif field.nullable? -%> + rb_hash_aset(kwargs, ID2SYM(rb_intern("<%= field.snake_case %>")), options-><%= field.c_name %> == <%= field.null_sentinel %> ? Qnil : UINT2NUM(options-><%= field.c_name %>)); +<%- else -%> + rb_hash_aset(kwargs, ID2SYM(rb_intern("<%= field.snake_case %>")), UINT2NUM(options-><%= field.c_name %>)); +<%- end -%> +<%- end -%> + + return kwargs; +} diff --git a/templates/ext/herb/parser_options_helpers.h.erb b/templates/ext/herb/parser_options_helpers.h.erb new file mode 100644 index 000000000..b5c36bc06 --- /dev/null +++ b/templates/ext/herb/parser_options_helpers.h.erb @@ -0,0 +1,10 @@ +#ifndef HERB_RUBY_PARSER_OPTIONS_HELPERS_H +#define HERB_RUBY_PARSER_OPTIONS_HELPERS_H + +#include +#include "../../src/include/parser/parser_options.h" + +void herb_extract_parser_options(VALUE options, parser_options_T* parser_options); +VALUE herb_build_parser_options_hash(const parser_options_T* options); + +#endif diff --git a/templates/java/nodes.c.erb b/templates/java/nodes.c.erb index 5b4ef3994..3ac173ee1 100644 --- a/templates/java/nodes.c.erb +++ b/templates/java/nodes.c.erb @@ -119,3 +119,7 @@ jobject Create<%= node.name %>(JNIEnv* env, <%= node.struct_type %>* <%= node.hu } <%- end -%> + +jobject CreateASTNode(JNIEnv* env, AST_NODE_T* node) { + return NodeFromCStruct(env, node); +} diff --git a/templates/java/org/herb/ParserOptions.java.erb b/templates/java/org/herb/ParserOptions.java.erb new file mode 100644 index 000000000..12eeb5cd0 --- /dev/null +++ b/templates/java/org/herb/ParserOptions.java.erb @@ -0,0 +1,24 @@ +package org.herb; + +public class ParserOptions { +<%- parser_options.fields.each do |field| -%> + private <%= field.java_type %> <%= field.camel_case %> = <%= field.java_default %>; +<%- end -%> + + public ParserOptions() {} + +<%- parser_options.fields.each do |field| -%> + public ParserOptions <%= field.camel_case %>(<%= field.java_type %> value) { + this.<%= field.camel_case %> = value; + return this; + } + + public <%= field.java_type %> <%= field.java_getter %>() { + return <%= field.camel_case %>; + } + +<%- end -%> + public static ParserOptions create() { + return new ParserOptions(); + } +} diff --git a/templates/java/parser_options_helpers.c.erb b/templates/java/parser_options_helpers.c.erb new file mode 100644 index 000000000..ed2b8a9ba --- /dev/null +++ b/templates/java/parser_options_helpers.c.erb @@ -0,0 +1,48 @@ +#include "parser_options_helpers.h" + +void herb_extract_parser_options(JNIEnv* env, jobject options, parser_options_T* parser_options) { + if (options == NULL) { return; } + + jclass optionsClass = (*env)->GetObjectClass(env, options); + +<%- parser_options.fields.each do |field| -%> +<%- if field.boolean? -%> + { + jmethodID method = (*env)->GetMethodID(env, optionsClass, "<%= field.java_getter %>", "<%= field.java_jni_signature %>"); + + if (method != NULL) { + jboolean val = (*env)->CallBooleanMethod(env, options, method); + parser_options-><%= field.c_name %> = (val == JNI_TRUE); + } + } + +<%- elsif field.nullable? -%> + { + jmethodID method = (*env)->GetMethodID(env, optionsClass, "<%= field.java_getter %>", "<%= field.java_jni_signature %>"); + + if (method != NULL) { + jobject val = (*env)->CallObjectMethod(env, options, method); + + if (val == NULL) { + parser_options-><%= field.c_name %> = <%= field.null_sentinel %>; + } else { + jclass integerClass = (*env)->FindClass(env, "java/lang/Integer"); + jmethodID intValue = (*env)->GetMethodID(env, integerClass, "intValue", "()I"); + parser_options-><%= field.c_name %> = (uint32_t) (*env)->CallIntMethod(env, val, intValue); + } + } + } + +<%- else -%> + { + jmethodID method = (*env)->GetMethodID(env, optionsClass, "<%= field.java_getter %>", "<%= field.java_jni_signature %>"); + + if (method != NULL) { + jint val = (*env)->CallIntMethod(env, options, method); + parser_options-><%= field.c_name %> = (uint32_t) val; + } + } + +<%- end -%> +<%- end -%> +} diff --git a/templates/java/parser_options_helpers.h.erb b/templates/java/parser_options_helpers.h.erb new file mode 100644 index 000000000..6064fc19f --- /dev/null +++ b/templates/java/parser_options_helpers.h.erb @@ -0,0 +1,9 @@ +#ifndef HERB_JAVA_PARSER_OPTIONS_HELPERS_H +#define HERB_JAVA_PARSER_OPTIONS_HELPERS_H + +#include +#include "../src/include/parser/parser_options.h" + +void herb_extract_parser_options(JNIEnv* env, jobject options, parser_options_T* parser_options); + +#endif diff --git a/templates/javascript/packages/core/src/parser-options.ts.erb b/templates/javascript/packages/core/src/parser-options.ts.erb new file mode 100644 index 000000000..d1ca592d5 --- /dev/null +++ b/templates/javascript/packages/core/src/parser-options.ts.erb @@ -0,0 +1,38 @@ +export interface ParseOptions { + <%- parser_options.fields.each do |field| -%> + <%- if field.nullable? -%> + /** <%= field.description %> */ + <%= field.snake_case %>?: <%= field.typescript_type %> + <%- else -%> + <%= field.snake_case %>?: <%= field.typescript_type %> + <%- end -%> + <%- end -%> +} + +export type SerializedParserOptions = Required + +export const DEFAULT_PARSER_OPTIONS: SerializedParserOptions = { +<%- parser_options.fields.each do |field| -%> + <%= field.snake_case %>: <%= field.js_default %>, +<%- end -%> +} + +/** + * Represents the parser options used during parsing. + */ +export class ParserOptions { + <%- parser_options.fields.each do |field| -%> + /** <%= field.description %> */ + readonly <%= field.snake_case %>: <%= field.typescript_type %> + + <%- end -%> + static from(options: SerializedParserOptions): ParserOptions { + return new ParserOptions(options) + } + + constructor(options: ParseOptions = {}) { + <%- parser_options.fields.each do |field| -%> + this.<%= field.snake_case %> = options.<%= field.snake_case %> ?? DEFAULT_PARSER_OPTIONS.<%= field.snake_case %> + <%- end -%> + } +} diff --git a/templates/lib/herb/parser_options.rb.erb b/templates/lib/herb/parser_options.rb.erb new file mode 100644 index 000000000..27bf02161 --- /dev/null +++ b/templates/lib/herb/parser_options.rb.erb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Herb + class ParserOptions + <%- parser_options.fields.each do |field| -%> + attr_reader :<%= field.snake_case %> #: <%= field.ruby_type %> + <%- end -%> + + <%- parser_options.fields.each do |field| -%> + DEFAULT_<%= field.upper_snake %> = <%= field.ruby_default %> #: <%= field.ruby_type %> + <%- end -%> + + #: (<%= parser_options.fields.map { |f| "?#{f.snake_case}: #{f.ruby_type}" }.join(", ") %>) -> void + def initialize(<%= parser_options.fields.map { |f| "#{f.snake_case}: DEFAULT_#{f.upper_snake}" }.join(", ") %>) + <%- parser_options.fields.each do |field| -%> + @<%= field.snake_case %> = <%= field.snake_case %> + <%- end -%> + end + + #: () -> Hash[Symbol, (bool | Numeric | nil)] + def to_h + { + <%- parser_options.fields.each do |field| -%> + <%= field.snake_case %>: @<%= field.snake_case %>, + <%- end -%> + } + end + + #: () -> String + def inspect + "#<#{self.class.name}\n " \ + <%- parser_options.fields.each_with_index do |field, index| -%> + <%- if index == parser_options.fields.length - 1 -%> + "<%= field.snake_case %>=#{@<%= field.snake_case %>}>" + <%- else -%> + "<%= field.snake_case %>=#{@<%= field.snake_case %>}\n " \ + <%- end -%> + <%- end -%> + end + end +end diff --git a/templates/rust/src/parser_options.rs.erb b/templates/rust/src/parser_options.rs.erb new file mode 100644 index 000000000..09e0e117b --- /dev/null +++ b/templates/rust/src/parser_options.rs.erb @@ -0,0 +1,16 @@ +#[derive(Debug, Clone)] +pub struct ParserOptions { + <%- parser_options.fields.each do |field| -%> + pub <%= field.snake_case %>: <%= field.rust_type %>, + <%- end -%> +} + +impl Default for ParserOptions { + fn default() -> Self { + Self { + <%- parser_options.fields.each do |field| -%> + <%= field.snake_case %>: <%= field.rust_default %>, + <%- end -%> + } + } +} diff --git a/templates/src/include/parser/parser_options.h.erb b/templates/src/include/parser/parser_options.h.erb new file mode 100644 index 000000000..e04b46b96 --- /dev/null +++ b/templates/src/include/parser/parser_options.h.erb @@ -0,0 +1,15 @@ +#ifndef HERB_PARSER_OPTIONS_H +#define HERB_PARSER_OPTIONS_H + +#include +#include + +typedef struct PARSER_OPTIONS_STRUCT { +<%- parser_options.all_c_fields.each do |field| -%> + <%= field.c_type %> <%= field.c_name %>; +<%- end -%> +} parser_options_T; + +extern const parser_options_T HERB_DEFAULT_PARSER_OPTIONS; + +#endif diff --git a/templates/src/parser_options_defaults.c.erb b/templates/src/parser_options_defaults.c.erb new file mode 100644 index 000000000..0ce75fb26 --- /dev/null +++ b/templates/src/parser_options_defaults.c.erb @@ -0,0 +1,13 @@ +#include "include/parser/parser_options.h" + +#include + +const parser_options_T HERB_DEFAULT_PARSER_OPTIONS = { +<%- parser_options.all_c_fields.each_with_index do |field, index| -%> + <%- if field.pointer? -%> + .<%= field.c_name %> = NULL<%= index < parser_options.all_c_fields.length - 1 ? "," : "" %> + <%- else -%> + .<%= field.c_name %> = <%= field.c_default %><%= index < parser_options.all_c_fields.length - 1 ? "," : "" %> + <%- end -%> +<%- end -%> +}; diff --git a/templates/template.rb b/templates/template.rb index f425a748f..4b24715c7 100755 --- a/templates/template.rb +++ b/templates/template.rb @@ -872,7 +872,7 @@ def self.render(template_file) ) end - rendered_template = read_template(template_path.to_s).result_with_hash({ nodes: nodes, errors: errors, union_kinds: union_kinds, helpers: helpers }) + rendered_template = read_template(template_path.to_s).result_with_hash({ nodes: nodes, errors: errors, union_kinds: union_kinds, helpers: helpers, parser_options: parser_options }) content = heading_for(name, template_file) + rendered_template check_gitignore(name) @@ -912,11 +912,169 @@ def self.gitignore_lines [] end + class ParserOptionField + attr_reader :name, :type, :default, :description, :c_name, :nullable, :null_sentinel, :ruby_unit, :default_ms + + def initialize(config) + @name = config.fetch("name") + @type = config.fetch("type") + @default = config["default"] + @default_ms = config["default_ms"] + @description = config.fetch("description", "") + @c_name = config.fetch("c_name", @name) + @nullable = config.fetch("nullable", false) + @null_sentinel = config["null_sentinel"] + @ruby_unit = config["ruby_unit"] + end + + def boolean? + @type == "boolean" + end + + def uint32? + @type == "uint32" + end + + def uint64? + @type == "uint64" + end + + def pointer? + @type == "pointer_uint32" + end + + def nullable? + @nullable + end + + # Naming conventions + def snake_case + @name + end + + def camel_case + @name.split("_").map.with_index { |w, i| i == 0 ? w : w.capitalize }.join + end + + def pascal_case + @name.split("_").map(&:capitalize).join + end + + def upper_snake + @name.upcase + end + + def java_getter + prefix = boolean? ? "is" : "get" + "#{prefix}#{pascal_case}" + end + + def java_jni_signature + return "()Z" if boolean? + return "()Ljava/lang/Integer;" if nullable? + + "()I" + end + + def ruby_default + return "nil" if nullable? && @default.nil? + return @default_ms.to_f / 1000 if @ruby_unit == "seconds" && @default_ms + + @default + end + + def c_default + return @null_sentinel if nullable? && @default.nil? + return @default_ms if @default_ms + + @default + end + + def js_default + return "null" if nullable? && @default.nil? + + @default_ms || @default + end + + def java_default + return "null" if nullable? && @default.nil? + + @default_ms || @default + end + + def rust_default + return "None" if nullable? && @default.nil? + value = @default_ms || @default + + nullable? ? "Some(#{value})" : value + end + + def c_type + return "bool" if boolean? + return "uint64_t" if uint64? + return "uint32_t*" if pointer? + + "uint32_t" + end + + def ruby_type + return "bool" if boolean? + return "Numeric" if @ruby_unit == "seconds" + + nullable? ? "Integer?" : "Integer" + end + + def typescript_type + return "boolean" if boolean? + + nullable? ? "number | null" : "number" + end + + def java_type + return "boolean" if boolean? + + nullable? ? "Integer" : "int" + end + + def rust_type + return "bool" if boolean? + + nullable? ? "Option" : "u32" + end + end + + class ParserOptionsConfig + attr_reader :fields, :internal_fields + + def initialize(config) + @fields = (config.fetch("fields", []) || []).map { |f| ParserOptionField.new(f) } + @internal_fields = (config.fetch("internal_fields", []) || []).map { |f| ParserOptionField.new(f) } + end + + def boolean_fields + @fields.select(&:boolean?) + end + + def non_boolean_fields + @fields.reject(&:boolean?) + end + + def all_c_fields + @fields + @internal_fields + end + end + + def self.parser_options + config_data = config.dig("parser_options") + return nil unless config_data + + ParserOptionsConfig.new(config_data) + end + def self.nodes (config.dig("nodes", "types") || []).map { |node| NodeType.new(node) } end - # Collect all unique union kinds from node fields def self.union_kinds union_kinds_set = Set.new diff --git a/templates/wasm/parser_options_helpers.cpp.erb b/templates/wasm/parser_options_helpers.cpp.erb new file mode 100644 index 000000000..67911b655 --- /dev/null +++ b/templates/wasm/parser_options_helpers.cpp.erb @@ -0,0 +1,25 @@ +#include "parser_options_helpers.h" + +void herb_extract_parser_options(emscripten::val options, parser_options_T* parser_options) { + if (options.isUndefined() || options.isNull() || options.typeOf().as() != "object") { return; } + + <%- parser_options.fields.each do |field| -%> + <%- if field.boolean? -%> + if (options.hasOwnProperty("<%= field.snake_case %>")) { + parser_options-><%= field.c_name %> = options["<%= field.snake_case %>"].as(); + } + + <%- elsif field.nullable? -%> + if (options.hasOwnProperty("<%= field.snake_case %>")) { + emscripten::val val = options["<%= field.snake_case %>"]; + parser_options-><%= field.c_name %> = val.isNull() ? <%= field.null_sentinel %> : (uint32_t) val.as(); + } + + <%- else -%> + if (options.hasOwnProperty("<%= field.snake_case %>")) { + parser_options-><%= field.c_name %> = (uint32_t) options["<%= field.snake_case %>"].as(); + } + + <%- end -%> + <%- end -%> +} diff --git a/templates/wasm/parser_options_helpers.h.erb b/templates/wasm/parser_options_helpers.h.erb new file mode 100644 index 000000000..c7a4ca8a8 --- /dev/null +++ b/templates/wasm/parser_options_helpers.h.erb @@ -0,0 +1,12 @@ +#ifndef HERB_WASM_PARSER_OPTIONS_HELPERS_H +#define HERB_WASM_PARSER_OPTIONS_HELPERS_H + +#include + +extern "C" { +#include "../src/include/parser/parser_options.h" +} + +void herb_extract_parser_options(emscripten::val options, parser_options_T* parser_options); + +#endif diff --git a/wasm/herb-wasm.cpp b/wasm/herb-wasm.cpp index 82613d114..ff25dd78e 100644 --- a/wasm/herb-wasm.cpp +++ b/wasm/herb-wasm.cpp @@ -6,6 +6,7 @@ #include "extension_helpers.h" #include "nodes.h" +#include "parser_options_helpers.h" extern "C" { #include "../src/include/lib/hb_allocator.h" @@ -44,71 +45,7 @@ val Herb_lex(const std::string& source) { val Herb_parse(const std::string& source, val options) { parser_options_T parser_options = HERB_DEFAULT_PARSER_OPTIONS; - - if (!options.isUndefined() && !options.isNull() && options.typeOf().as() == "object") { - if (options.hasOwnProperty("track_whitespace")) { - bool track_whitespace = options["track_whitespace"].as(); - if (track_whitespace) { - parser_options.track_whitespace = true; - } - } - - if (options.hasOwnProperty("analyze")) { - bool analyze = options["analyze"].as(); - if (!analyze) { - parser_options.analyze = false; - } - } - - if (options.hasOwnProperty("strict")) { - parser_options.strict = options["strict"].as(); - } - - if (options.hasOwnProperty("action_view_helpers")) { - parser_options.action_view_helpers = options["action_view_helpers"].as(); - } - - if (options.hasOwnProperty("transform_conditionals")) { - parser_options.transform_conditionals = options["transform_conditionals"].as(); - } - - if (options.hasOwnProperty("render_nodes")) { - parser_options.render_nodes = options["render_nodes"].as(); - } - - if (options.hasOwnProperty("strict_locals")) { - parser_options.strict_locals = options["strict_locals"].as(); - } - - if (options.hasOwnProperty("prism_nodes")) { - parser_options.prism_nodes = options["prism_nodes"].as(); - } - - if (options.hasOwnProperty("prism_nodes_deep")) { - parser_options.prism_nodes_deep = options["prism_nodes_deep"].as(); - } - - if (options.hasOwnProperty("prism_program")) { - parser_options.prism_program = options["prism_program"].as(); - } - - if (options.hasOwnProperty("dot_notation_tags")) { - parser_options.dot_notation_tags = options["dot_notation_tags"].as(); - } - - if (options.hasOwnProperty("html")) { - parser_options.html = options["html"].as(); - } - - if (options.hasOwnProperty("timeout")) { - parser_options.timeout_ms = (uint32_t) options["timeout"].as(); - } - - if (options.hasOwnProperty("max_errors")) { - val max_errors_val = options["max_errors"]; - parser_options.max_errors = max_errors_val.isNull() ? 0 : (uint32_t) max_errors_val.as(); - } - } + herb_extract_parser_options(options, &parser_options); hb_allocator_T allocator; if (!hb_allocator_init(&allocator, HB_ALLOCATOR_ARENA)) {