From 7d19fa78e5224aea625e7f0327287a65a0086085 Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Tue, 7 Jul 2026 21:49:29 +0100 Subject: [PATCH 01/17] Initial work on Native Markup Expressions --- Zend/tests/markup_operand_position_bc.phpt | 100 + Zend/zend_ast.c | 10 + Zend/zend_ast.h | 3 + Zend/zend_compile.c | 37 + Zend/zend_globals.h | 5 + Zend/zend_language_parser.y | 75 + Zend/zend_language_scanner.h | 1 + Zend/zend_language_scanner.l | 309 ++- Zend/zend_markup.c | 698 ++++++ Zend/zend_markup.h | 68 + Zend/zend_markup_entities.h | 2148 +++++++++++++++++ Zend/zend_markup_entities_gen.php | 101 + configure.ac | 1 + ext/html/CREDITS | 2 + ext/html/RFC.md | 593 +++++ ext/html/benchmarks/bench_component.php | 44 + ext/html/benchmarks/bench_render.php | 80 + ext/html/benchmarks/bench_scan.php | 32 + ext/html/config.m4 | 7 + ext/html/config.w32 | 8 + ext/html/html.c | 2006 +++++++++++++++ ext/html/html.stub.php | 314 +++ ext/html/html_arginfo.h | 271 +++ ext/html/markup2php | 53 + ext/html/php_html.h | 50 + ext/html/tests/attribute_htmlable.phpt | 24 + ext/html/tests/attributes.phpt | 35 + ext/html/tests/children_coercion.phpt | 37 + ext/html/tests/children_traversable.phpt | 43 + ext/html/tests/component_decorators.phpt | 45 + ext/html/tests/component_di_hooks.phpt | 90 + ext/html/tests/component_dispatch.phpt | 36 + .../tests/component_resolution_errors.phpt | 35 + ext/html/tests/component_scoped_hooks.phpt | 112 + ext/html/tests/component_slot_errors.phpt | 46 + ext/html/tests/component_slots.phpt | 47 + .../tests/component_throwing_constructor.phpt | 43 + ext/html/tests/deep_nesting.phpt | 25 + ext/html/tests/dispatch_hardening.phpt | 65 + ext/html/tests/dom_detached_node.phpt | 24 + ext/html/tests/dom_interop.phpt | 47 + ext/html/tests/dom_optional_document.phpt | 52 + ext/html/tests/element_basic.phpt | 34 + ext/html/tests/escaping.phpt | 22 + ext/html/tests/fragment_void_raw.phpt | 40 + ext/html/tests/htmlable_default_tostring.phpt | 71 + ext/html/tests/htmlable_missing_tohtml.phpt | 12 + ext/html/tests/htmlable_tohtml.phpt | 64 + ext/html/tests/htmlable_tohtml_errors.phpt | 44 + ext/html/tests/htmlable_tohtml_opcache.phpt | 34 + ext/html/tests/security_names.phpt | 34 + ext/html/tests/slot_attribute.phpt | 31 + ext/html/tests/syntax_attr_prefix.phpt | 68 + ext/html/tests/syntax_attributes.phpt | 25 + ext/html/tests/syntax_basic.phpt | 36 + ext/html/tests/syntax_bc.phpt | 46 + ext/html/tests/syntax_braces.phpt | 30 + ext/html/tests/syntax_childless.phpt | 27 + ext/html/tests/syntax_comments.phpt | 28 + .../tests/syntax_component_hyphenated.phpt | 28 + .../tests/syntax_component_namespace.phpt | 37 + .../syntax_component_namespace_fallback.phpt | 40 + .../tests/syntax_component_qualified.phpt | 58 + ext/html/tests/syntax_components.phpt | 57 + ext/html/tests/syntax_doctype.phpt | 28 + ext/html/tests/syntax_doctype_errors.phpt | 27 + ext/html/tests/syntax_dynamic.phpt | 112 + ext/html/tests/syntax_dynamic_errors.phpt | 78 + ext/html/tests/syntax_entities.phpt | 55 + ext/html/tests/syntax_entities_tokenizer.phpt | 19 + ext/html/tests/syntax_greeting.phpt | 18 + ext/html/tests/syntax_lineno.phpt | 37 + ext/html/tests/syntax_mismatch.phpt | 10 + ext/html/tests/syntax_named_slots.phpt | 46 + ext/html/tests/syntax_self_close_output.phpt | 25 + ext/html/tests/syntax_slot_mismatch.phpt | 11 + ext/html/tests/syntax_spread.phpt | 40 + ext/html/tests/syntax_whitespace.phpt | 37 + ext/html/tests/transpile.phpt | 49 + ext/tokenizer/tokenizer_data.c | 11 + ext/tokenizer/tokenizer_data.stub.php | 55 + ext/tokenizer/tokenizer_data_arginfo.h | 13 +- win32/build/config.w32 | 2 +- 83 files changed, 9358 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/markup_operand_position_bc.phpt create mode 100644 Zend/zend_markup.c create mode 100644 Zend/zend_markup.h create mode 100644 Zend/zend_markup_entities.h create mode 100644 Zend/zend_markup_entities_gen.php create mode 100644 ext/html/CREDITS create mode 100644 ext/html/RFC.md create mode 100644 ext/html/benchmarks/bench_component.php create mode 100644 ext/html/benchmarks/bench_render.php create mode 100644 ext/html/benchmarks/bench_scan.php create mode 100644 ext/html/config.m4 create mode 100644 ext/html/config.w32 create mode 100644 ext/html/html.c create mode 100644 ext/html/html.stub.php create mode 100644 ext/html/html_arginfo.h create mode 100755 ext/html/markup2php create mode 100644 ext/html/php_html.h create mode 100644 ext/html/tests/attribute_htmlable.phpt create mode 100644 ext/html/tests/attributes.phpt create mode 100644 ext/html/tests/children_coercion.phpt create mode 100644 ext/html/tests/children_traversable.phpt create mode 100644 ext/html/tests/component_decorators.phpt create mode 100644 ext/html/tests/component_di_hooks.phpt create mode 100644 ext/html/tests/component_dispatch.phpt create mode 100644 ext/html/tests/component_resolution_errors.phpt create mode 100644 ext/html/tests/component_scoped_hooks.phpt create mode 100644 ext/html/tests/component_slot_errors.phpt create mode 100644 ext/html/tests/component_slots.phpt create mode 100644 ext/html/tests/component_throwing_constructor.phpt create mode 100644 ext/html/tests/deep_nesting.phpt create mode 100644 ext/html/tests/dispatch_hardening.phpt create mode 100644 ext/html/tests/dom_detached_node.phpt create mode 100644 ext/html/tests/dom_interop.phpt create mode 100644 ext/html/tests/dom_optional_document.phpt create mode 100644 ext/html/tests/element_basic.phpt create mode 100644 ext/html/tests/escaping.phpt create mode 100644 ext/html/tests/fragment_void_raw.phpt create mode 100644 ext/html/tests/htmlable_default_tostring.phpt create mode 100644 ext/html/tests/htmlable_missing_tohtml.phpt create mode 100644 ext/html/tests/htmlable_tohtml.phpt create mode 100644 ext/html/tests/htmlable_tohtml_errors.phpt create mode 100644 ext/html/tests/htmlable_tohtml_opcache.phpt create mode 100644 ext/html/tests/security_names.phpt create mode 100644 ext/html/tests/slot_attribute.phpt create mode 100644 ext/html/tests/syntax_attr_prefix.phpt create mode 100644 ext/html/tests/syntax_attributes.phpt create mode 100644 ext/html/tests/syntax_basic.phpt create mode 100644 ext/html/tests/syntax_bc.phpt create mode 100644 ext/html/tests/syntax_braces.phpt create mode 100644 ext/html/tests/syntax_childless.phpt create mode 100644 ext/html/tests/syntax_comments.phpt create mode 100644 ext/html/tests/syntax_component_hyphenated.phpt create mode 100644 ext/html/tests/syntax_component_namespace.phpt create mode 100644 ext/html/tests/syntax_component_namespace_fallback.phpt create mode 100644 ext/html/tests/syntax_component_qualified.phpt create mode 100644 ext/html/tests/syntax_components.phpt create mode 100644 ext/html/tests/syntax_doctype.phpt create mode 100644 ext/html/tests/syntax_doctype_errors.phpt create mode 100644 ext/html/tests/syntax_dynamic.phpt create mode 100644 ext/html/tests/syntax_dynamic_errors.phpt create mode 100644 ext/html/tests/syntax_entities.phpt create mode 100644 ext/html/tests/syntax_entities_tokenizer.phpt create mode 100644 ext/html/tests/syntax_greeting.phpt create mode 100644 ext/html/tests/syntax_lineno.phpt create mode 100644 ext/html/tests/syntax_mismatch.phpt create mode 100644 ext/html/tests/syntax_named_slots.phpt create mode 100644 ext/html/tests/syntax_self_close_output.phpt create mode 100644 ext/html/tests/syntax_slot_mismatch.phpt create mode 100644 ext/html/tests/syntax_spread.phpt create mode 100644 ext/html/tests/syntax_whitespace.phpt create mode 100644 ext/html/tests/transpile.phpt diff --git a/Zend/tests/markup_operand_position_bc.phpt b/Zend/tests/markup_operand_position_bc.phpt new file mode 100644 index 000000000000..5dcff3286666 --- /dev/null +++ b/Zend/tests/markup_operand_position_bc.phpt @@ -0,0 +1,100 @@ +--TEST-- +Native markup: bare "<" after operand-ending tokens stays a comparison (no-space BC) +--FILE-- + 1 }check(new Base)); + +// Bare `exit` is an expression operand; "<" after it is a comparison. +// (Only reached when the left side is falsy-compared, so guard it.) +function never_exit(): bool { return false && exit` operator is untouched in infix (operator) position. +var_dump($a[0]<>2); + +// Bare `yield` is a complete operand: `<` and `<>` after it stay comparisons +// ((yield) != 5, (yield) < FOO), with or without spaces. Yielding markup +// therefore needs parentheses: `yield (
)`. +function gen_ne() { var_dump(yield <> 5); } +$g = gen_ne(); $g->current(); $g->send(7); +function gen_lt() { var_dump(yield < FOO); } +$g = gen_lt(); $g->current(); $g->send("aaa"); +function gen_lt_nospace() { $r = yieldcurrent(); $g->send("aaa"); + +// Inverse direction: in operand position (after `=`, `(`, `,`, `=>`, `return`, +// and parenthesized after `yield`) a "; + take(

hi

,
); + $f = fn() => a; + yield (
  • item
  • ); + return done; +} + +// Dynamic tags: "<$tag" and "<{expr}" in operand position are markup +// ("$i--<$n" above is the operator-position inverse, still a comparison). +function never_runs_dynamic() { + $tag = 'div'; + $x = <$tag class="a">hi; + $y = <{$tag . 'x'}>hi; + take(<{pick()}/>); + return <$tag/>; +} +echo "parsed\n"; +?> +--EXPECT-- +bool(true) +bool(true) +int(4) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +parsed diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index d3ce419c737e..aef7325f3d10 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2574,6 +2574,16 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } smart_str_appends(str, "::class"); break; + case ZEND_AST_CALLABLE_NAME: + /* Only produced by markup component lowering, which is a call and so + * never reaches a const-expr export; handled for completeness. It + * compiles to a constant function name (or candidate-name list, see + * zend_compile_callable_name), so export the as-written name quoted + * rather than as a bare constant. */ + smart_str_appendc(str, '\''); + zend_ast_export_ns_name(str, ast->child[0], 0, indent); + smart_str_appendc(str, '\''); + break; case ZEND_AST_ASSIGN: BINARY_OP(" = ", 90, 91, 90); case ZEND_AST_ASSIGN_REF: BINARY_OP(" =& ", 90, 91, 90); case ZEND_AST_ASSIGN_OP: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 24b77d7d3493..aa59f1e2b720 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -111,6 +111,9 @@ enum _zend_ast_kind { ZEND_AST_BREAK, ZEND_AST_CONTINUE, ZEND_AST_PROPERTY_HOOK_SHORT_BODY, + /* Native markup: declared last in its group so it appends to the enum + * instead of renumbering the kinds after it (which would break ext/php-ast). */ + ZEND_AST_CALLABLE_NAME, /* 2 child nodes */ ZEND_AST_DIM = 2 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9f8ebad8ab29..aa1cf4f4c67e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11425,6 +11425,40 @@ static void zend_compile_class_name(znode *result, const zend_ast *ast) /* {{{ * } /* }}} */ +/* Native markup (RFC: Native Markup Expressions). Resolve a component tag's name + * against the *function* import table (`use function`) and current namespace, + * yielding a compile-time constant: a single name, or a candidate list (current + * namespace, then global) when an unqualified name in a namespace needs PHP's + * usual global fallback. This is the function-table analogue of + * ZEND_AST_CLASS_NAME: a `` tag is lowered to two candidate names — a + * class one (ZEND_AST_CLASS_NAME) and a function one (this) — so the runtime's + * class-then-function dispatch can honor `use` and `use function` independently, + * exactly as PHP resolves class vs call names. See zend_ast_create_markup_component + * in Zend/zend_markup.c. */ +static void zend_compile_callable_name(znode *result, const zend_ast *ast) /* {{{ */ +{ + zend_ast *name_ast = ast->child[0]; + zend_string *name = zend_ast_get_str(name_ast); + bool is_fully_qualified; + zend_string *resolved = + zend_resolve_function_name(name, name_ast->attr, &is_fully_qualified); + + result->op_type = IS_CONST; + if (!is_fully_qualified && !zend_string_equals(resolved, name)) { + /* An unqualified name inside a namespace resolves like an ordinary + * call: current namespace first, then the global namespace. Pass both + * candidates, in that order, for the runtime to try. */ + zval candidates; + array_init_size(&candidates, 2); + add_next_index_str(&candidates, resolved); + add_next_index_str(&candidates, zend_string_copy(name)); + ZVAL_COPY_VALUE(&result->u.constant, &candidates); + } else { + ZVAL_STR(&result->u.constant, resolved); + } +} +/* }}} */ + static zend_op *zend_compile_rope_add_ex(zend_op *opline, znode *result, uint32_t num, znode *elem_node) /* {{{ */ { if (num == 0) { @@ -12223,6 +12257,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_CLASS_NAME: zend_compile_class_name(result, ast); return; + case ZEND_AST_CALLABLE_NAME: + zend_compile_callable_name(result, ast); + return; case ZEND_AST_ENCAPS_LIST: zend_compile_encaps_list(result, ast); return; diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 61499c0cc23d..062cf62f3a9c 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -402,6 +402,11 @@ struct _zend_php_scanner_globals { /* initial string length after scanning to first variable */ int scanned_string_len; + /* Last significant token returned, used to decide whether a bare "<" in + * ST_IN_SCRIPTING is in operand position (begins native markup) or operator + * position (a comparison). See zend_language_scanner.l. */ + int last_token; + /* hooks */ void (*on_event)( zend_php_scanner_event event, int token, int line, diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index b4dda00404ea..e6c2f5c82c8d 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -26,6 +26,7 @@ #include "zend_constants.h" #include "zend_language_scanner.h" #include "zend_exceptions.h" +#include "zend_markup.h" #define YYSIZE_T size_t #define yytnamerr zend_yytnamerr @@ -250,6 +251,21 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); /* Token used to force a parse error from the lexer */ %token T_ERROR +/* Native markup tokens. Declared last so they append to the token enum: + * inserting them mid-list would renumber every later token (T_INLINE_HTML + * onward), breaking code that hardcodes token integers. */ +%token T_MARKUP_NAME "markup tag name" +%token T_MARKUP_SLOT_NAME "markup slot name" +%token T_MARKUP_TEXT "markup text" +%token T_MARKUP_ATTR_VALUE "markup attribute value" +%token T_MARKUP_COMMENT "markup comment" +%token T_MARKUP_OPEN "markup tag start" +%token T_MARKUP_CLOSE_OPEN "markup closing tag start" +%token T_MARKUP_TAG_END "markup tag end" +%token T_MARKUP_SELF_CLOSE "markup self-close" +%token T_MARKUP_INTERP_START "markup interpolation start" +%token T_MARKUP_DOCTYPE "markup doctype" + %type top_statement namespace_name name statement function_declaration_statement %type class_declaration_statement trait_declaration_statement legacy_namespace_name %type interface_declaration_statement interface_extends_list @@ -283,6 +299,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type attributed_statement attributed_top_statement attributed_class_statement attributed_parameter %type attribute_decl attribute attributes attribute_group namespace_declaration_name %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list +%type markup_element markup_children markup_child markup_slot +%type markup_attributes markup_attribute %type enum_declaration_statement enum_backing_type enum_case enum_case_expr %type function_name non_empty_member_modifiers %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body @@ -1249,9 +1267,66 @@ new_non_dereferenceable: { $$ = zend_ast_create(ZEND_AST_NEW, $2, zend_ast_create_list(0, ZEND_AST_ARG_LIST)); } ; +markup_element: + T_MARKUP_OPEN T_MARKUP_NAME markup_attributes T_MARKUP_TAG_END markup_children T_MARKUP_CLOSE_OPEN T_MARKUP_NAME T_MARKUP_TAG_END + { $$ = zend_ast_create_markup_checked($2, $3, $5, $7); if (!$$) { YYERROR; } } + | T_MARKUP_OPEN T_MARKUP_NAME markup_attributes T_MARKUP_SELF_CLOSE + { $$ = zend_ast_create_markup_element($2, $3, zend_ast_create_list(0, ZEND_AST_ARRAY)); if (!$$) { YYERROR; } } + | T_MARKUP_OPEN T_MARKUP_TAG_END markup_children T_MARKUP_CLOSE_OPEN T_MARKUP_TAG_END + { $$ = zend_ast_create_markup_element(NULL, NULL, $3); if (!$$) { YYERROR; } } + | T_MARKUP_OPEN T_VARIABLE markup_attributes T_MARKUP_TAG_END markup_children T_MARKUP_CLOSE_OPEN T_VARIABLE T_MARKUP_TAG_END + { $$ = zend_ast_create_markup_dynamic_checked($2, $3, $5, $7); if (!$$) { YYERROR; } } + | T_MARKUP_OPEN T_VARIABLE markup_attributes T_MARKUP_SELF_CLOSE + { $$ = zend_ast_create_markup_dynamic($2, $3, zend_ast_create_list(0, ZEND_AST_ARRAY)); } + | T_MARKUP_OPEN T_MARKUP_INTERP_START expr '}' markup_attributes T_MARKUP_TAG_END markup_children T_MARKUP_CLOSE_OPEN T_MARKUP_TAG_END + { $$ = zend_ast_create_markup_dynamic_expr($3, $5, $7); } + | T_MARKUP_OPEN T_MARKUP_INTERP_START expr '}' markup_attributes T_MARKUP_SELF_CLOSE + { $$ = zend_ast_create_markup_dynamic_expr($3, $5, zend_ast_create_list(0, ZEND_AST_ARRAY)); } +; + +markup_attributes: + %empty { $$ = zend_ast_create_list(0, ZEND_AST_ARRAY); } + | markup_attributes markup_attribute { $$ = zend_ast_list_add($1, $2); } +; + +markup_attribute: + T_MARKUP_NAME '=' T_MARKUP_ATTR_VALUE + { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); } + | T_MARKUP_NAME '=' T_MARKUP_INTERP_START expr '}' + { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $4, $1); } + | T_MARKUP_NAME + { zval t; ZVAL_TRUE(&t); $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, zend_ast_create_zval(&t), $1); } + | T_MARKUP_INTERP_START T_ELLIPSIS expr '}' + { $$ = zend_ast_create(ZEND_AST_UNPACK, $3); } +; + +markup_children: + %empty { $$ = zend_ast_create_list(0, ZEND_AST_ARRAY); } + | markup_children markup_child { $$ = zend_ast_list_add($1, $2); } +; + +markup_child: + T_MARKUP_TEXT { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); } + | T_MARKUP_COMMENT { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, zend_ast_create_markup_raw($1), NULL); } + | T_MARKUP_DOCTYPE { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, zend_ast_create_markup_raw($1), NULL); } + | T_MARKUP_INTERP_START expr '}' { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $2, NULL); } + | T_MARKUP_INTERP_START '}' { zval n; ZVAL_NULL(&n); $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, zend_ast_create_zval(&n), NULL); } + | markup_element { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); } + | markup_slot { $$ = $1; } +; + +markup_slot: + T_MARKUP_OPEN T_MARKUP_SLOT_NAME markup_attributes T_MARKUP_TAG_END markup_children T_MARKUP_CLOSE_OPEN T_MARKUP_SLOT_NAME T_MARKUP_TAG_END + { $$ = zend_ast_create_markup_slot($2, $3, $5, $7); if (!$$) { YYERROR; } } + | T_MARKUP_OPEN T_MARKUP_SLOT_NAME markup_attributes T_MARKUP_SELF_CLOSE + { $$ = zend_ast_create_markup_slot($2, $3, NULL, NULL); if (!$$) { YYERROR; } } +; + expr: variable { $$ = $1; } + | markup_element + { $$ = $1; } | T_LIST '(' array_pair_list ')' '=' expr { $3->attr = ZEND_ARRAY_SYNTAX_LIST; $$ = zend_ast_create(ZEND_AST_ASSIGN, $3, $6); } | '[' array_pair_list ']' '=' expr diff --git a/Zend/zend_language_scanner.h b/Zend/zend_language_scanner.h index e502d91411b5..1415e2fa991d 100644 --- a/Zend/zend_language_scanner.h +++ b/Zend/zend_language_scanner.h @@ -30,6 +30,7 @@ typedef struct _zend_lex_state { unsigned char *yy_marker; unsigned char *yy_limit; int yy_state; + int last_token; /* operand-vs-operator position for native markup */ zend_stack state_stack; zend_ptr_stack heredoc_label_stack; zend_stack nest_location_stack; /* for syntax error reporting */ diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 07f2d44cb5c6..490715becce8 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -46,6 +46,7 @@ #include "zend_strtod.h" #include "zend_exceptions.h" #include "zend_virtual_cwd.h" +#include "zend_markup.h" #define YYCTYPE unsigned char #define YYFILL(n) { if ((YYCURSOR + n) >= (YYLIMIT + ZEND_MMAP_AHEAD)) { return 0; } } @@ -194,6 +195,7 @@ void startup_scanner(void) zend_stack_init(&SCNG(nest_location_stack), sizeof(zend_nest_location)); zend_ptr_stack_init(&SCNG(heredoc_label_stack)); SCNG(heredoc_scan_ahead) = 0; + SCNG(last_token) = 0; } static void heredoc_label_dtor(zend_heredoc_label *heredoc_label) { @@ -233,6 +235,7 @@ ZEND_API void zend_save_lexical_state(zend_lex_state *lex_state) lex_state->in = SCNG(yy_in); lex_state->yy_state = YYSTATE; + lex_state->last_token = SCNG(last_token); lex_state->filename = CG(compiled_filename); lex_state->lineno = CG(zend_lineno); CG(compiled_filename) = NULL; @@ -273,6 +276,7 @@ ZEND_API void zend_restore_lexical_state(zend_lex_state *lex_state) SCNG(yy_in) = lex_state->in; YYSETCONDITION(lex_state->yy_state); + SCNG(last_token) = lex_state->last_token; CG(zend_lineno) = lex_state->lineno; zend_restore_compiled_filename(lex_state->filename); @@ -543,6 +547,7 @@ ZEND_API zend_result open_file_for_scanning(zend_file_handle *file_handle) /* Reset the scanner for scanning the new file */ SCNG(yy_in) = file_handle; SCNG(yy_start) = NULL; + SCNG(last_token) = 0; if (size != (size_t)-1) { if (CG(multibyte)) { @@ -744,6 +749,7 @@ ZEND_API void zend_prepare_string_for_scanning(zval *str, zend_string *filename) SCNG(yy_in) = NULL; SCNG(yy_start) = NULL; + SCNG(last_token) = 0; buf = Z_STRVAL_P(str); size = old_len; @@ -1357,6 +1363,71 @@ static zend_result check_nesting_at_end(void) } \ } while (0) +/* Native markup (RFC: Native Markup Expressions). A bare "<" in ST_IN_SCRIPTING + * begins a markup expression only in *operand* position (where a value, not an + * operator, is expected). We approximate operand-vs-operator position from the + * last significant token: if it can end an operand, the "<" is a comparison; + * otherwise a value is expected and "" begins markup. Because "<" + * immediately followed by a letter or ">" in operand position is always a syntax + * error today, reclaiming it is backward compatible. + * + * Known limitation: "}" must stay an operand ender (match/array/interpolation + * results), so markup as an expression statement directly after a block — + * `if ($c) { ... }

    hi

    ;` — lexes as a comparison and fails to parse. + * Assign or echo the markup instead. Contributors adding a new token that can + * END an expression operand must add it to this switch, or "NEWTOKEN < X" + * silently becomes a markup parse error. */ +static bool zend_markup_operand_position(void) +{ + switch (SCNG(last_token)) { + /* tokens that END an operand → the following "<" is a comparison */ + case T_VARIABLE: + case T_LNUMBER: + case T_DNUMBER: + case T_NUM_STRING: + case T_CONSTANT_ENCAPSED_STRING: + case T_STRING: + case T_NAME_FULLY_QUALIFIED: + case T_NAME_QUALIFIED: + case T_NAME_RELATIVE: + case T_LINE: + case T_FILE: + case T_DIR: + case T_CLASS_C: + case T_TRAIT_C: + case T_METHOD_C: + case T_FUNC_C: + case T_NS_C: + case T_PROPERTY_C: + case T_END_HEREDOC: + /* postfix increment/decrement and the `class` keyword in `Foo::class` + * also END an operand: `$i++ 5` and + * `yield < LIM` are valid PHP, so a "<" after `yield` stays a + * comparison and BC is preserved. The cost is that yielding markup + * needs parentheses — `yield (
    )` — which the RFC documents. + * (`yield from` requires an operand, so markup after it just works.) */ + case T_YIELD: + case ')': + case ']': + case '}': + case '"': /* doubles as the closing quote: string states intervene */ + case '`': + return false; + default: + return true; + } +} + int ZEND_FASTCALL lex_scan(zval *zendlval, zend_parser_stack_elem *elem) { int token; @@ -1842,7 +1913,7 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN(T_IS_EQUAL); } -"!="|"<>" { +"!=" { RETURN_TOKEN(T_IS_NOT_EQUAL); } @@ -1977,6 +2048,235 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ } +/* Native markup (RFC: Native Markup Expressions). In operand position, "" a fragment; in operator position both keep + * their existing meaning ("<" comparison, "<>" not-equal). Nested elements + * inside markup content use a bare "<" handled by ST_MARKUP_CONTENT. */ +"<""\\"?[a-zA-Z] { + /* ""<>" { + if (zend_markup_operand_position()) { + yyless(1); + yy_push_state(ST_MARKUP_TAG); + RETURN_TOKEN(T_MARKUP_OPEN); + } + RETURN_TOKEN(T_IS_NOT_EQUAL); +} + +"<""$"{LABEL} { + /* "<$tag": a dynamic tag whose variable's runtime value names the element + * or component (<$tag>…). Only markup in operand position; in + * operator position "<" keeps its comparison meaning ("$a <$b"). */ + yyless(1); + if (zend_markup_operand_position()) { + yy_push_state(ST_MARKUP_TAG); + RETURN_TOKEN(T_MARKUP_OPEN); + } + RETURN_TOKEN('<'); +} + +"<{" { + /* "<{expr}": a dynamic tag computed by an arbitrary expression, closed + * anonymously ("") or self-closed. The "{" is left for ST_MARKUP_TAG's + * own interpolation rule. Only markup in operand position; in operator + * position "<" keeps its comparison meaning. */ + yyless(1); + if (zend_markup_operand_position()) { + yy_push_state(ST_MARKUP_TAG); + RETURN_TOKEN(T_MARKUP_OPEN); + } + RETURN_TOKEN('<'); +} + +{WHITESPACE} { + /* Count newlines and emit T_WHITESPACE in tokenizer mode, like ordinary + * whitespace (a bare restart would freeze line numbers and swallow the + * whitespace from token_get_all). */ + goto return_whitespace; +} + +"slot:"[a-zA-Z][a-zA-Z0-9_.-]* { + /* a named-slot block ; strip the "slot:" prefix */ + RETURN_TOKEN_WITH_STR(T_MARKUP_SLOT_NAME, sizeof("slot:") - 1); +} + +"\\"?[a-zA-Z][a-zA-Z0-9_.:-]*("\\"[a-zA-Z][a-zA-Z0-9_.:-]*)* { + /* A tag name, optionally namespace-qualified () or fully + * qualified (<\App\Card/>). A name containing "\" always dispatches as a + * component (see zend_markup_name_is_component in Zend/zend_markup.c). */ + RETURN_TOKEN_WITH_STR(T_MARKUP_NAME, 0); +} + +[:@][a-zA-Z][a-zA-Z0-9_.:-]* { + /* An attribute name with a ":" or "@" shorthand prefix (Vue/Alpine: + * :class, @click). Attribute position only: this can never lex as a tag + * name, because every markup-open rule requires a letter, "\", "$", "{" + * or ">" after "<", and ST_MARKUP_CLOSE is excluded here. */ + RETURN_TOKEN_WITH_STR(T_MARKUP_NAME, 0); +} + +"$"{LABEL} { + /* A dynamic tag name (<$tag>…): an ordinary variable whose runtime + * value decides element vs component (see Html\render_dynamic). */ + RETURN_TOKEN_WITH_STR(T_VARIABLE, 1); +} + +"=" { + RETURN_TOKEN('='); +} + +"\"" [^"]* "\"" { + /* Literal double-quoted attribute value (escaped at render time). The + * quotes stay part of the token text (yyleng untouched) so token_get_all + * round-trips; only the zval payload is the unquoted interior, with + * character references decoded for the compiler. */ + HANDLE_NEWLINES(yytext, yyleng); + zend_copy_value(zendlval, yytext + 1, yyleng - 2); + if (PARSER_MODE() && !SCNG(on_event) && Z_TYPE_P(zendlval) == IS_STRING) { + ZVAL_STR(zendlval, zend_markup_decode_entities(Z_STR_P(zendlval))); + } + RETURN_TOKEN_WITH_VAL(T_MARKUP_ATTR_VALUE); +} + +"{" { + yy_push_state(ST_IN_SCRIPTING); + enter_nesting('{'); + RETURN_TOKEN(T_MARKUP_INTERP_START); +} + +"/>" { + yy_pop_state(); + RETURN_TOKEN(T_MARKUP_SELF_CLOSE); +} + +">" { + BEGIN(ST_MARKUP_CONTENT); + RETURN_TOKEN(T_MARKUP_TAG_END); +} + +">" { + yy_pop_state(); + RETURN_TOKEN(T_MARKUP_TAG_END); +} + +{ANY_CHAR} { + if (YYCURSOR > YYLIMIT) { + RETURN_END_TOKEN; + } + RETURN_TOKEN(T_ERROR); +} + +""; + * content is literal (no interpolation). */ + while (YYCURSOR < YYLIMIT) { + if (YYCURSOR + 2 < YYLIMIT && YYCURSOR[0] == '-' && YYCURSOR[1] == '-' && YYCURSOR[2] == '>') { + YYCURSOR += 3; + yyleng = YYCURSOR - SCNG(yy_text); + HANDLE_NEWLINES(yytext, yyleng); + RETURN_TOKEN_WITH_STR(T_MARKUP_COMMENT, 0); + } + YYCURSOR++; + } + RETURN_TOKEN(T_ERROR); +} + +""; + * content is literal (no interpolation). */ + while (YYCURSOR < YYLIMIT) { + if (*YYCURSOR == '>') { + YYCURSOR++; + yyleng = YYCURSOR - SCNG(yy_text); + HANDLE_NEWLINES(yytext, yyleng); + RETURN_TOKEN_WITH_STR(T_MARKUP_DOCTYPE, 0); + } + YYCURSOR++; + } + RETURN_TOKEN(T_ERROR); +} + +"" shortcut (they end at "]]>" and may contain ">"), so + * reject with a targeted message rather than the generic stray-"<" one. */ + if (PARSER_MODE()) { + zend_throw_exception(zend_ce_parse_error, + "Unsupported markup declaration; only comments and are supported", 0); + } + RETURN_TOKEN(T_ERROR); +} + +""<" { + /* A nested tag (""); "= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') + || next == '\\' || next == '>' || next == '$' || next == '{') { + yy_push_state(ST_MARKUP_TAG); + RETURN_TOKEN(T_MARKUP_OPEN); + } + } + if (PARSER_MODE()) { + zend_throw_exception(zend_ce_parse_error, + "Unescaped \"<\" in markup text; write {'<'} for a literal less-than sign", 0); + } + RETURN_TOKEN(T_ERROR); +} + +"{" { + yy_push_state(ST_IN_SCRIPTING); + enter_nesting('{'); + RETURN_TOKEN(T_MARKUP_INTERP_START); +} + +{ANY_CHAR} { + if (YYCURSOR > YYLIMIT) { + RETURN_END_TOKEN; + } + /* Accumulate a run of literal text up to the next "<" or "{". */ + while (YYCURSOR < YYLIMIT) { + if (*YYCURSOR == '<' || *YYCURSOR == '{') { + break; + } + YYCURSOR++; + } + yyleng = YYCURSOR - SCNG(yy_text); + HANDLE_NEWLINES(yytext, yyleng); + /* Normalize whitespace for the compiler only; when collecting tokens + * (token_get_all) emit the raw text so the source round-trips. */ + if (PARSER_MODE() && !SCNG(on_event)) { + zend_string *normalized = zend_markup_normalize_text(yytext, yyleng); + if (ZSTR_LEN(normalized) == 0) { + zend_string_release(normalized); + goto restart; + } + ZVAL_STR(zendlval, zend_markup_decode_entities(normalized)); + RETURN_TOKEN_WITH_VAL(T_MARKUP_TEXT); + } + RETURN_TOKEN_WITH_STR(T_MARKUP_TEXT, 0); +} + + "${" { yy_push_state(ST_LOOKING_FOR_VARNAME); enter_nesting('{'); @@ -3196,12 +3496,19 @@ emit_token_with_val: } emit_token: + /* Track the last significant token for operand-vs-operator detection of a + * bare "<" (native markup). Whitespace/comments are skipped in parser mode + * but emitted in tokenizer mode, so ignore them here for a consistent view. */ + if (token != T_WHITESPACE && token != T_COMMENT && token != T_DOC_COMMENT) { + SCNG(last_token) = token; + } if (SCNG(on_event)) { SCNG(on_event)(ON_TOKEN, token, start_line, yytext, yyleng, SCNG(on_event_context)); } return token; emit_token_with_ident: + SCNG(last_token) = token; if (PARSER_MODE()) { elem->ident = SCNG(yy_text); } diff --git a/Zend/zend_markup.c b/Zend/zend_markup.c new file mode 100644 index 000000000000..16f91e1dbd74 --- /dev/null +++ b/Zend/zend_markup.c @@ -0,0 +1,698 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright © Zend Technologies Ltd., a subsidiary company of | + | Perforce Software, Inc., and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Liam Hammett | + +----------------------------------------------------------------------+ +*/ + +/* Native markup (RFC: Native Markup Expressions) compile-time support: + * + * - text normalization and character-reference decoding, called by the + * lexer (Zend/zend_language_scanner.l) on markup text and literal + * attribute values; + * - AST lowering, called by the grammar actions + * (Zend/zend_language_parser.y) to turn markup elements into ordinary + * `new \Html\Element(...)` / `\Html\render_component(...)` ASTs, so no + * new AST node kinds or zend_compile.c support are needed. + * + * The lexing mechanics themselves (scanner states, operand-position + * detection) stay in the scanner; everything here is pure data-to-data. */ + +#include "zend.h" +#include "zend_ast.h" +#include "zend_compile.h" +#include "zend_exceptions.h" +#include "zend_smart_str.h" +#include "zend_markup.h" +#include "zend_markup_entities.h" + +/* Native markup whitespace normalization (RFC §7), following the JSX/Babel rules: + * - split the text node into lines; + * - trim leading whitespace on every line but the first, and trailing + * whitespace on every line but the last; + * - drop lines that are empty after trimming; + * - join the surviving lines with a single space. + * The net effect: indentation/newlines between block elements vanish, while a + * meaningful single space between inline content is preserved. Returns a new + * zend_string (possibly empty — the caller drops empty text nodes). */ +typedef struct { const char *p; size_t n; } zend_markup_line; + +static zend_always_inline zend_markup_line *zend_markup_lines_grow( + zend_markup_line *lines, zend_markup_line *stackbuf, uint32_t count, uint32_t *cap) +{ + *cap *= 2; + zend_markup_line *grown = emalloc(*cap * sizeof(zend_markup_line)); + memcpy(grown, lines, count * sizeof(zend_markup_line)); + if (lines != stackbuf) { + efree(lines); + } + return grown; +} + +zend_string *zend_markup_normalize_text(const char *text, size_t len) +{ + zend_markup_line stackbuf[32]; + zend_markup_line *lines = stackbuf; + uint32_t cap = 32, count = 0; + size_t pos = 0; + + /* Split on \r\n | \n | \r (matching JS String.split), keeping a trailing + * empty line after a final break so the previous line counts as "not last". */ + for (;;) { + size_t e = pos; + while (e < len && text[e] != '\n' && text[e] != '\r') { + e++; + } + if (count == cap) { + lines = zend_markup_lines_grow(lines, stackbuf, count, &cap); + } + lines[count].p = text + pos; + lines[count].n = e - pos; + count++; + + if (e >= len) { + break; + } + if (text[e] == '\r' && e + 1 < len && text[e + 1] == '\n') { + e++; + } + pos = e + 1; + if (pos == len) { + if (count == cap) { + lines = zend_markup_lines_grow(lines, stackbuf, count, &cap); + } + lines[count].p = text + len; + lines[count].n = 0; + count++; + break; + } + } + + /* Index of the last line containing a non-space/tab char (default 0, as in Babel). */ + uint32_t last_nonblank = 0; + for (uint32_t k = 0; k < count; k++) { + for (size_t x = 0; x < lines[k].n; x++) { + if (lines[k].p[x] != ' ' && lines[k].p[x] != '\t') { + last_nonblank = k; + break; + } + } + } + + /* Output is never longer than the input. */ + zend_string *result = zend_string_alloc(len, 0); + char *out = ZSTR_VAL(result); + size_t outlen = 0; + + for (uint32_t k = 0; k < count; k++) { + const char *p = lines[k].p; + size_t s = 0, en = lines[k].n; + bool is_first = (k == 0); + bool is_last = (k == count - 1); + + if (!is_first) { + while (s < en && (p[s] == ' ' || p[s] == '\t')) s++; + } + if (!is_last) { + while (en > s && (p[en - 1] == ' ' || p[en - 1] == '\t')) en--; + } + if (en > s) { + for (size_t x = s; x < en; x++) { + out[outlen++] = (p[x] == '\t') ? ' ' : p[x]; + } + if (k != last_nonblank) { + out[outlen++] = ' '; + } + } + } + + if (lines != stackbuf) { + efree(lines); + } + + if (outlen == 0) { + zend_string_release(result); + return ZSTR_EMPTY_ALLOC(); + } + ZSTR_LEN(result) = outlen; + out[outlen] = '\0'; + return result; +} + +/* Native markup character references (RFC §7). Markup text and literal + * attribute values decode HTML character references at compile time: the + * frozen WHATWG named set (semicolon-terminated forms only; the standard + * guarantees the list will never grow) plus numeric { / 😀 forms. + * Decoding is lenient like HTML and JSX: anything that does not parse as a + * reference — a bare "&", an unknown name, an out-of-range or surrogate + * codepoint — stays literal text, so "fish & chips" needs no escaping. + * Decoded text is a plain string escaped at render time, so "&" in source + * round-trips to "&" in output. Text decodes after whitespace + * normalization, so " " survives it (the entity spelling of JSX's {' '}). */ + +static const zend_markup_entity *zend_markup_entity_lookup(const char *name, size_t len) +{ + uint32_t lo = 0, hi = ZEND_MARKUP_ENTITY_COUNT; + + while (lo < hi) { + uint32_t mid = lo + (hi - lo) / 2; + const zend_markup_entity *e = &zend_markup_entities[mid]; + int r = memcmp(name, e->name, MIN(len, e->name_len)); + if (r == 0) { + r = (len > e->name_len) - (len < e->name_len); + } + if (r == 0) { + return e; + } + if (r < 0) { + hi = mid; + } else { + lo = mid + 1; + } + } + return NULL; +} + +static size_t zend_markup_utf8_encode(char *out, uint32_t cp) +{ + if (cp < 0x80) { + out[0] = (char)cp; + return 1; + } + if (cp < 0x800) { + out[0] = (char)(0xC0 | (cp >> 6)); + out[1] = (char)(0x80 | (cp & 0x3F)); + return 2; + } + if (cp < 0x10000) { + out[0] = (char)(0xE0 | (cp >> 12)); + out[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); + out[2] = (char)(0x80 | (cp & 0x3F)); + return 3; + } + out[0] = (char)(0xF0 | (cp >> 18)); + out[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); + out[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); + out[3] = (char)(0x80 | (cp & 0x3F)); + return 4; +} + +/* Takes ownership of str; returns it unchanged when it contains no "&". */ +zend_string *zend_markup_decode_entities(zend_string *str) +{ + const char *p = ZSTR_VAL(str); + const char *end = p + ZSTR_LEN(str); + const char *amp = memchr(p, '&', ZSTR_LEN(str)); + smart_str buf = {0}; + + if (!amp) { + return str; + } + + while (amp) { + const char *q = amp + 1; + const char *repl = NULL; + size_t repl_len = 0; + char utf8[4]; + + smart_str_appendl(&buf, p, amp - p); + + if (q < end && *q == '#') { + /* Numeric reference: { or 😀 */ + uint32_t cp = 0; + bool overflow = false, hex = false; + const char *digits; + + q++; + if (q < end && (*q == 'x' || *q == 'X')) { + hex = true; + q++; + } + digits = q; + while (q < end) { + uint32_t d; + if (*q >= '0' && *q <= '9') { + d = *q - '0'; + } else if (hex && *q >= 'a' && *q <= 'f') { + d = *q - 'a' + 10; + } else if (hex && *q >= 'A' && *q <= 'F') { + d = *q - 'A' + 10; + } else { + break; + } + if (cp > 0x10FFFF) { + overflow = true; + } else { + cp = cp * (hex ? 16 : 10) + d; + } + q++; + } + if (q > digits && q < end && *q == ';' + && !overflow && cp > 0 && cp <= 0x10FFFF + && !(cp >= 0xD800 && cp <= 0xDFFF)) { + repl_len = zend_markup_utf8_encode(utf8, cp); + repl = utf8; + q++; + } + } else { + /* Named reference: &name; (case-sensitive, ASCII alphanumeric) */ + const char *name = q; + while (q < end && (size_t)(q - name) <= ZEND_MARKUP_ENTITY_MAX_NAME + && ((*q >= 'a' && *q <= 'z') || (*q >= 'A' && *q <= 'Z') + || (*q >= '0' && *q <= '9'))) { + q++; + } + if (q > name && q < end && *q == ';') { + const zend_markup_entity *e = zend_markup_entity_lookup(name, q - name); + if (e) { + repl = e->utf8; + repl_len = e->utf8_len; + q++; + } + } + } + + if (repl) { + smart_str_appendl(&buf, repl, repl_len); + p = q; + } else { + smart_str_appendc(&buf, '&'); + p = amp + 1; + } + amp = (p < end) ? memchr(p, '&', end - p) : NULL; + } + smart_str_appendl(&buf, p, end - p); + + zend_string_release_ex(str, 0); + return smart_str_extract(&buf); +} + +/* Native markup AST lowering (RFC: Native Markup Expressions, Phase 2/3). + * Lower a markup element into an ordinary `new \Html\Element(tag, [], [children])` + * (or `new \Html\Fragment([children])` when the tag name is empty) so no new AST + * node kind or zend_compile.c support is needed. `name` is the tag-name zval AST + * or NULL for a fragment; `children` is a ZEND_AST_ARRAY of ZEND_AST_ARRAY_ELEM. + * + * All lowering targets are named through the ZEND_MARKUP_*_FQ macros (see + * zend_markup.h) so the markup-to-runtime vocabulary has a single home. */ + +/* Build a fully-qualified name AST for one of the ZEND_MARKUP_*_FQ symbols. */ +static zend_ast *zend_ast_markup_fq_name(const char *name, size_t len) +{ + zend_ast *ast = zend_ast_create_zval_from_str(zend_string_init(name, len, 0)); + ast->attr = ZEND_NAME_FQ; + return ast; +} + +/* Element vs component classification (RFC §4): a tag whose first character is + * uppercase, which is namespace-qualified (contains "\"), or which names a static + * method via "::", dispatches as a component; anything else is a literal HTML + * element. The single home for this rule: the compiler applies it to static tag + * names here, and Html\render_dynamic applies it to a dynamic tag's runtime + * value (hence ZEND_API). */ +ZEND_API bool zend_markup_name_is_component(zend_string *tag) +{ + return (ZSTR_LEN(tag) > 0 && ZSTR_VAL(tag)[0] >= 'A' && ZSTR_VAL(tag)[0] <= 'Z') + || memchr(ZSTR_VAL(tag), '\\', ZSTR_LEN(tag)) != NULL + || strstr(ZSTR_VAL(tag), "::") != NULL; +} + +/* Wrap a list of children in `new \Html\Fragment([children])`. Consumes (and, if + * NULL, creates) the children list. */ +static zend_ast *zend_ast_wrap_fragment(zend_ast *children) +{ + if (children == NULL) { + children = zend_ast_create_list(0, ZEND_AST_ARRAY); + } + children->attr = ZEND_ARRAY_SYNTAX_SHORT; + + zend_ast *frag_class = zend_ast_markup_fq_name(ZEND_STRL(ZEND_MARKUP_FRAGMENT_FQ)); + + return zend_ast_create(ZEND_AST_NEW, frag_class, + zend_ast_create_list(1, ZEND_AST_ARG_LIST, children)); +} + +/* Wrap a string AST in a `\Html\raw(...)` call (trusted passthrough). Used for + * `` comments, which are emitted as literal HTML (RFC §7). */ +zend_ast *zend_ast_create_markup_raw(zend_ast *str) +{ + zend_ast *fn = zend_ast_markup_fq_name(ZEND_STRL(ZEND_MARKUP_RAW_FQ)); + return zend_ast_create(ZEND_AST_CALL, fn, + zend_ast_create_list(1, ZEND_AST_ARG_LIST, str)); +} + +/* Build a `` block (RFC §6) as a *keyed* array element + * `'name' => new \Html\Fragment([children])`. The component builder later routes + * keyed elements to render_component's namedSlots argument. `close` is consumed. */ +zend_ast *zend_ast_create_markup_slot(zend_ast *open, zend_ast *attrs, zend_ast *children, zend_ast *close) +{ + if (close != NULL) { + if (!zend_string_equals(Z_STR_P(zend_ast_get_zval(open)), Z_STR_P(zend_ast_get_zval(close)))) { + zend_throw_exception_ex(zend_ce_compile_error, 0, + "Mismatched markup closing tag: expected , found ", + Z_STRVAL_P(zend_ast_get_zval(open)), Z_STRVAL_P(zend_ast_get_zval(close))); + zend_ast_destroy(open); + zend_ast_destroy(attrs); + if (children != NULL) { + zend_ast_destroy(children); + } + zend_ast_destroy(close); + return NULL; + } + zend_ast_destroy(close); + } + /* A slot block routes body content only; silently discarding written + * attributes would hide a mistake. */ + if (attrs != NULL && zend_ast_get_list(attrs)->children > 0) { + zend_throw_exception_ex(zend_ce_compile_error, 0, + "Markup slot cannot have attributes", + Z_STRVAL_P(zend_ast_get_zval(open))); + zend_ast_destroy(open); + zend_ast_destroy(attrs); + if (children != NULL) { + zend_ast_destroy(children); + } + return NULL; + } + if (attrs != NULL) { + zend_ast_destroy(attrs); + } + return zend_ast_create(ZEND_AST_ARRAY_ELEM, zend_ast_wrap_fragment(children), open); +} + +/* Partition a markup body into loose content and blocks: keyed + * elements are named slots, everything else forms the anonymous slot (RFC §6). + * Consumes `children` (may be NULL); both outputs are fresh ZEND_AST_ARRAY + * lists with ZEND_ARRAY_SYNTAX_SHORT set. */ +static void zend_markup_partition_children(zend_ast *children, zend_ast **anon_out, zend_ast **named_out) +{ + zend_ast *anon = zend_ast_create_list(0, ZEND_AST_ARRAY); + zend_ast *named_slots = zend_ast_create_list(0, ZEND_AST_ARRAY); + + if (children != NULL) { + zend_ast_list *clist = zend_ast_get_list(children); + for (uint32_t i = 0; i < clist->children; i++) { + zend_ast *elem = clist->child[i]; + if (elem != NULL && elem->kind == ZEND_AST_ARRAY_ELEM && elem->child[1] != NULL) { + named_slots = zend_ast_list_add(named_slots, elem); + } else { + anon = zend_ast_list_add(anon, elem); + } + } + /* Detach the moved elements before freeing the now-empty container. */ + clist->children = 0; + zend_ast_destroy(children); + } + anon->attr = ZEND_ARRAY_SYNTAX_SHORT; + named_slots->attr = ZEND_ARRAY_SYNTAX_SHORT; + + *anon_out = anon; + *named_out = named_slots; +} + +/* Lower a component tag (capitalized name, namespace-qualified name, or + * "Class::method") into a call to + * \Html\render_component(component, props, slot, namedSlots, functionComponent) + * (RFC §3). Attributes become the props array; loose body content becomes a + * single \Html\Fragment passed as the anonymous slot, and blocks + * (keyed elements) are partitioned into the namedSlots array. + * + * Component resolution is two-stage. A bare tag could name either a class + * component or a function component, which PHP resolves through *separate* import + * tables (`use` vs `use function`). So this pass resolves the name *both* ways — + * a class candidate (ZEND_AST_CLASS_NAME, the `component` argument) and a function + * candidate (ZEND_AST_CALLABLE_NAME, the `functionComponent` argument) — and the + * runtime applies the dispatch order (Class::method, then Htmlable class, then + * userland function) across the two, so `use App\C;` resolves a class and + * `use function App\C;` resolves a function, exactly as ordinary PHP does. A + * leading "\" makes both candidates fully qualified. A "Class::method" tag is + * unambiguously a static method, so it has no function candidate. See + * PHP_FUNCTION(Html_render_component) in ext/html/html.c for the second stage. */ +static zend_ast *zend_ast_create_markup_component(zend_ast *name, zend_ast *attrs, zend_ast *children) +{ + zend_string *tag = zend_ast_get_str(name); + const char *sep = strstr(ZSTR_VAL(tag), "::"); + /* A leading "\" is a fully-qualified reference; otherwise resolve the name + * (unqualified or qualified) against the current use/namespace. */ + uint32_t name_type = (ZSTR_LEN(tag) > 0 && ZSTR_VAL(tag)[0] == '\\') + ? ZEND_NAME_FQ : ZEND_NAME_NOT_FQ; + zend_ast *component_name; + zend_ast *function_name; + + if (sep != NULL) { + /* "Class::method": resolve the class part as a class, then append + * "::method". A static method is never a plain function. */ + size_t class_len = sep - ZSTR_VAL(tag); + zend_ast *class_name = zend_ast_create_zval_from_str( + zend_string_init(ZSTR_VAL(tag), class_len, 0)); + class_name->attr = name_type; + + zval suffix; + ZVAL_STR(&suffix, zend_string_init(sep, ZSTR_LEN(tag) - class_len, 0)); + component_name = zend_ast_create_concat_op( + zend_ast_create(ZEND_AST_CLASS_NAME, class_name), + zend_ast_create_zval(&suffix)); + + zval null_zv; + ZVAL_NULL(&null_zv); + function_name = zend_ast_create_zval(&null_zv); + } else { + /* Resolve the name both as a class (honors `use`) and as a function + * (honors `use function`); the runtime picks between them. */ + zend_ast *class_name = zend_ast_create_zval_from_str(zend_string_copy(tag)); + class_name->attr = name_type; + component_name = zend_ast_create(ZEND_AST_CLASS_NAME, class_name); + + zend_ast *func_name = zend_ast_create_zval_from_str(zend_string_copy(tag)); + func_name->attr = name_type; + function_name = zend_ast_create(ZEND_AST_CALLABLE_NAME, func_name); + } + zend_ast_destroy(name); + + if (attrs == NULL) { + attrs = zend_ast_create_list(0, ZEND_AST_ARRAY); + } + attrs->attr = ZEND_ARRAY_SYNTAX_SHORT; + + /* Partition the body: keyed elements are blocks (named slots); + * everything else is loose content forming the anonymous slot (RFC §6). */ + zend_ast *anon, *named_slots; + zend_markup_partition_children(children, &anon, &named_slots); + + /* Anonymous slot: a single Html\Fragment, or null when there is no loose body. */ + zend_ast *slot; + if (zend_ast_get_list(anon)->children > 0) { + slot = zend_ast_wrap_fragment(anon); + } else { + zend_ast_destroy(anon); + zval null_zv; + ZVAL_NULL(&null_zv); + slot = zend_ast_create_zval(&null_zv); + } + + zend_ast *fn = zend_ast_markup_fq_name(ZEND_STRL(ZEND_MARKUP_COMPONENT_FQ)); + + zend_ast *args = zend_ast_create_list(0, ZEND_AST_ARG_LIST); + args = zend_ast_list_add(args, component_name); + args = zend_ast_list_add(args, attrs); + args = zend_ast_list_add(args, slot); + args = zend_ast_list_add(args, named_slots); + args = zend_ast_list_add(args, function_name); + + return zend_ast_create(ZEND_AST_CALL, fn, args); +} + +zend_ast *zend_ast_create_markup_element(zend_ast *name, zend_ast *attrs, zend_ast *children) +{ + zend_ast *class_name, *args, *result; + + /* The grammar reduces a markup element only once its closing tag has been + * scanned, so nodes created here would inherit the *close* line. Stamp the + * lowered expression with the line the element opens on instead, so errors + * in a multi-line element report like any multi-line call. The tag-name + * token carries that line; a fragment has none, but its children list is + * created as content begins. */ + uint32_t lineno = (name != NULL) + ? zend_ast_get_lineno(name) : zend_ast_get_lineno(children); + + /* A capitalized name, or one containing "::", is a component (RFC §3). */ + if (name != NULL && zend_markup_name_is_component(zend_ast_get_str(name))) { + result = zend_ast_create_markup_component(name, attrs, children); + result->lineno = lineno; + return result; + } + + /* blocks lower to keyed children and are only routed by the + * component builder above; in a plain element or fragment one would slip + * into the children array as a stray string key. */ + zend_ast_list *clist = zend_ast_get_list(children); + for (uint32_t i = 0; i < clist->children; i++) { + zend_ast *elem = clist->child[i]; + if (elem != NULL && elem->kind == ZEND_AST_ARRAY_ELEM && elem->child[1] != NULL) { + zend_throw_exception_ex(zend_ce_compile_error, 0, + "Markup slot is only allowed in a component body", + Z_STRVAL_P(zend_ast_get_zval(elem->child[1]))); + if (name != NULL) { + zend_ast_destroy(name); + } + if (attrs != NULL) { + zend_ast_destroy(attrs); + } + zend_ast_destroy(children); + return NULL; + } + } + + children->attr = ZEND_ARRAY_SYNTAX_SHORT; + + args = zend_ast_create_list(0, ZEND_AST_ARG_LIST); + + if (name == NULL) { + /* A fragment has no attributes (the grammar forbids them on `<>`). */ + if (attrs != NULL) { + zend_ast_destroy(attrs); + } + class_name = zend_ast_markup_fq_name(ZEND_STRL(ZEND_MARKUP_FRAGMENT_FQ)); + args = zend_ast_list_add(args, children); + } else { + if (attrs == NULL) { + attrs = zend_ast_create_list(0, ZEND_AST_ARRAY); + } + attrs->attr = ZEND_ARRAY_SYNTAX_SHORT; + class_name = zend_ast_markup_fq_name(ZEND_STRL(ZEND_MARKUP_ELEMENT_FQ)); + args = zend_ast_list_add(args, name); + args = zend_ast_list_add(args, attrs); + args = zend_ast_list_add(args, children); + } + + result = zend_ast_create(ZEND_AST_NEW, class_name, args); + result->lineno = lineno; + return result; +} + +/* Build a markup element after checking the closing tag matches the opener + * (RFC §7). Both NULL means a `<>…` fragment. `close` is consumed. */ +zend_ast *zend_ast_create_markup_checked(zend_ast *open, zend_ast *attrs, zend_ast *children, zend_ast *close) +{ + bool match; + if (open == NULL || close == NULL) { + match = (open == NULL && close == NULL); + } else { + match = zend_string_equals(Z_STR_P(zend_ast_get_zval(open)), Z_STR_P(zend_ast_get_zval(close))); + } + if (!match) { + zend_throw_exception_ex(zend_ce_compile_error, 0, + "Mismatched markup closing tag: expected , found ", + open ? Z_STRVAL_P(zend_ast_get_zval(open)) : "", + close ? Z_STRVAL_P(zend_ast_get_zval(close)) : ""); + goto fail; + } + if (close != NULL) { + zend_ast_destroy(close); + close = NULL; + } + + return zend_ast_create_markup_element(open, attrs, children); + +fail: + if (open != NULL) { + zend_ast_destroy(open); + } + if (attrs != NULL) { + zend_ast_destroy(attrs); + } + zend_ast_destroy(children); + if (close != NULL) { + zend_ast_destroy(close); + } + return NULL; +} + +/* Lower a dynamic tag `<$tag …>…` (RFC §4) into a call to + * \Html\render_dynamic($tag, attributes, children, namedSlots). The variable's + * runtime value decides what a static tag name decides at compile time, by the + * same classification rule (zend_markup_name_is_component): a component name + * dispatches through render_component's machinery (children become the + * anonymous-slot Fragment, hooks and decorators included), anything else + * constructs a literal \Html\Element. Only classification moves to runtime; + * the body is still partitioned into loose children vs blocks + * here, at compile time. `name` is the T_VARIABLE zval AST (the name without + * the "$"). */ +/* Build the \Html\render_dynamic(tag_expr, attributes, children, namedSlots) + * call both dynamic-tag forms lower into. `tag_expr` is an already-built + * expression AST producing the tag name; `lineno` is the opening-tag line + * (see zend_ast_create_markup_element for why it is stamped explicitly). */ +static zend_ast *zend_markup_dynamic_call(zend_ast *tag_expr, zend_ast *attrs, zend_ast *children, uint32_t lineno) +{ + if (attrs == NULL) { + attrs = zend_ast_create_list(0, ZEND_AST_ARRAY); + } + attrs->attr = ZEND_ARRAY_SYNTAX_SHORT; + + zend_ast *anon, *named_slots; + zend_markup_partition_children(children, &anon, &named_slots); + + zend_ast *fn = zend_ast_markup_fq_name(ZEND_STRL(ZEND_MARKUP_DYNAMIC_FQ)); + + zend_ast *args = zend_ast_create_list(0, ZEND_AST_ARG_LIST); + args = zend_ast_list_add(args, tag_expr); + args = zend_ast_list_add(args, attrs); + args = zend_ast_list_add(args, anon); + args = zend_ast_list_add(args, named_slots); + + zend_ast *result = zend_ast_create(ZEND_AST_CALL, fn, args); + result->lineno = lineno; + return result; +} + +zend_ast *zend_ast_create_markup_dynamic(zend_ast *name, zend_ast *attrs, zend_ast *children) +{ + uint32_t lineno = zend_ast_get_lineno(name); + + zend_ast *var = zend_ast_create(ZEND_AST_VAR, name); + var->lineno = lineno; + + return zend_markup_dynamic_call(var, attrs, children, lineno); +} + +/* The brace form `<{expr} …>…` / `<{expr} …/>`: the tag is an arbitrary + * expression, evaluated once. It closes anonymously (``, like a fragment) + * because a closing tag cannot restate the expression - re-evaluating it + * would run its side effects twice, and matching it textually is meaningless + * for computed values. */ +zend_ast *zend_ast_create_markup_dynamic_expr(zend_ast *expr, zend_ast *attrs, zend_ast *children) +{ + return zend_markup_dynamic_call(expr, attrs, children, zend_ast_get_lineno(expr)); +} + +/* Build a dynamic tag after checking the closing variable matches the opener: + * `<$a>…` is a compile error, exactly as `
    …` is. `close` is + * consumed. */ +zend_ast *zend_ast_create_markup_dynamic_checked(zend_ast *open, zend_ast *attrs, zend_ast *children, zend_ast *close) +{ + if (!zend_string_equals(Z_STR_P(zend_ast_get_zval(open)), Z_STR_P(zend_ast_get_zval(close)))) { + zend_throw_exception_ex(zend_ce_compile_error, 0, + "Mismatched markup closing tag: expected , found ", + Z_STRVAL_P(zend_ast_get_zval(open)), Z_STRVAL_P(zend_ast_get_zval(close))); + zend_ast_destroy(open); + if (attrs != NULL) { + zend_ast_destroy(attrs); + } + zend_ast_destroy(children); + zend_ast_destroy(close); + return NULL; + } + zend_ast_destroy(close); + + return zend_ast_create_markup_dynamic(open, attrs, children); +} diff --git a/Zend/zend_markup.h b/Zend/zend_markup.h new file mode 100644 index 000000000000..9a1d6142ec54 --- /dev/null +++ b/Zend/zend_markup.h @@ -0,0 +1,68 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright © Zend Technologies Ltd., a subsidiary company of | + | Perforce Software, Inc., and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Liam Hammett | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_MARKUP_H +#define ZEND_MARKUP_H + +#include "zend_ast.h" + +/* The fully-qualified names that native markup (RFC: Native Markup Expressions) + * lowers into. This is the single home for the markup-to-runtime vocabulary: + * the parser (Zend/zend_language_parser.y) lowers markup expressions to exactly + * these symbols, and the ext/html runtime registers exactly them (asserted at + * MINIT, see PHP_MINIT_FUNCTION(html)). + * + * Renaming the markup namespace (e.g. the open `Html\` vs `PHP\Html\` question) + * is a one-line edit here rather than a hunt across the parser, stub, and + * runtime. Keep in sync with ext/html/html.stub.php, which declares the classes + * and functions these names refer to. */ + +#define ZEND_MARKUP_ELEMENT_FQ "Html\\Element" +#define ZEND_MARKUP_FRAGMENT_FQ "Html\\Fragment" +#define ZEND_MARKUP_RAW_FQ "Html\\raw" +#define ZEND_MARKUP_COMPONENT_FQ "Html\\render_component" +#define ZEND_MARKUP_DYNAMIC_FQ "Html\\render_dynamic" + +BEGIN_EXTERN_C() + +/* Element vs component classification (RFC §4): uppercase first character, + * a "\", or a "::" dispatches as a component; anything else is a literal HTML + * element. The single home for this rule — the compiler applies it to static + * tag names, and Html\render_dynamic applies it to a dynamic tag's runtime + * value, so `<$tag>` decides at runtime exactly what `` decides at + * compile time. */ +ZEND_API bool zend_markup_name_is_component(zend_string *tag); + +/* Compile-time text handling, called by the lexer (zend_language_scanner.l) on + * markup text nodes and literal attribute values. Both live in zend_markup.c. */ +zend_string *zend_markup_normalize_text(const char *text, size_t len); +zend_string *zend_markup_decode_entities(zend_string *str); + +/* AST lowering, called by the markup grammar actions (zend_language_parser.y). + * Each returns NULL with a CompileError exception pending on invalid markup + * (mismatched closing tag, misplaced slot); the grammar action then YYERRORs. */ +zend_ast *zend_ast_create_markup_raw(zend_ast *str); +zend_ast *zend_ast_create_markup_slot(zend_ast *open, zend_ast *attrs, zend_ast *children, zend_ast *close); +zend_ast *zend_ast_create_markup_element(zend_ast *name, zend_ast *attrs, zend_ast *children); +zend_ast *zend_ast_create_markup_checked(zend_ast *open, zend_ast *attrs, zend_ast *children, zend_ast *close); +zend_ast *zend_ast_create_markup_dynamic(zend_ast *name, zend_ast *attrs, zend_ast *children); +zend_ast *zend_ast_create_markup_dynamic_checked(zend_ast *open, zend_ast *attrs, zend_ast *children, zend_ast *close); +zend_ast *zend_ast_create_markup_dynamic_expr(zend_ast *expr, zend_ast *attrs, zend_ast *children); + +END_EXTERN_C() + +#endif /* ZEND_MARKUP_H */ diff --git a/Zend/zend_markup_entities.h b/Zend/zend_markup_entities.h new file mode 100644 index 000000000000..1b7e80f109da --- /dev/null +++ b/Zend/zend_markup_entities.h @@ -0,0 +1,2148 @@ +/* This is a generated file — edit Zend/zend_markup_entities_gen.php instead. + * Source data: https://html.spec.whatwg.org/entities.json (a frozen list; the + * HTML standard guarantees no named character reference will ever be added). + * Semicolon-terminated forms only; sorted by name in byte order for bsearch. */ + +#ifndef ZEND_MARKUP_ENTITIES_H +#define ZEND_MARKUP_ENTITIES_H + +typedef struct _zend_markup_entity { + const char *name; /* without the leading '&' and trailing ';' */ + const char *utf8; /* replacement text, UTF-8 */ + uint8_t name_len; + uint8_t utf8_len; +} zend_markup_entity; + +#define ZEND_MARKUP_ENTITY_COUNT 2125 +#define ZEND_MARKUP_ENTITY_MAX_NAME 31 +#define ZEND_MARKUP_ENTITY_MAX_UTF8 6 + +static const zend_markup_entity zend_markup_entities[ZEND_MARKUP_ENTITY_COUNT] = { + {"AElig", "\xC3\x86", 5, 2}, + {"AMP", "\x26", 3, 1}, + {"Aacute", "\xC3\x81", 6, 2}, + {"Abreve", "\xC4\x82", 6, 2}, + {"Acirc", "\xC3\x82", 5, 2}, + {"Acy", "\xD0\x90", 3, 2}, + {"Afr", "\xF0\x9D\x94\x84", 3, 4}, + {"Agrave", "\xC3\x80", 6, 2}, + {"Alpha", "\xCE\x91", 5, 2}, + {"Amacr", "\xC4\x80", 5, 2}, + {"And", "\xE2\xA9\x93", 3, 3}, + {"Aogon", "\xC4\x84", 5, 2}, + {"Aopf", "\xF0\x9D\x94\xB8", 4, 4}, + {"ApplyFunction", "\xE2\x81\xA1", 13, 3}, + {"Aring", "\xC3\x85", 5, 2}, + {"Ascr", "\xF0\x9D\x92\x9C", 4, 4}, + {"Assign", "\xE2\x89\x94", 6, 3}, + {"Atilde", "\xC3\x83", 6, 2}, + {"Auml", "\xC3\x84", 4, 2}, + {"Backslash", "\xE2\x88\x96", 9, 3}, + {"Barv", "\xE2\xAB\xA7", 4, 3}, + {"Barwed", "\xE2\x8C\x86", 6, 3}, + {"Bcy", "\xD0\x91", 3, 2}, + {"Because", "\xE2\x88\xB5", 7, 3}, + {"Bernoullis", "\xE2\x84\xAC", 10, 3}, + {"Beta", "\xCE\x92", 4, 2}, + {"Bfr", "\xF0\x9D\x94\x85", 3, 4}, + {"Bopf", "\xF0\x9D\x94\xB9", 4, 4}, + {"Breve", "\xCB\x98", 5, 2}, + {"Bscr", "\xE2\x84\xAC", 4, 3}, + {"Bumpeq", "\xE2\x89\x8E", 6, 3}, + {"CHcy", "\xD0\xA7", 4, 2}, + {"COPY", "\xC2\xA9", 4, 2}, + {"Cacute", "\xC4\x86", 6, 2}, + {"Cap", "\xE2\x8B\x92", 3, 3}, + {"CapitalDifferentialD", "\xE2\x85\x85", 20, 3}, + {"Cayleys", "\xE2\x84\xAD", 7, 3}, + {"Ccaron", "\xC4\x8C", 6, 2}, + {"Ccedil", "\xC3\x87", 6, 2}, + {"Ccirc", "\xC4\x88", 5, 2}, + {"Cconint", "\xE2\x88\xB0", 7, 3}, + {"Cdot", "\xC4\x8A", 4, 2}, + {"Cedilla", "\xC2\xB8", 7, 2}, + {"CenterDot", "\xC2\xB7", 9, 2}, + {"Cfr", "\xE2\x84\xAD", 3, 3}, + {"Chi", "\xCE\xA7", 3, 2}, + {"CircleDot", "\xE2\x8A\x99", 9, 3}, + {"CircleMinus", "\xE2\x8A\x96", 11, 3}, + {"CirclePlus", "\xE2\x8A\x95", 10, 3}, + {"CircleTimes", "\xE2\x8A\x97", 11, 3}, + {"ClockwiseContourIntegral", "\xE2\x88\xB2", 24, 3}, + {"CloseCurlyDoubleQuote", "\xE2\x80\x9D", 21, 3}, + {"CloseCurlyQuote", "\xE2\x80\x99", 15, 3}, + {"Colon", "\xE2\x88\xB7", 5, 3}, + {"Colone", "\xE2\xA9\xB4", 6, 3}, + {"Congruent", "\xE2\x89\xA1", 9, 3}, + {"Conint", "\xE2\x88\xAF", 6, 3}, + {"ContourIntegral", "\xE2\x88\xAE", 15, 3}, + {"Copf", "\xE2\x84\x82", 4, 3}, + {"Coproduct", "\xE2\x88\x90", 9, 3}, + {"CounterClockwiseContourIntegral", "\xE2\x88\xB3", 31, 3}, + {"Cross", "\xE2\xA8\xAF", 5, 3}, + {"Cscr", "\xF0\x9D\x92\x9E", 4, 4}, + {"Cup", "\xE2\x8B\x93", 3, 3}, + {"CupCap", "\xE2\x89\x8D", 6, 3}, + {"DD", "\xE2\x85\x85", 2, 3}, + {"DDotrahd", "\xE2\xA4\x91", 8, 3}, + {"DJcy", "\xD0\x82", 4, 2}, + {"DScy", "\xD0\x85", 4, 2}, + {"DZcy", "\xD0\x8F", 4, 2}, + {"Dagger", "\xE2\x80\xA1", 6, 3}, + {"Darr", "\xE2\x86\xA1", 4, 3}, + {"Dashv", "\xE2\xAB\xA4", 5, 3}, + {"Dcaron", "\xC4\x8E", 6, 2}, + {"Dcy", "\xD0\x94", 3, 2}, + {"Del", "\xE2\x88\x87", 3, 3}, + {"Delta", "\xCE\x94", 5, 2}, + {"Dfr", "\xF0\x9D\x94\x87", 3, 4}, + {"DiacriticalAcute", "\xC2\xB4", 16, 2}, + {"DiacriticalDot", "\xCB\x99", 14, 2}, + {"DiacriticalDoubleAcute", "\xCB\x9D", 22, 2}, + {"DiacriticalGrave", "\x60", 16, 1}, + {"DiacriticalTilde", "\xCB\x9C", 16, 2}, + {"Diamond", "\xE2\x8B\x84", 7, 3}, + {"DifferentialD", "\xE2\x85\x86", 13, 3}, + {"Dopf", "\xF0\x9D\x94\xBB", 4, 4}, + {"Dot", "\xC2\xA8", 3, 2}, + {"DotDot", "\xE2\x83\x9C", 6, 3}, + {"DotEqual", "\xE2\x89\x90", 8, 3}, + {"DoubleContourIntegral", "\xE2\x88\xAF", 21, 3}, + {"DoubleDot", "\xC2\xA8", 9, 2}, + {"DoubleDownArrow", "\xE2\x87\x93", 15, 3}, + {"DoubleLeftArrow", "\xE2\x87\x90", 15, 3}, + {"DoubleLeftRightArrow", "\xE2\x87\x94", 20, 3}, + {"DoubleLeftTee", "\xE2\xAB\xA4", 13, 3}, + {"DoubleLongLeftArrow", "\xE2\x9F\xB8", 19, 3}, + {"DoubleLongLeftRightArrow", "\xE2\x9F\xBA", 24, 3}, + {"DoubleLongRightArrow", "\xE2\x9F\xB9", 20, 3}, + {"DoubleRightArrow", "\xE2\x87\x92", 16, 3}, + {"DoubleRightTee", "\xE2\x8A\xA8", 14, 3}, + {"DoubleUpArrow", "\xE2\x87\x91", 13, 3}, + {"DoubleUpDownArrow", "\xE2\x87\x95", 17, 3}, + {"DoubleVerticalBar", "\xE2\x88\xA5", 17, 3}, + {"DownArrow", "\xE2\x86\x93", 9, 3}, + {"DownArrowBar", "\xE2\xA4\x93", 12, 3}, + {"DownArrowUpArrow", "\xE2\x87\xB5", 16, 3}, + {"DownBreve", "\xCC\x91", 9, 2}, + {"DownLeftRightVector", "\xE2\xA5\x90", 19, 3}, + {"DownLeftTeeVector", "\xE2\xA5\x9E", 17, 3}, + {"DownLeftVector", "\xE2\x86\xBD", 14, 3}, + {"DownLeftVectorBar", "\xE2\xA5\x96", 17, 3}, + {"DownRightTeeVector", "\xE2\xA5\x9F", 18, 3}, + {"DownRightVector", "\xE2\x87\x81", 15, 3}, + {"DownRightVectorBar", "\xE2\xA5\x97", 18, 3}, + {"DownTee", "\xE2\x8A\xA4", 7, 3}, + {"DownTeeArrow", "\xE2\x86\xA7", 12, 3}, + {"Downarrow", "\xE2\x87\x93", 9, 3}, + {"Dscr", "\xF0\x9D\x92\x9F", 4, 4}, + {"Dstrok", "\xC4\x90", 6, 2}, + {"ENG", "\xC5\x8A", 3, 2}, + {"ETH", "\xC3\x90", 3, 2}, + {"Eacute", "\xC3\x89", 6, 2}, + {"Ecaron", "\xC4\x9A", 6, 2}, + {"Ecirc", "\xC3\x8A", 5, 2}, + {"Ecy", "\xD0\xAD", 3, 2}, + {"Edot", "\xC4\x96", 4, 2}, + {"Efr", "\xF0\x9D\x94\x88", 3, 4}, + {"Egrave", "\xC3\x88", 6, 2}, + {"Element", "\xE2\x88\x88", 7, 3}, + {"Emacr", "\xC4\x92", 5, 2}, + {"EmptySmallSquare", "\xE2\x97\xBB", 16, 3}, + {"EmptyVerySmallSquare", "\xE2\x96\xAB", 20, 3}, + {"Eogon", "\xC4\x98", 5, 2}, + {"Eopf", "\xF0\x9D\x94\xBC", 4, 4}, + {"Epsilon", "\xCE\x95", 7, 2}, + {"Equal", "\xE2\xA9\xB5", 5, 3}, + {"EqualTilde", "\xE2\x89\x82", 10, 3}, + {"Equilibrium", "\xE2\x87\x8C", 11, 3}, + {"Escr", "\xE2\x84\xB0", 4, 3}, + {"Esim", "\xE2\xA9\xB3", 4, 3}, + {"Eta", "\xCE\x97", 3, 2}, + {"Euml", "\xC3\x8B", 4, 2}, + {"Exists", "\xE2\x88\x83", 6, 3}, + {"ExponentialE", "\xE2\x85\x87", 12, 3}, + {"Fcy", "\xD0\xA4", 3, 2}, + {"Ffr", "\xF0\x9D\x94\x89", 3, 4}, + {"FilledSmallSquare", "\xE2\x97\xBC", 17, 3}, + {"FilledVerySmallSquare", "\xE2\x96\xAA", 21, 3}, + {"Fopf", "\xF0\x9D\x94\xBD", 4, 4}, + {"ForAll", "\xE2\x88\x80", 6, 3}, + {"Fouriertrf", "\xE2\x84\xB1", 10, 3}, + {"Fscr", "\xE2\x84\xB1", 4, 3}, + {"GJcy", "\xD0\x83", 4, 2}, + {"GT", "\x3E", 2, 1}, + {"Gamma", "\xCE\x93", 5, 2}, + {"Gammad", "\xCF\x9C", 6, 2}, + {"Gbreve", "\xC4\x9E", 6, 2}, + {"Gcedil", "\xC4\xA2", 6, 2}, + {"Gcirc", "\xC4\x9C", 5, 2}, + {"Gcy", "\xD0\x93", 3, 2}, + {"Gdot", "\xC4\xA0", 4, 2}, + {"Gfr", "\xF0\x9D\x94\x8A", 3, 4}, + {"Gg", "\xE2\x8B\x99", 2, 3}, + {"Gopf", "\xF0\x9D\x94\xBE", 4, 4}, + {"GreaterEqual", "\xE2\x89\xA5", 12, 3}, + {"GreaterEqualLess", "\xE2\x8B\x9B", 16, 3}, + {"GreaterFullEqual", "\xE2\x89\xA7", 16, 3}, + {"GreaterGreater", "\xE2\xAA\xA2", 14, 3}, + {"GreaterLess", "\xE2\x89\xB7", 11, 3}, + {"GreaterSlantEqual", "\xE2\xA9\xBE", 17, 3}, + {"GreaterTilde", "\xE2\x89\xB3", 12, 3}, + {"Gscr", "\xF0\x9D\x92\xA2", 4, 4}, + {"Gt", "\xE2\x89\xAB", 2, 3}, + {"HARDcy", "\xD0\xAA", 6, 2}, + {"Hacek", "\xCB\x87", 5, 2}, + {"Hat", "\x5E", 3, 1}, + {"Hcirc", "\xC4\xA4", 5, 2}, + {"Hfr", "\xE2\x84\x8C", 3, 3}, + {"HilbertSpace", "\xE2\x84\x8B", 12, 3}, + {"Hopf", "\xE2\x84\x8D", 4, 3}, + {"HorizontalLine", "\xE2\x94\x80", 14, 3}, + {"Hscr", "\xE2\x84\x8B", 4, 3}, + {"Hstrok", "\xC4\xA6", 6, 2}, + {"HumpDownHump", "\xE2\x89\x8E", 12, 3}, + {"HumpEqual", "\xE2\x89\x8F", 9, 3}, + {"IEcy", "\xD0\x95", 4, 2}, + {"IJlig", "\xC4\xB2", 5, 2}, + {"IOcy", "\xD0\x81", 4, 2}, + {"Iacute", "\xC3\x8D", 6, 2}, + {"Icirc", "\xC3\x8E", 5, 2}, + {"Icy", "\xD0\x98", 3, 2}, + {"Idot", "\xC4\xB0", 4, 2}, + {"Ifr", "\xE2\x84\x91", 3, 3}, + {"Igrave", "\xC3\x8C", 6, 2}, + {"Im", "\xE2\x84\x91", 2, 3}, + {"Imacr", "\xC4\xAA", 5, 2}, + {"ImaginaryI", "\xE2\x85\x88", 10, 3}, + {"Implies", "\xE2\x87\x92", 7, 3}, + {"Int", "\xE2\x88\xAC", 3, 3}, + {"Integral", "\xE2\x88\xAB", 8, 3}, + {"Intersection", "\xE2\x8B\x82", 12, 3}, + {"InvisibleComma", "\xE2\x81\xA3", 14, 3}, + {"InvisibleTimes", "\xE2\x81\xA2", 14, 3}, + {"Iogon", "\xC4\xAE", 5, 2}, + {"Iopf", "\xF0\x9D\x95\x80", 4, 4}, + {"Iota", "\xCE\x99", 4, 2}, + {"Iscr", "\xE2\x84\x90", 4, 3}, + {"Itilde", "\xC4\xA8", 6, 2}, + {"Iukcy", "\xD0\x86", 5, 2}, + {"Iuml", "\xC3\x8F", 4, 2}, + {"Jcirc", "\xC4\xB4", 5, 2}, + {"Jcy", "\xD0\x99", 3, 2}, + {"Jfr", "\xF0\x9D\x94\x8D", 3, 4}, + {"Jopf", "\xF0\x9D\x95\x81", 4, 4}, + {"Jscr", "\xF0\x9D\x92\xA5", 4, 4}, + {"Jsercy", "\xD0\x88", 6, 2}, + {"Jukcy", "\xD0\x84", 5, 2}, + {"KHcy", "\xD0\xA5", 4, 2}, + {"KJcy", "\xD0\x8C", 4, 2}, + {"Kappa", "\xCE\x9A", 5, 2}, + {"Kcedil", "\xC4\xB6", 6, 2}, + {"Kcy", "\xD0\x9A", 3, 2}, + {"Kfr", "\xF0\x9D\x94\x8E", 3, 4}, + {"Kopf", "\xF0\x9D\x95\x82", 4, 4}, + {"Kscr", "\xF0\x9D\x92\xA6", 4, 4}, + {"LJcy", "\xD0\x89", 4, 2}, + {"LT", "\x3C", 2, 1}, + {"Lacute", "\xC4\xB9", 6, 2}, + {"Lambda", "\xCE\x9B", 6, 2}, + {"Lang", "\xE2\x9F\xAA", 4, 3}, + {"Laplacetrf", "\xE2\x84\x92", 10, 3}, + {"Larr", "\xE2\x86\x9E", 4, 3}, + {"Lcaron", "\xC4\xBD", 6, 2}, + {"Lcedil", "\xC4\xBB", 6, 2}, + {"Lcy", "\xD0\x9B", 3, 2}, + {"LeftAngleBracket", "\xE2\x9F\xA8", 16, 3}, + {"LeftArrow", "\xE2\x86\x90", 9, 3}, + {"LeftArrowBar", "\xE2\x87\xA4", 12, 3}, + {"LeftArrowRightArrow", "\xE2\x87\x86", 19, 3}, + {"LeftCeiling", "\xE2\x8C\x88", 11, 3}, + {"LeftDoubleBracket", "\xE2\x9F\xA6", 17, 3}, + {"LeftDownTeeVector", "\xE2\xA5\xA1", 17, 3}, + {"LeftDownVector", "\xE2\x87\x83", 14, 3}, + {"LeftDownVectorBar", "\xE2\xA5\x99", 17, 3}, + {"LeftFloor", "\xE2\x8C\x8A", 9, 3}, + {"LeftRightArrow", "\xE2\x86\x94", 14, 3}, + {"LeftRightVector", "\xE2\xA5\x8E", 15, 3}, + {"LeftTee", "\xE2\x8A\xA3", 7, 3}, + {"LeftTeeArrow", "\xE2\x86\xA4", 12, 3}, + {"LeftTeeVector", "\xE2\xA5\x9A", 13, 3}, + {"LeftTriangle", "\xE2\x8A\xB2", 12, 3}, + {"LeftTriangleBar", "\xE2\xA7\x8F", 15, 3}, + {"LeftTriangleEqual", "\xE2\x8A\xB4", 17, 3}, + {"LeftUpDownVector", "\xE2\xA5\x91", 16, 3}, + {"LeftUpTeeVector", "\xE2\xA5\xA0", 15, 3}, + {"LeftUpVector", "\xE2\x86\xBF", 12, 3}, + {"LeftUpVectorBar", "\xE2\xA5\x98", 15, 3}, + {"LeftVector", "\xE2\x86\xBC", 10, 3}, + {"LeftVectorBar", "\xE2\xA5\x92", 13, 3}, + {"Leftarrow", "\xE2\x87\x90", 9, 3}, + {"Leftrightarrow", "\xE2\x87\x94", 14, 3}, + {"LessEqualGreater", "\xE2\x8B\x9A", 16, 3}, + {"LessFullEqual", "\xE2\x89\xA6", 13, 3}, + {"LessGreater", "\xE2\x89\xB6", 11, 3}, + {"LessLess", "\xE2\xAA\xA1", 8, 3}, + {"LessSlantEqual", "\xE2\xA9\xBD", 14, 3}, + {"LessTilde", "\xE2\x89\xB2", 9, 3}, + {"Lfr", "\xF0\x9D\x94\x8F", 3, 4}, + {"Ll", "\xE2\x8B\x98", 2, 3}, + {"Lleftarrow", "\xE2\x87\x9A", 10, 3}, + {"Lmidot", "\xC4\xBF", 6, 2}, + {"LongLeftArrow", "\xE2\x9F\xB5", 13, 3}, + {"LongLeftRightArrow", "\xE2\x9F\xB7", 18, 3}, + {"LongRightArrow", "\xE2\x9F\xB6", 14, 3}, + {"Longleftarrow", "\xE2\x9F\xB8", 13, 3}, + {"Longleftrightarrow", "\xE2\x9F\xBA", 18, 3}, + {"Longrightarrow", "\xE2\x9F\xB9", 14, 3}, + {"Lopf", "\xF0\x9D\x95\x83", 4, 4}, + {"LowerLeftArrow", "\xE2\x86\x99", 14, 3}, + {"LowerRightArrow", "\xE2\x86\x98", 15, 3}, + {"Lscr", "\xE2\x84\x92", 4, 3}, + {"Lsh", "\xE2\x86\xB0", 3, 3}, + {"Lstrok", "\xC5\x81", 6, 2}, + {"Lt", "\xE2\x89\xAA", 2, 3}, + {"Map", "\xE2\xA4\x85", 3, 3}, + {"Mcy", "\xD0\x9C", 3, 2}, + {"MediumSpace", "\xE2\x81\x9F", 11, 3}, + {"Mellintrf", "\xE2\x84\xB3", 9, 3}, + {"Mfr", "\xF0\x9D\x94\x90", 3, 4}, + {"MinusPlus", "\xE2\x88\x93", 9, 3}, + {"Mopf", "\xF0\x9D\x95\x84", 4, 4}, + {"Mscr", "\xE2\x84\xB3", 4, 3}, + {"Mu", "\xCE\x9C", 2, 2}, + {"NJcy", "\xD0\x8A", 4, 2}, + {"Nacute", "\xC5\x83", 6, 2}, + {"Ncaron", "\xC5\x87", 6, 2}, + {"Ncedil", "\xC5\x85", 6, 2}, + {"Ncy", "\xD0\x9D", 3, 2}, + {"NegativeMediumSpace", "\xE2\x80\x8B", 19, 3}, + {"NegativeThickSpace", "\xE2\x80\x8B", 18, 3}, + {"NegativeThinSpace", "\xE2\x80\x8B", 17, 3}, + {"NegativeVeryThinSpace", "\xE2\x80\x8B", 21, 3}, + {"NestedGreaterGreater", "\xE2\x89\xAB", 20, 3}, + {"NestedLessLess", "\xE2\x89\xAA", 14, 3}, + {"NewLine", "\x0A", 7, 1}, + {"Nfr", "\xF0\x9D\x94\x91", 3, 4}, + {"NoBreak", "\xE2\x81\xA0", 7, 3}, + {"NonBreakingSpace", "\xC2\xA0", 16, 2}, + {"Nopf", "\xE2\x84\x95", 4, 3}, + {"Not", "\xE2\xAB\xAC", 3, 3}, + {"NotCongruent", "\xE2\x89\xA2", 12, 3}, + {"NotCupCap", "\xE2\x89\xAD", 9, 3}, + {"NotDoubleVerticalBar", "\xE2\x88\xA6", 20, 3}, + {"NotElement", "\xE2\x88\x89", 10, 3}, + {"NotEqual", "\xE2\x89\xA0", 8, 3}, + {"NotEqualTilde", "\xE2\x89\x82\xCC\xB8", 13, 5}, + {"NotExists", "\xE2\x88\x84", 9, 3}, + {"NotGreater", "\xE2\x89\xAF", 10, 3}, + {"NotGreaterEqual", "\xE2\x89\xB1", 15, 3}, + {"NotGreaterFullEqual", "\xE2\x89\xA7\xCC\xB8", 19, 5}, + {"NotGreaterGreater", "\xE2\x89\xAB\xCC\xB8", 17, 5}, + {"NotGreaterLess", "\xE2\x89\xB9", 14, 3}, + {"NotGreaterSlantEqual", "\xE2\xA9\xBE\xCC\xB8", 20, 5}, + {"NotGreaterTilde", "\xE2\x89\xB5", 15, 3}, + {"NotHumpDownHump", "\xE2\x89\x8E\xCC\xB8", 15, 5}, + {"NotHumpEqual", "\xE2\x89\x8F\xCC\xB8", 12, 5}, + {"NotLeftTriangle", "\xE2\x8B\xAA", 15, 3}, + {"NotLeftTriangleBar", "\xE2\xA7\x8F\xCC\xB8", 18, 5}, + {"NotLeftTriangleEqual", "\xE2\x8B\xAC", 20, 3}, + {"NotLess", "\xE2\x89\xAE", 7, 3}, + {"NotLessEqual", "\xE2\x89\xB0", 12, 3}, + {"NotLessGreater", "\xE2\x89\xB8", 14, 3}, + {"NotLessLess", "\xE2\x89\xAA\xCC\xB8", 11, 5}, + {"NotLessSlantEqual", "\xE2\xA9\xBD\xCC\xB8", 17, 5}, + {"NotLessTilde", "\xE2\x89\xB4", 12, 3}, + {"NotNestedGreaterGreater", "\xE2\xAA\xA2\xCC\xB8", 23, 5}, + {"NotNestedLessLess", "\xE2\xAA\xA1\xCC\xB8", 17, 5}, + {"NotPrecedes", "\xE2\x8A\x80", 11, 3}, + {"NotPrecedesEqual", "\xE2\xAA\xAF\xCC\xB8", 16, 5}, + {"NotPrecedesSlantEqual", "\xE2\x8B\xA0", 21, 3}, + {"NotReverseElement", "\xE2\x88\x8C", 17, 3}, + {"NotRightTriangle", "\xE2\x8B\xAB", 16, 3}, + {"NotRightTriangleBar", "\xE2\xA7\x90\xCC\xB8", 19, 5}, + {"NotRightTriangleEqual", "\xE2\x8B\xAD", 21, 3}, + {"NotSquareSubset", "\xE2\x8A\x8F\xCC\xB8", 15, 5}, + {"NotSquareSubsetEqual", "\xE2\x8B\xA2", 20, 3}, + {"NotSquareSuperset", "\xE2\x8A\x90\xCC\xB8", 17, 5}, + {"NotSquareSupersetEqual", "\xE2\x8B\xA3", 22, 3}, + {"NotSubset", "\xE2\x8A\x82\xE2\x83\x92", 9, 6}, + {"NotSubsetEqual", "\xE2\x8A\x88", 14, 3}, + {"NotSucceeds", "\xE2\x8A\x81", 11, 3}, + {"NotSucceedsEqual", "\xE2\xAA\xB0\xCC\xB8", 16, 5}, + {"NotSucceedsSlantEqual", "\xE2\x8B\xA1", 21, 3}, + {"NotSucceedsTilde", "\xE2\x89\xBF\xCC\xB8", 16, 5}, + {"NotSuperset", "\xE2\x8A\x83\xE2\x83\x92", 11, 6}, + {"NotSupersetEqual", "\xE2\x8A\x89", 16, 3}, + {"NotTilde", "\xE2\x89\x81", 8, 3}, + {"NotTildeEqual", "\xE2\x89\x84", 13, 3}, + {"NotTildeFullEqual", "\xE2\x89\x87", 17, 3}, + {"NotTildeTilde", "\xE2\x89\x89", 13, 3}, + {"NotVerticalBar", "\xE2\x88\xA4", 14, 3}, + {"Nscr", "\xF0\x9D\x92\xA9", 4, 4}, + {"Ntilde", "\xC3\x91", 6, 2}, + {"Nu", "\xCE\x9D", 2, 2}, + {"OElig", "\xC5\x92", 5, 2}, + {"Oacute", "\xC3\x93", 6, 2}, + {"Ocirc", "\xC3\x94", 5, 2}, + {"Ocy", "\xD0\x9E", 3, 2}, + {"Odblac", "\xC5\x90", 6, 2}, + {"Ofr", "\xF0\x9D\x94\x92", 3, 4}, + {"Ograve", "\xC3\x92", 6, 2}, + {"Omacr", "\xC5\x8C", 5, 2}, + {"Omega", "\xCE\xA9", 5, 2}, + {"Omicron", "\xCE\x9F", 7, 2}, + {"Oopf", "\xF0\x9D\x95\x86", 4, 4}, + {"OpenCurlyDoubleQuote", "\xE2\x80\x9C", 20, 3}, + {"OpenCurlyQuote", "\xE2\x80\x98", 14, 3}, + {"Or", "\xE2\xA9\x94", 2, 3}, + {"Oscr", "\xF0\x9D\x92\xAA", 4, 4}, + {"Oslash", "\xC3\x98", 6, 2}, + {"Otilde", "\xC3\x95", 6, 2}, + {"Otimes", "\xE2\xA8\xB7", 6, 3}, + {"Ouml", "\xC3\x96", 4, 2}, + {"OverBar", "\xE2\x80\xBE", 7, 3}, + {"OverBrace", "\xE2\x8F\x9E", 9, 3}, + {"OverBracket", "\xE2\x8E\xB4", 11, 3}, + {"OverParenthesis", "\xE2\x8F\x9C", 15, 3}, + {"PartialD", "\xE2\x88\x82", 8, 3}, + {"Pcy", "\xD0\x9F", 3, 2}, + {"Pfr", "\xF0\x9D\x94\x93", 3, 4}, + {"Phi", "\xCE\xA6", 3, 2}, + {"Pi", "\xCE\xA0", 2, 2}, + {"PlusMinus", "\xC2\xB1", 9, 2}, + {"Poincareplane", "\xE2\x84\x8C", 13, 3}, + {"Popf", "\xE2\x84\x99", 4, 3}, + {"Pr", "\xE2\xAA\xBB", 2, 3}, + {"Precedes", "\xE2\x89\xBA", 8, 3}, + {"PrecedesEqual", "\xE2\xAA\xAF", 13, 3}, + {"PrecedesSlantEqual", "\xE2\x89\xBC", 18, 3}, + {"PrecedesTilde", "\xE2\x89\xBE", 13, 3}, + {"Prime", "\xE2\x80\xB3", 5, 3}, + {"Product", "\xE2\x88\x8F", 7, 3}, + {"Proportion", "\xE2\x88\xB7", 10, 3}, + {"Proportional", "\xE2\x88\x9D", 12, 3}, + {"Pscr", "\xF0\x9D\x92\xAB", 4, 4}, + {"Psi", "\xCE\xA8", 3, 2}, + {"QUOT", "\x22", 4, 1}, + {"Qfr", "\xF0\x9D\x94\x94", 3, 4}, + {"Qopf", "\xE2\x84\x9A", 4, 3}, + {"Qscr", "\xF0\x9D\x92\xAC", 4, 4}, + {"RBarr", "\xE2\xA4\x90", 5, 3}, + {"REG", "\xC2\xAE", 3, 2}, + {"Racute", "\xC5\x94", 6, 2}, + {"Rang", "\xE2\x9F\xAB", 4, 3}, + {"Rarr", "\xE2\x86\xA0", 4, 3}, + {"Rarrtl", "\xE2\xA4\x96", 6, 3}, + {"Rcaron", "\xC5\x98", 6, 2}, + {"Rcedil", "\xC5\x96", 6, 2}, + {"Rcy", "\xD0\xA0", 3, 2}, + {"Re", "\xE2\x84\x9C", 2, 3}, + {"ReverseElement", "\xE2\x88\x8B", 14, 3}, + {"ReverseEquilibrium", "\xE2\x87\x8B", 18, 3}, + {"ReverseUpEquilibrium", "\xE2\xA5\xAF", 20, 3}, + {"Rfr", "\xE2\x84\x9C", 3, 3}, + {"Rho", "\xCE\xA1", 3, 2}, + {"RightAngleBracket", "\xE2\x9F\xA9", 17, 3}, + {"RightArrow", "\xE2\x86\x92", 10, 3}, + {"RightArrowBar", "\xE2\x87\xA5", 13, 3}, + {"RightArrowLeftArrow", "\xE2\x87\x84", 19, 3}, + {"RightCeiling", "\xE2\x8C\x89", 12, 3}, + {"RightDoubleBracket", "\xE2\x9F\xA7", 18, 3}, + {"RightDownTeeVector", "\xE2\xA5\x9D", 18, 3}, + {"RightDownVector", "\xE2\x87\x82", 15, 3}, + {"RightDownVectorBar", "\xE2\xA5\x95", 18, 3}, + {"RightFloor", "\xE2\x8C\x8B", 10, 3}, + {"RightTee", "\xE2\x8A\xA2", 8, 3}, + {"RightTeeArrow", "\xE2\x86\xA6", 13, 3}, + {"RightTeeVector", "\xE2\xA5\x9B", 14, 3}, + {"RightTriangle", "\xE2\x8A\xB3", 13, 3}, + {"RightTriangleBar", "\xE2\xA7\x90", 16, 3}, + {"RightTriangleEqual", "\xE2\x8A\xB5", 18, 3}, + {"RightUpDownVector", "\xE2\xA5\x8F", 17, 3}, + {"RightUpTeeVector", "\xE2\xA5\x9C", 16, 3}, + {"RightUpVector", "\xE2\x86\xBE", 13, 3}, + {"RightUpVectorBar", "\xE2\xA5\x94", 16, 3}, + {"RightVector", "\xE2\x87\x80", 11, 3}, + {"RightVectorBar", "\xE2\xA5\x93", 14, 3}, + {"Rightarrow", "\xE2\x87\x92", 10, 3}, + {"Ropf", "\xE2\x84\x9D", 4, 3}, + {"RoundImplies", "\xE2\xA5\xB0", 12, 3}, + {"Rrightarrow", "\xE2\x87\x9B", 11, 3}, + {"Rscr", "\xE2\x84\x9B", 4, 3}, + {"Rsh", "\xE2\x86\xB1", 3, 3}, + {"RuleDelayed", "\xE2\xA7\xB4", 11, 3}, + {"SHCHcy", "\xD0\xA9", 6, 2}, + {"SHcy", "\xD0\xA8", 4, 2}, + {"SOFTcy", "\xD0\xAC", 6, 2}, + {"Sacute", "\xC5\x9A", 6, 2}, + {"Sc", "\xE2\xAA\xBC", 2, 3}, + {"Scaron", "\xC5\xA0", 6, 2}, + {"Scedil", "\xC5\x9E", 6, 2}, + {"Scirc", "\xC5\x9C", 5, 2}, + {"Scy", "\xD0\xA1", 3, 2}, + {"Sfr", "\xF0\x9D\x94\x96", 3, 4}, + {"ShortDownArrow", "\xE2\x86\x93", 14, 3}, + {"ShortLeftArrow", "\xE2\x86\x90", 14, 3}, + {"ShortRightArrow", "\xE2\x86\x92", 15, 3}, + {"ShortUpArrow", "\xE2\x86\x91", 12, 3}, + {"Sigma", "\xCE\xA3", 5, 2}, + {"SmallCircle", "\xE2\x88\x98", 11, 3}, + {"Sopf", "\xF0\x9D\x95\x8A", 4, 4}, + {"Sqrt", "\xE2\x88\x9A", 4, 3}, + {"Square", "\xE2\x96\xA1", 6, 3}, + {"SquareIntersection", "\xE2\x8A\x93", 18, 3}, + {"SquareSubset", "\xE2\x8A\x8F", 12, 3}, + {"SquareSubsetEqual", "\xE2\x8A\x91", 17, 3}, + {"SquareSuperset", "\xE2\x8A\x90", 14, 3}, + {"SquareSupersetEqual", "\xE2\x8A\x92", 19, 3}, + {"SquareUnion", "\xE2\x8A\x94", 11, 3}, + {"Sscr", "\xF0\x9D\x92\xAE", 4, 4}, + {"Star", "\xE2\x8B\x86", 4, 3}, + {"Sub", "\xE2\x8B\x90", 3, 3}, + {"Subset", "\xE2\x8B\x90", 6, 3}, + {"SubsetEqual", "\xE2\x8A\x86", 11, 3}, + {"Succeeds", "\xE2\x89\xBB", 8, 3}, + {"SucceedsEqual", "\xE2\xAA\xB0", 13, 3}, + {"SucceedsSlantEqual", "\xE2\x89\xBD", 18, 3}, + {"SucceedsTilde", "\xE2\x89\xBF", 13, 3}, + {"SuchThat", "\xE2\x88\x8B", 8, 3}, + {"Sum", "\xE2\x88\x91", 3, 3}, + {"Sup", "\xE2\x8B\x91", 3, 3}, + {"Superset", "\xE2\x8A\x83", 8, 3}, + {"SupersetEqual", "\xE2\x8A\x87", 13, 3}, + {"Supset", "\xE2\x8B\x91", 6, 3}, + {"THORN", "\xC3\x9E", 5, 2}, + {"TRADE", "\xE2\x84\xA2", 5, 3}, + {"TSHcy", "\xD0\x8B", 5, 2}, + {"TScy", "\xD0\xA6", 4, 2}, + {"Tab", "\x09", 3, 1}, + {"Tau", "\xCE\xA4", 3, 2}, + {"Tcaron", "\xC5\xA4", 6, 2}, + {"Tcedil", "\xC5\xA2", 6, 2}, + {"Tcy", "\xD0\xA2", 3, 2}, + {"Tfr", "\xF0\x9D\x94\x97", 3, 4}, + {"Therefore", "\xE2\x88\xB4", 9, 3}, + {"Theta", "\xCE\x98", 5, 2}, + {"ThickSpace", "\xE2\x81\x9F\xE2\x80\x8A", 10, 6}, + {"ThinSpace", "\xE2\x80\x89", 9, 3}, + {"Tilde", "\xE2\x88\xBC", 5, 3}, + {"TildeEqual", "\xE2\x89\x83", 10, 3}, + {"TildeFullEqual", "\xE2\x89\x85", 14, 3}, + {"TildeTilde", "\xE2\x89\x88", 10, 3}, + {"Topf", "\xF0\x9D\x95\x8B", 4, 4}, + {"TripleDot", "\xE2\x83\x9B", 9, 3}, + {"Tscr", "\xF0\x9D\x92\xAF", 4, 4}, + {"Tstrok", "\xC5\xA6", 6, 2}, + {"Uacute", "\xC3\x9A", 6, 2}, + {"Uarr", "\xE2\x86\x9F", 4, 3}, + {"Uarrocir", "\xE2\xA5\x89", 8, 3}, + {"Ubrcy", "\xD0\x8E", 5, 2}, + {"Ubreve", "\xC5\xAC", 6, 2}, + {"Ucirc", "\xC3\x9B", 5, 2}, + {"Ucy", "\xD0\xA3", 3, 2}, + {"Udblac", "\xC5\xB0", 6, 2}, + {"Ufr", "\xF0\x9D\x94\x98", 3, 4}, + {"Ugrave", "\xC3\x99", 6, 2}, + {"Umacr", "\xC5\xAA", 5, 2}, + {"UnderBar", "\x5F", 8, 1}, + {"UnderBrace", "\xE2\x8F\x9F", 10, 3}, + {"UnderBracket", "\xE2\x8E\xB5", 12, 3}, + {"UnderParenthesis", "\xE2\x8F\x9D", 16, 3}, + {"Union", "\xE2\x8B\x83", 5, 3}, + {"UnionPlus", "\xE2\x8A\x8E", 9, 3}, + {"Uogon", "\xC5\xB2", 5, 2}, + {"Uopf", "\xF0\x9D\x95\x8C", 4, 4}, + {"UpArrow", "\xE2\x86\x91", 7, 3}, + {"UpArrowBar", "\xE2\xA4\x92", 10, 3}, + {"UpArrowDownArrow", "\xE2\x87\x85", 16, 3}, + {"UpDownArrow", "\xE2\x86\x95", 11, 3}, + {"UpEquilibrium", "\xE2\xA5\xAE", 13, 3}, + {"UpTee", "\xE2\x8A\xA5", 5, 3}, + {"UpTeeArrow", "\xE2\x86\xA5", 10, 3}, + {"Uparrow", "\xE2\x87\x91", 7, 3}, + {"Updownarrow", "\xE2\x87\x95", 11, 3}, + {"UpperLeftArrow", "\xE2\x86\x96", 14, 3}, + {"UpperRightArrow", "\xE2\x86\x97", 15, 3}, + {"Upsi", "\xCF\x92", 4, 2}, + {"Upsilon", "\xCE\xA5", 7, 2}, + {"Uring", "\xC5\xAE", 5, 2}, + {"Uscr", "\xF0\x9D\x92\xB0", 4, 4}, + {"Utilde", "\xC5\xA8", 6, 2}, + {"Uuml", "\xC3\x9C", 4, 2}, + {"VDash", "\xE2\x8A\xAB", 5, 3}, + {"Vbar", "\xE2\xAB\xAB", 4, 3}, + {"Vcy", "\xD0\x92", 3, 2}, + {"Vdash", "\xE2\x8A\xA9", 5, 3}, + {"Vdashl", "\xE2\xAB\xA6", 6, 3}, + {"Vee", "\xE2\x8B\x81", 3, 3}, + {"Verbar", "\xE2\x80\x96", 6, 3}, + {"Vert", "\xE2\x80\x96", 4, 3}, + {"VerticalBar", "\xE2\x88\xA3", 11, 3}, + {"VerticalLine", "\x7C", 12, 1}, + {"VerticalSeparator", "\xE2\x9D\x98", 17, 3}, + {"VerticalTilde", "\xE2\x89\x80", 13, 3}, + {"VeryThinSpace", "\xE2\x80\x8A", 13, 3}, + {"Vfr", "\xF0\x9D\x94\x99", 3, 4}, + {"Vopf", "\xF0\x9D\x95\x8D", 4, 4}, + {"Vscr", "\xF0\x9D\x92\xB1", 4, 4}, + {"Vvdash", "\xE2\x8A\xAA", 6, 3}, + {"Wcirc", "\xC5\xB4", 5, 2}, + {"Wedge", "\xE2\x8B\x80", 5, 3}, + {"Wfr", "\xF0\x9D\x94\x9A", 3, 4}, + {"Wopf", "\xF0\x9D\x95\x8E", 4, 4}, + {"Wscr", "\xF0\x9D\x92\xB2", 4, 4}, + {"Xfr", "\xF0\x9D\x94\x9B", 3, 4}, + {"Xi", "\xCE\x9E", 2, 2}, + {"Xopf", "\xF0\x9D\x95\x8F", 4, 4}, + {"Xscr", "\xF0\x9D\x92\xB3", 4, 4}, + {"YAcy", "\xD0\xAF", 4, 2}, + {"YIcy", "\xD0\x87", 4, 2}, + {"YUcy", "\xD0\xAE", 4, 2}, + {"Yacute", "\xC3\x9D", 6, 2}, + {"Ycirc", "\xC5\xB6", 5, 2}, + {"Ycy", "\xD0\xAB", 3, 2}, + {"Yfr", "\xF0\x9D\x94\x9C", 3, 4}, + {"Yopf", "\xF0\x9D\x95\x90", 4, 4}, + {"Yscr", "\xF0\x9D\x92\xB4", 4, 4}, + {"Yuml", "\xC5\xB8", 4, 2}, + {"ZHcy", "\xD0\x96", 4, 2}, + {"Zacute", "\xC5\xB9", 6, 2}, + {"Zcaron", "\xC5\xBD", 6, 2}, + {"Zcy", "\xD0\x97", 3, 2}, + {"Zdot", "\xC5\xBB", 4, 2}, + {"ZeroWidthSpace", "\xE2\x80\x8B", 14, 3}, + {"Zeta", "\xCE\x96", 4, 2}, + {"Zfr", "\xE2\x84\xA8", 3, 3}, + {"Zopf", "\xE2\x84\xA4", 4, 3}, + {"Zscr", "\xF0\x9D\x92\xB5", 4, 4}, + {"aacute", "\xC3\xA1", 6, 2}, + {"abreve", "\xC4\x83", 6, 2}, + {"ac", "\xE2\x88\xBE", 2, 3}, + {"acE", "\xE2\x88\xBE\xCC\xB3", 3, 5}, + {"acd", "\xE2\x88\xBF", 3, 3}, + {"acirc", "\xC3\xA2", 5, 2}, + {"acute", "\xC2\xB4", 5, 2}, + {"acy", "\xD0\xB0", 3, 2}, + {"aelig", "\xC3\xA6", 5, 2}, + {"af", "\xE2\x81\xA1", 2, 3}, + {"afr", "\xF0\x9D\x94\x9E", 3, 4}, + {"agrave", "\xC3\xA0", 6, 2}, + {"alefsym", "\xE2\x84\xB5", 7, 3}, + {"aleph", "\xE2\x84\xB5", 5, 3}, + {"alpha", "\xCE\xB1", 5, 2}, + {"amacr", "\xC4\x81", 5, 2}, + {"amalg", "\xE2\xA8\xBF", 5, 3}, + {"amp", "\x26", 3, 1}, + {"and", "\xE2\x88\xA7", 3, 3}, + {"andand", "\xE2\xA9\x95", 6, 3}, + {"andd", "\xE2\xA9\x9C", 4, 3}, + {"andslope", "\xE2\xA9\x98", 8, 3}, + {"andv", "\xE2\xA9\x9A", 4, 3}, + {"ang", "\xE2\x88\xA0", 3, 3}, + {"ange", "\xE2\xA6\xA4", 4, 3}, + {"angle", "\xE2\x88\xA0", 5, 3}, + {"angmsd", "\xE2\x88\xA1", 6, 3}, + {"angmsdaa", "\xE2\xA6\xA8", 8, 3}, + {"angmsdab", "\xE2\xA6\xA9", 8, 3}, + {"angmsdac", "\xE2\xA6\xAA", 8, 3}, + {"angmsdad", "\xE2\xA6\xAB", 8, 3}, + {"angmsdae", "\xE2\xA6\xAC", 8, 3}, + {"angmsdaf", "\xE2\xA6\xAD", 8, 3}, + {"angmsdag", "\xE2\xA6\xAE", 8, 3}, + {"angmsdah", "\xE2\xA6\xAF", 8, 3}, + {"angrt", "\xE2\x88\x9F", 5, 3}, + {"angrtvb", "\xE2\x8A\xBE", 7, 3}, + {"angrtvbd", "\xE2\xA6\x9D", 8, 3}, + {"angsph", "\xE2\x88\xA2", 6, 3}, + {"angst", "\xC3\x85", 5, 2}, + {"angzarr", "\xE2\x8D\xBC", 7, 3}, + {"aogon", "\xC4\x85", 5, 2}, + {"aopf", "\xF0\x9D\x95\x92", 4, 4}, + {"ap", "\xE2\x89\x88", 2, 3}, + {"apE", "\xE2\xA9\xB0", 3, 3}, + {"apacir", "\xE2\xA9\xAF", 6, 3}, + {"ape", "\xE2\x89\x8A", 3, 3}, + {"apid", "\xE2\x89\x8B", 4, 3}, + {"apos", "\x27", 4, 1}, + {"approx", "\xE2\x89\x88", 6, 3}, + {"approxeq", "\xE2\x89\x8A", 8, 3}, + {"aring", "\xC3\xA5", 5, 2}, + {"ascr", "\xF0\x9D\x92\xB6", 4, 4}, + {"ast", "\x2A", 3, 1}, + {"asymp", "\xE2\x89\x88", 5, 3}, + {"asympeq", "\xE2\x89\x8D", 7, 3}, + {"atilde", "\xC3\xA3", 6, 2}, + {"auml", "\xC3\xA4", 4, 2}, + {"awconint", "\xE2\x88\xB3", 8, 3}, + {"awint", "\xE2\xA8\x91", 5, 3}, + {"bNot", "\xE2\xAB\xAD", 4, 3}, + {"backcong", "\xE2\x89\x8C", 8, 3}, + {"backepsilon", "\xCF\xB6", 11, 2}, + {"backprime", "\xE2\x80\xB5", 9, 3}, + {"backsim", "\xE2\x88\xBD", 7, 3}, + {"backsimeq", "\xE2\x8B\x8D", 9, 3}, + {"barvee", "\xE2\x8A\xBD", 6, 3}, + {"barwed", "\xE2\x8C\x85", 6, 3}, + {"barwedge", "\xE2\x8C\x85", 8, 3}, + {"bbrk", "\xE2\x8E\xB5", 4, 3}, + {"bbrktbrk", "\xE2\x8E\xB6", 8, 3}, + {"bcong", "\xE2\x89\x8C", 5, 3}, + {"bcy", "\xD0\xB1", 3, 2}, + {"bdquo", "\xE2\x80\x9E", 5, 3}, + {"becaus", "\xE2\x88\xB5", 6, 3}, + {"because", "\xE2\x88\xB5", 7, 3}, + {"bemptyv", "\xE2\xA6\xB0", 7, 3}, + {"bepsi", "\xCF\xB6", 5, 2}, + {"bernou", "\xE2\x84\xAC", 6, 3}, + {"beta", "\xCE\xB2", 4, 2}, + {"beth", "\xE2\x84\xB6", 4, 3}, + {"between", "\xE2\x89\xAC", 7, 3}, + {"bfr", "\xF0\x9D\x94\x9F", 3, 4}, + {"bigcap", "\xE2\x8B\x82", 6, 3}, + {"bigcirc", "\xE2\x97\xAF", 7, 3}, + {"bigcup", "\xE2\x8B\x83", 6, 3}, + {"bigodot", "\xE2\xA8\x80", 7, 3}, + {"bigoplus", "\xE2\xA8\x81", 8, 3}, + {"bigotimes", "\xE2\xA8\x82", 9, 3}, + {"bigsqcup", "\xE2\xA8\x86", 8, 3}, + {"bigstar", "\xE2\x98\x85", 7, 3}, + {"bigtriangledown", "\xE2\x96\xBD", 15, 3}, + {"bigtriangleup", "\xE2\x96\xB3", 13, 3}, + {"biguplus", "\xE2\xA8\x84", 8, 3}, + {"bigvee", "\xE2\x8B\x81", 6, 3}, + {"bigwedge", "\xE2\x8B\x80", 8, 3}, + {"bkarow", "\xE2\xA4\x8D", 6, 3}, + {"blacklozenge", "\xE2\xA7\xAB", 12, 3}, + {"blacksquare", "\xE2\x96\xAA", 11, 3}, + {"blacktriangle", "\xE2\x96\xB4", 13, 3}, + {"blacktriangledown", "\xE2\x96\xBE", 17, 3}, + {"blacktriangleleft", "\xE2\x97\x82", 17, 3}, + {"blacktriangleright", "\xE2\x96\xB8", 18, 3}, + {"blank", "\xE2\x90\xA3", 5, 3}, + {"blk12", "\xE2\x96\x92", 5, 3}, + {"blk14", "\xE2\x96\x91", 5, 3}, + {"blk34", "\xE2\x96\x93", 5, 3}, + {"block", "\xE2\x96\x88", 5, 3}, + {"bne", "\x3D\xE2\x83\xA5", 3, 4}, + {"bnequiv", "\xE2\x89\xA1\xE2\x83\xA5", 7, 6}, + {"bnot", "\xE2\x8C\x90", 4, 3}, + {"bopf", "\xF0\x9D\x95\x93", 4, 4}, + {"bot", "\xE2\x8A\xA5", 3, 3}, + {"bottom", "\xE2\x8A\xA5", 6, 3}, + {"bowtie", "\xE2\x8B\x88", 6, 3}, + {"boxDL", "\xE2\x95\x97", 5, 3}, + {"boxDR", "\xE2\x95\x94", 5, 3}, + {"boxDl", "\xE2\x95\x96", 5, 3}, + {"boxDr", "\xE2\x95\x93", 5, 3}, + {"boxH", "\xE2\x95\x90", 4, 3}, + {"boxHD", "\xE2\x95\xA6", 5, 3}, + {"boxHU", "\xE2\x95\xA9", 5, 3}, + {"boxHd", "\xE2\x95\xA4", 5, 3}, + {"boxHu", "\xE2\x95\xA7", 5, 3}, + {"boxUL", "\xE2\x95\x9D", 5, 3}, + {"boxUR", "\xE2\x95\x9A", 5, 3}, + {"boxUl", "\xE2\x95\x9C", 5, 3}, + {"boxUr", "\xE2\x95\x99", 5, 3}, + {"boxV", "\xE2\x95\x91", 4, 3}, + {"boxVH", "\xE2\x95\xAC", 5, 3}, + {"boxVL", "\xE2\x95\xA3", 5, 3}, + {"boxVR", "\xE2\x95\xA0", 5, 3}, + {"boxVh", "\xE2\x95\xAB", 5, 3}, + {"boxVl", "\xE2\x95\xA2", 5, 3}, + {"boxVr", "\xE2\x95\x9F", 5, 3}, + {"boxbox", "\xE2\xA7\x89", 6, 3}, + {"boxdL", "\xE2\x95\x95", 5, 3}, + {"boxdR", "\xE2\x95\x92", 5, 3}, + {"boxdl", "\xE2\x94\x90", 5, 3}, + {"boxdr", "\xE2\x94\x8C", 5, 3}, + {"boxh", "\xE2\x94\x80", 4, 3}, + {"boxhD", "\xE2\x95\xA5", 5, 3}, + {"boxhU", "\xE2\x95\xA8", 5, 3}, + {"boxhd", "\xE2\x94\xAC", 5, 3}, + {"boxhu", "\xE2\x94\xB4", 5, 3}, + {"boxminus", "\xE2\x8A\x9F", 8, 3}, + {"boxplus", "\xE2\x8A\x9E", 7, 3}, + {"boxtimes", "\xE2\x8A\xA0", 8, 3}, + {"boxuL", "\xE2\x95\x9B", 5, 3}, + {"boxuR", "\xE2\x95\x98", 5, 3}, + {"boxul", "\xE2\x94\x98", 5, 3}, + {"boxur", "\xE2\x94\x94", 5, 3}, + {"boxv", "\xE2\x94\x82", 4, 3}, + {"boxvH", "\xE2\x95\xAA", 5, 3}, + {"boxvL", "\xE2\x95\xA1", 5, 3}, + {"boxvR", "\xE2\x95\x9E", 5, 3}, + {"boxvh", "\xE2\x94\xBC", 5, 3}, + {"boxvl", "\xE2\x94\xA4", 5, 3}, + {"boxvr", "\xE2\x94\x9C", 5, 3}, + {"bprime", "\xE2\x80\xB5", 6, 3}, + {"breve", "\xCB\x98", 5, 2}, + {"brvbar", "\xC2\xA6", 6, 2}, + {"bscr", "\xF0\x9D\x92\xB7", 4, 4}, + {"bsemi", "\xE2\x81\x8F", 5, 3}, + {"bsim", "\xE2\x88\xBD", 4, 3}, + {"bsime", "\xE2\x8B\x8D", 5, 3}, + {"bsol", "\x5C", 4, 1}, + {"bsolb", "\xE2\xA7\x85", 5, 3}, + {"bsolhsub", "\xE2\x9F\x88", 8, 3}, + {"bull", "\xE2\x80\xA2", 4, 3}, + {"bullet", "\xE2\x80\xA2", 6, 3}, + {"bump", "\xE2\x89\x8E", 4, 3}, + {"bumpE", "\xE2\xAA\xAE", 5, 3}, + {"bumpe", "\xE2\x89\x8F", 5, 3}, + {"bumpeq", "\xE2\x89\x8F", 6, 3}, + {"cacute", "\xC4\x87", 6, 2}, + {"cap", "\xE2\x88\xA9", 3, 3}, + {"capand", "\xE2\xA9\x84", 6, 3}, + {"capbrcup", "\xE2\xA9\x89", 8, 3}, + {"capcap", "\xE2\xA9\x8B", 6, 3}, + {"capcup", "\xE2\xA9\x87", 6, 3}, + {"capdot", "\xE2\xA9\x80", 6, 3}, + {"caps", "\xE2\x88\xA9\xEF\xB8\x80", 4, 6}, + {"caret", "\xE2\x81\x81", 5, 3}, + {"caron", "\xCB\x87", 5, 2}, + {"ccaps", "\xE2\xA9\x8D", 5, 3}, + {"ccaron", "\xC4\x8D", 6, 2}, + {"ccedil", "\xC3\xA7", 6, 2}, + {"ccirc", "\xC4\x89", 5, 2}, + {"ccups", "\xE2\xA9\x8C", 5, 3}, + {"ccupssm", "\xE2\xA9\x90", 7, 3}, + {"cdot", "\xC4\x8B", 4, 2}, + {"cedil", "\xC2\xB8", 5, 2}, + {"cemptyv", "\xE2\xA6\xB2", 7, 3}, + {"cent", "\xC2\xA2", 4, 2}, + {"centerdot", "\xC2\xB7", 9, 2}, + {"cfr", "\xF0\x9D\x94\xA0", 3, 4}, + {"chcy", "\xD1\x87", 4, 2}, + {"check", "\xE2\x9C\x93", 5, 3}, + {"checkmark", "\xE2\x9C\x93", 9, 3}, + {"chi", "\xCF\x87", 3, 2}, + {"cir", "\xE2\x97\x8B", 3, 3}, + {"cirE", "\xE2\xA7\x83", 4, 3}, + {"circ", "\xCB\x86", 4, 2}, + {"circeq", "\xE2\x89\x97", 6, 3}, + {"circlearrowleft", "\xE2\x86\xBA", 15, 3}, + {"circlearrowright", "\xE2\x86\xBB", 16, 3}, + {"circledR", "\xC2\xAE", 8, 2}, + {"circledS", "\xE2\x93\x88", 8, 3}, + {"circledast", "\xE2\x8A\x9B", 10, 3}, + {"circledcirc", "\xE2\x8A\x9A", 11, 3}, + {"circleddash", "\xE2\x8A\x9D", 11, 3}, + {"cire", "\xE2\x89\x97", 4, 3}, + {"cirfnint", "\xE2\xA8\x90", 8, 3}, + {"cirmid", "\xE2\xAB\xAF", 6, 3}, + {"cirscir", "\xE2\xA7\x82", 7, 3}, + {"clubs", "\xE2\x99\xA3", 5, 3}, + {"clubsuit", "\xE2\x99\xA3", 8, 3}, + {"colon", "\x3A", 5, 1}, + {"colone", "\xE2\x89\x94", 6, 3}, + {"coloneq", "\xE2\x89\x94", 7, 3}, + {"comma", "\x2C", 5, 1}, + {"commat", "\x40", 6, 1}, + {"comp", "\xE2\x88\x81", 4, 3}, + {"compfn", "\xE2\x88\x98", 6, 3}, + {"complement", "\xE2\x88\x81", 10, 3}, + {"complexes", "\xE2\x84\x82", 9, 3}, + {"cong", "\xE2\x89\x85", 4, 3}, + {"congdot", "\xE2\xA9\xAD", 7, 3}, + {"conint", "\xE2\x88\xAE", 6, 3}, + {"copf", "\xF0\x9D\x95\x94", 4, 4}, + {"coprod", "\xE2\x88\x90", 6, 3}, + {"copy", "\xC2\xA9", 4, 2}, + {"copysr", "\xE2\x84\x97", 6, 3}, + {"crarr", "\xE2\x86\xB5", 5, 3}, + {"cross", "\xE2\x9C\x97", 5, 3}, + {"cscr", "\xF0\x9D\x92\xB8", 4, 4}, + {"csub", "\xE2\xAB\x8F", 4, 3}, + {"csube", "\xE2\xAB\x91", 5, 3}, + {"csup", "\xE2\xAB\x90", 4, 3}, + {"csupe", "\xE2\xAB\x92", 5, 3}, + {"ctdot", "\xE2\x8B\xAF", 5, 3}, + {"cudarrl", "\xE2\xA4\xB8", 7, 3}, + {"cudarrr", "\xE2\xA4\xB5", 7, 3}, + {"cuepr", "\xE2\x8B\x9E", 5, 3}, + {"cuesc", "\xE2\x8B\x9F", 5, 3}, + {"cularr", "\xE2\x86\xB6", 6, 3}, + {"cularrp", "\xE2\xA4\xBD", 7, 3}, + {"cup", "\xE2\x88\xAA", 3, 3}, + {"cupbrcap", "\xE2\xA9\x88", 8, 3}, + {"cupcap", "\xE2\xA9\x86", 6, 3}, + {"cupcup", "\xE2\xA9\x8A", 6, 3}, + {"cupdot", "\xE2\x8A\x8D", 6, 3}, + {"cupor", "\xE2\xA9\x85", 5, 3}, + {"cups", "\xE2\x88\xAA\xEF\xB8\x80", 4, 6}, + {"curarr", "\xE2\x86\xB7", 6, 3}, + {"curarrm", "\xE2\xA4\xBC", 7, 3}, + {"curlyeqprec", "\xE2\x8B\x9E", 11, 3}, + {"curlyeqsucc", "\xE2\x8B\x9F", 11, 3}, + {"curlyvee", "\xE2\x8B\x8E", 8, 3}, + {"curlywedge", "\xE2\x8B\x8F", 10, 3}, + {"curren", "\xC2\xA4", 6, 2}, + {"curvearrowleft", "\xE2\x86\xB6", 14, 3}, + {"curvearrowright", "\xE2\x86\xB7", 15, 3}, + {"cuvee", "\xE2\x8B\x8E", 5, 3}, + {"cuwed", "\xE2\x8B\x8F", 5, 3}, + {"cwconint", "\xE2\x88\xB2", 8, 3}, + {"cwint", "\xE2\x88\xB1", 5, 3}, + {"cylcty", "\xE2\x8C\xAD", 6, 3}, + {"dArr", "\xE2\x87\x93", 4, 3}, + {"dHar", "\xE2\xA5\xA5", 4, 3}, + {"dagger", "\xE2\x80\xA0", 6, 3}, + {"daleth", "\xE2\x84\xB8", 6, 3}, + {"darr", "\xE2\x86\x93", 4, 3}, + {"dash", "\xE2\x80\x90", 4, 3}, + {"dashv", "\xE2\x8A\xA3", 5, 3}, + {"dbkarow", "\xE2\xA4\x8F", 7, 3}, + {"dblac", "\xCB\x9D", 5, 2}, + {"dcaron", "\xC4\x8F", 6, 2}, + {"dcy", "\xD0\xB4", 3, 2}, + {"dd", "\xE2\x85\x86", 2, 3}, + {"ddagger", "\xE2\x80\xA1", 7, 3}, + {"ddarr", "\xE2\x87\x8A", 5, 3}, + {"ddotseq", "\xE2\xA9\xB7", 7, 3}, + {"deg", "\xC2\xB0", 3, 2}, + {"delta", "\xCE\xB4", 5, 2}, + {"demptyv", "\xE2\xA6\xB1", 7, 3}, + {"dfisht", "\xE2\xA5\xBF", 6, 3}, + {"dfr", "\xF0\x9D\x94\xA1", 3, 4}, + {"dharl", "\xE2\x87\x83", 5, 3}, + {"dharr", "\xE2\x87\x82", 5, 3}, + {"diam", "\xE2\x8B\x84", 4, 3}, + {"diamond", "\xE2\x8B\x84", 7, 3}, + {"diamondsuit", "\xE2\x99\xA6", 11, 3}, + {"diams", "\xE2\x99\xA6", 5, 3}, + {"die", "\xC2\xA8", 3, 2}, + {"digamma", "\xCF\x9D", 7, 2}, + {"disin", "\xE2\x8B\xB2", 5, 3}, + {"div", "\xC3\xB7", 3, 2}, + {"divide", "\xC3\xB7", 6, 2}, + {"divideontimes", "\xE2\x8B\x87", 13, 3}, + {"divonx", "\xE2\x8B\x87", 6, 3}, + {"djcy", "\xD1\x92", 4, 2}, + {"dlcorn", "\xE2\x8C\x9E", 6, 3}, + {"dlcrop", "\xE2\x8C\x8D", 6, 3}, + {"dollar", "\x24", 6, 1}, + {"dopf", "\xF0\x9D\x95\x95", 4, 4}, + {"dot", "\xCB\x99", 3, 2}, + {"doteq", "\xE2\x89\x90", 5, 3}, + {"doteqdot", "\xE2\x89\x91", 8, 3}, + {"dotminus", "\xE2\x88\xB8", 8, 3}, + {"dotplus", "\xE2\x88\x94", 7, 3}, + {"dotsquare", "\xE2\x8A\xA1", 9, 3}, + {"doublebarwedge", "\xE2\x8C\x86", 14, 3}, + {"downarrow", "\xE2\x86\x93", 9, 3}, + {"downdownarrows", "\xE2\x87\x8A", 14, 3}, + {"downharpoonleft", "\xE2\x87\x83", 15, 3}, + {"downharpoonright", "\xE2\x87\x82", 16, 3}, + {"drbkarow", "\xE2\xA4\x90", 8, 3}, + {"drcorn", "\xE2\x8C\x9F", 6, 3}, + {"drcrop", "\xE2\x8C\x8C", 6, 3}, + {"dscr", "\xF0\x9D\x92\xB9", 4, 4}, + {"dscy", "\xD1\x95", 4, 2}, + {"dsol", "\xE2\xA7\xB6", 4, 3}, + {"dstrok", "\xC4\x91", 6, 2}, + {"dtdot", "\xE2\x8B\xB1", 5, 3}, + {"dtri", "\xE2\x96\xBF", 4, 3}, + {"dtrif", "\xE2\x96\xBE", 5, 3}, + {"duarr", "\xE2\x87\xB5", 5, 3}, + {"duhar", "\xE2\xA5\xAF", 5, 3}, + {"dwangle", "\xE2\xA6\xA6", 7, 3}, + {"dzcy", "\xD1\x9F", 4, 2}, + {"dzigrarr", "\xE2\x9F\xBF", 8, 3}, + {"eDDot", "\xE2\xA9\xB7", 5, 3}, + {"eDot", "\xE2\x89\x91", 4, 3}, + {"eacute", "\xC3\xA9", 6, 2}, + {"easter", "\xE2\xA9\xAE", 6, 3}, + {"ecaron", "\xC4\x9B", 6, 2}, + {"ecir", "\xE2\x89\x96", 4, 3}, + {"ecirc", "\xC3\xAA", 5, 2}, + {"ecolon", "\xE2\x89\x95", 6, 3}, + {"ecy", "\xD1\x8D", 3, 2}, + {"edot", "\xC4\x97", 4, 2}, + {"ee", "\xE2\x85\x87", 2, 3}, + {"efDot", "\xE2\x89\x92", 5, 3}, + {"efr", "\xF0\x9D\x94\xA2", 3, 4}, + {"eg", "\xE2\xAA\x9A", 2, 3}, + {"egrave", "\xC3\xA8", 6, 2}, + {"egs", "\xE2\xAA\x96", 3, 3}, + {"egsdot", "\xE2\xAA\x98", 6, 3}, + {"el", "\xE2\xAA\x99", 2, 3}, + {"elinters", "\xE2\x8F\xA7", 8, 3}, + {"ell", "\xE2\x84\x93", 3, 3}, + {"els", "\xE2\xAA\x95", 3, 3}, + {"elsdot", "\xE2\xAA\x97", 6, 3}, + {"emacr", "\xC4\x93", 5, 2}, + {"empty", "\xE2\x88\x85", 5, 3}, + {"emptyset", "\xE2\x88\x85", 8, 3}, + {"emptyv", "\xE2\x88\x85", 6, 3}, + {"emsp", "\xE2\x80\x83", 4, 3}, + {"emsp13", "\xE2\x80\x84", 6, 3}, + {"emsp14", "\xE2\x80\x85", 6, 3}, + {"eng", "\xC5\x8B", 3, 2}, + {"ensp", "\xE2\x80\x82", 4, 3}, + {"eogon", "\xC4\x99", 5, 2}, + {"eopf", "\xF0\x9D\x95\x96", 4, 4}, + {"epar", "\xE2\x8B\x95", 4, 3}, + {"eparsl", "\xE2\xA7\xA3", 6, 3}, + {"eplus", "\xE2\xA9\xB1", 5, 3}, + {"epsi", "\xCE\xB5", 4, 2}, + {"epsilon", "\xCE\xB5", 7, 2}, + {"epsiv", "\xCF\xB5", 5, 2}, + {"eqcirc", "\xE2\x89\x96", 6, 3}, + {"eqcolon", "\xE2\x89\x95", 7, 3}, + {"eqsim", "\xE2\x89\x82", 5, 3}, + {"eqslantgtr", "\xE2\xAA\x96", 10, 3}, + {"eqslantless", "\xE2\xAA\x95", 11, 3}, + {"equals", "\x3D", 6, 1}, + {"equest", "\xE2\x89\x9F", 6, 3}, + {"equiv", "\xE2\x89\xA1", 5, 3}, + {"equivDD", "\xE2\xA9\xB8", 7, 3}, + {"eqvparsl", "\xE2\xA7\xA5", 8, 3}, + {"erDot", "\xE2\x89\x93", 5, 3}, + {"erarr", "\xE2\xA5\xB1", 5, 3}, + {"escr", "\xE2\x84\xAF", 4, 3}, + {"esdot", "\xE2\x89\x90", 5, 3}, + {"esim", "\xE2\x89\x82", 4, 3}, + {"eta", "\xCE\xB7", 3, 2}, + {"eth", "\xC3\xB0", 3, 2}, + {"euml", "\xC3\xAB", 4, 2}, + {"euro", "\xE2\x82\xAC", 4, 3}, + {"excl", "\x21", 4, 1}, + {"exist", "\xE2\x88\x83", 5, 3}, + {"expectation", "\xE2\x84\xB0", 11, 3}, + {"exponentiale", "\xE2\x85\x87", 12, 3}, + {"fallingdotseq", "\xE2\x89\x92", 13, 3}, + {"fcy", "\xD1\x84", 3, 2}, + {"female", "\xE2\x99\x80", 6, 3}, + {"ffilig", "\xEF\xAC\x83", 6, 3}, + {"fflig", "\xEF\xAC\x80", 5, 3}, + {"ffllig", "\xEF\xAC\x84", 6, 3}, + {"ffr", "\xF0\x9D\x94\xA3", 3, 4}, + {"filig", "\xEF\xAC\x81", 5, 3}, + {"fjlig", "\x66\x6A", 5, 2}, + {"flat", "\xE2\x99\xAD", 4, 3}, + {"fllig", "\xEF\xAC\x82", 5, 3}, + {"fltns", "\xE2\x96\xB1", 5, 3}, + {"fnof", "\xC6\x92", 4, 2}, + {"fopf", "\xF0\x9D\x95\x97", 4, 4}, + {"forall", "\xE2\x88\x80", 6, 3}, + {"fork", "\xE2\x8B\x94", 4, 3}, + {"forkv", "\xE2\xAB\x99", 5, 3}, + {"fpartint", "\xE2\xA8\x8D", 8, 3}, + {"frac12", "\xC2\xBD", 6, 2}, + {"frac13", "\xE2\x85\x93", 6, 3}, + {"frac14", "\xC2\xBC", 6, 2}, + {"frac15", "\xE2\x85\x95", 6, 3}, + {"frac16", "\xE2\x85\x99", 6, 3}, + {"frac18", "\xE2\x85\x9B", 6, 3}, + {"frac23", "\xE2\x85\x94", 6, 3}, + {"frac25", "\xE2\x85\x96", 6, 3}, + {"frac34", "\xC2\xBE", 6, 2}, + {"frac35", "\xE2\x85\x97", 6, 3}, + {"frac38", "\xE2\x85\x9C", 6, 3}, + {"frac45", "\xE2\x85\x98", 6, 3}, + {"frac56", "\xE2\x85\x9A", 6, 3}, + {"frac58", "\xE2\x85\x9D", 6, 3}, + {"frac78", "\xE2\x85\x9E", 6, 3}, + {"frasl", "\xE2\x81\x84", 5, 3}, + {"frown", "\xE2\x8C\xA2", 5, 3}, + {"fscr", "\xF0\x9D\x92\xBB", 4, 4}, + {"gE", "\xE2\x89\xA7", 2, 3}, + {"gEl", "\xE2\xAA\x8C", 3, 3}, + {"gacute", "\xC7\xB5", 6, 2}, + {"gamma", "\xCE\xB3", 5, 2}, + {"gammad", "\xCF\x9D", 6, 2}, + {"gap", "\xE2\xAA\x86", 3, 3}, + {"gbreve", "\xC4\x9F", 6, 2}, + {"gcirc", "\xC4\x9D", 5, 2}, + {"gcy", "\xD0\xB3", 3, 2}, + {"gdot", "\xC4\xA1", 4, 2}, + {"ge", "\xE2\x89\xA5", 2, 3}, + {"gel", "\xE2\x8B\x9B", 3, 3}, + {"geq", "\xE2\x89\xA5", 3, 3}, + {"geqq", "\xE2\x89\xA7", 4, 3}, + {"geqslant", "\xE2\xA9\xBE", 8, 3}, + {"ges", "\xE2\xA9\xBE", 3, 3}, + {"gescc", "\xE2\xAA\xA9", 5, 3}, + {"gesdot", "\xE2\xAA\x80", 6, 3}, + {"gesdoto", "\xE2\xAA\x82", 7, 3}, + {"gesdotol", "\xE2\xAA\x84", 8, 3}, + {"gesl", "\xE2\x8B\x9B\xEF\xB8\x80", 4, 6}, + {"gesles", "\xE2\xAA\x94", 6, 3}, + {"gfr", "\xF0\x9D\x94\xA4", 3, 4}, + {"gg", "\xE2\x89\xAB", 2, 3}, + {"ggg", "\xE2\x8B\x99", 3, 3}, + {"gimel", "\xE2\x84\xB7", 5, 3}, + {"gjcy", "\xD1\x93", 4, 2}, + {"gl", "\xE2\x89\xB7", 2, 3}, + {"glE", "\xE2\xAA\x92", 3, 3}, + {"gla", "\xE2\xAA\xA5", 3, 3}, + {"glj", "\xE2\xAA\xA4", 3, 3}, + {"gnE", "\xE2\x89\xA9", 3, 3}, + {"gnap", "\xE2\xAA\x8A", 4, 3}, + {"gnapprox", "\xE2\xAA\x8A", 8, 3}, + {"gne", "\xE2\xAA\x88", 3, 3}, + {"gneq", "\xE2\xAA\x88", 4, 3}, + {"gneqq", "\xE2\x89\xA9", 5, 3}, + {"gnsim", "\xE2\x8B\xA7", 5, 3}, + {"gopf", "\xF0\x9D\x95\x98", 4, 4}, + {"grave", "\x60", 5, 1}, + {"gscr", "\xE2\x84\x8A", 4, 3}, + {"gsim", "\xE2\x89\xB3", 4, 3}, + {"gsime", "\xE2\xAA\x8E", 5, 3}, + {"gsiml", "\xE2\xAA\x90", 5, 3}, + {"gt", "\x3E", 2, 1}, + {"gtcc", "\xE2\xAA\xA7", 4, 3}, + {"gtcir", "\xE2\xA9\xBA", 5, 3}, + {"gtdot", "\xE2\x8B\x97", 5, 3}, + {"gtlPar", "\xE2\xA6\x95", 6, 3}, + {"gtquest", "\xE2\xA9\xBC", 7, 3}, + {"gtrapprox", "\xE2\xAA\x86", 9, 3}, + {"gtrarr", "\xE2\xA5\xB8", 6, 3}, + {"gtrdot", "\xE2\x8B\x97", 6, 3}, + {"gtreqless", "\xE2\x8B\x9B", 9, 3}, + {"gtreqqless", "\xE2\xAA\x8C", 10, 3}, + {"gtrless", "\xE2\x89\xB7", 7, 3}, + {"gtrsim", "\xE2\x89\xB3", 6, 3}, + {"gvertneqq", "\xE2\x89\xA9\xEF\xB8\x80", 9, 6}, + {"gvnE", "\xE2\x89\xA9\xEF\xB8\x80", 4, 6}, + {"hArr", "\xE2\x87\x94", 4, 3}, + {"hairsp", "\xE2\x80\x8A", 6, 3}, + {"half", "\xC2\xBD", 4, 2}, + {"hamilt", "\xE2\x84\x8B", 6, 3}, + {"hardcy", "\xD1\x8A", 6, 2}, + {"harr", "\xE2\x86\x94", 4, 3}, + {"harrcir", "\xE2\xA5\x88", 7, 3}, + {"harrw", "\xE2\x86\xAD", 5, 3}, + {"hbar", "\xE2\x84\x8F", 4, 3}, + {"hcirc", "\xC4\xA5", 5, 2}, + {"hearts", "\xE2\x99\xA5", 6, 3}, + {"heartsuit", "\xE2\x99\xA5", 9, 3}, + {"hellip", "\xE2\x80\xA6", 6, 3}, + {"hercon", "\xE2\x8A\xB9", 6, 3}, + {"hfr", "\xF0\x9D\x94\xA5", 3, 4}, + {"hksearow", "\xE2\xA4\xA5", 8, 3}, + {"hkswarow", "\xE2\xA4\xA6", 8, 3}, + {"hoarr", "\xE2\x87\xBF", 5, 3}, + {"homtht", "\xE2\x88\xBB", 6, 3}, + {"hookleftarrow", "\xE2\x86\xA9", 13, 3}, + {"hookrightarrow", "\xE2\x86\xAA", 14, 3}, + {"hopf", "\xF0\x9D\x95\x99", 4, 4}, + {"horbar", "\xE2\x80\x95", 6, 3}, + {"hscr", "\xF0\x9D\x92\xBD", 4, 4}, + {"hslash", "\xE2\x84\x8F", 6, 3}, + {"hstrok", "\xC4\xA7", 6, 2}, + {"hybull", "\xE2\x81\x83", 6, 3}, + {"hyphen", "\xE2\x80\x90", 6, 3}, + {"iacute", "\xC3\xAD", 6, 2}, + {"ic", "\xE2\x81\xA3", 2, 3}, + {"icirc", "\xC3\xAE", 5, 2}, + {"icy", "\xD0\xB8", 3, 2}, + {"iecy", "\xD0\xB5", 4, 2}, + {"iexcl", "\xC2\xA1", 5, 2}, + {"iff", "\xE2\x87\x94", 3, 3}, + {"ifr", "\xF0\x9D\x94\xA6", 3, 4}, + {"igrave", "\xC3\xAC", 6, 2}, + {"ii", "\xE2\x85\x88", 2, 3}, + {"iiiint", "\xE2\xA8\x8C", 6, 3}, + {"iiint", "\xE2\x88\xAD", 5, 3}, + {"iinfin", "\xE2\xA7\x9C", 6, 3}, + {"iiota", "\xE2\x84\xA9", 5, 3}, + {"ijlig", "\xC4\xB3", 5, 2}, + {"imacr", "\xC4\xAB", 5, 2}, + {"image", "\xE2\x84\x91", 5, 3}, + {"imagline", "\xE2\x84\x90", 8, 3}, + {"imagpart", "\xE2\x84\x91", 8, 3}, + {"imath", "\xC4\xB1", 5, 2}, + {"imof", "\xE2\x8A\xB7", 4, 3}, + {"imped", "\xC6\xB5", 5, 2}, + {"in", "\xE2\x88\x88", 2, 3}, + {"incare", "\xE2\x84\x85", 6, 3}, + {"infin", "\xE2\x88\x9E", 5, 3}, + {"infintie", "\xE2\xA7\x9D", 8, 3}, + {"inodot", "\xC4\xB1", 6, 2}, + {"int", "\xE2\x88\xAB", 3, 3}, + {"intcal", "\xE2\x8A\xBA", 6, 3}, + {"integers", "\xE2\x84\xA4", 8, 3}, + {"intercal", "\xE2\x8A\xBA", 8, 3}, + {"intlarhk", "\xE2\xA8\x97", 8, 3}, + {"intprod", "\xE2\xA8\xBC", 7, 3}, + {"iocy", "\xD1\x91", 4, 2}, + {"iogon", "\xC4\xAF", 5, 2}, + {"iopf", "\xF0\x9D\x95\x9A", 4, 4}, + {"iota", "\xCE\xB9", 4, 2}, + {"iprod", "\xE2\xA8\xBC", 5, 3}, + {"iquest", "\xC2\xBF", 6, 2}, + {"iscr", "\xF0\x9D\x92\xBE", 4, 4}, + {"isin", "\xE2\x88\x88", 4, 3}, + {"isinE", "\xE2\x8B\xB9", 5, 3}, + {"isindot", "\xE2\x8B\xB5", 7, 3}, + {"isins", "\xE2\x8B\xB4", 5, 3}, + {"isinsv", "\xE2\x8B\xB3", 6, 3}, + {"isinv", "\xE2\x88\x88", 5, 3}, + {"it", "\xE2\x81\xA2", 2, 3}, + {"itilde", "\xC4\xA9", 6, 2}, + {"iukcy", "\xD1\x96", 5, 2}, + {"iuml", "\xC3\xAF", 4, 2}, + {"jcirc", "\xC4\xB5", 5, 2}, + {"jcy", "\xD0\xB9", 3, 2}, + {"jfr", "\xF0\x9D\x94\xA7", 3, 4}, + {"jmath", "\xC8\xB7", 5, 2}, + {"jopf", "\xF0\x9D\x95\x9B", 4, 4}, + {"jscr", "\xF0\x9D\x92\xBF", 4, 4}, + {"jsercy", "\xD1\x98", 6, 2}, + {"jukcy", "\xD1\x94", 5, 2}, + {"kappa", "\xCE\xBA", 5, 2}, + {"kappav", "\xCF\xB0", 6, 2}, + {"kcedil", "\xC4\xB7", 6, 2}, + {"kcy", "\xD0\xBA", 3, 2}, + {"kfr", "\xF0\x9D\x94\xA8", 3, 4}, + {"kgreen", "\xC4\xB8", 6, 2}, + {"khcy", "\xD1\x85", 4, 2}, + {"kjcy", "\xD1\x9C", 4, 2}, + {"kopf", "\xF0\x9D\x95\x9C", 4, 4}, + {"kscr", "\xF0\x9D\x93\x80", 4, 4}, + {"lAarr", "\xE2\x87\x9A", 5, 3}, + {"lArr", "\xE2\x87\x90", 4, 3}, + {"lAtail", "\xE2\xA4\x9B", 6, 3}, + {"lBarr", "\xE2\xA4\x8E", 5, 3}, + {"lE", "\xE2\x89\xA6", 2, 3}, + {"lEg", "\xE2\xAA\x8B", 3, 3}, + {"lHar", "\xE2\xA5\xA2", 4, 3}, + {"lacute", "\xC4\xBA", 6, 2}, + {"laemptyv", "\xE2\xA6\xB4", 8, 3}, + {"lagran", "\xE2\x84\x92", 6, 3}, + {"lambda", "\xCE\xBB", 6, 2}, + {"lang", "\xE2\x9F\xA8", 4, 3}, + {"langd", "\xE2\xA6\x91", 5, 3}, + {"langle", "\xE2\x9F\xA8", 6, 3}, + {"lap", "\xE2\xAA\x85", 3, 3}, + {"laquo", "\xC2\xAB", 5, 2}, + {"larr", "\xE2\x86\x90", 4, 3}, + {"larrb", "\xE2\x87\xA4", 5, 3}, + {"larrbfs", "\xE2\xA4\x9F", 7, 3}, + {"larrfs", "\xE2\xA4\x9D", 6, 3}, + {"larrhk", "\xE2\x86\xA9", 6, 3}, + {"larrlp", "\xE2\x86\xAB", 6, 3}, + {"larrpl", "\xE2\xA4\xB9", 6, 3}, + {"larrsim", "\xE2\xA5\xB3", 7, 3}, + {"larrtl", "\xE2\x86\xA2", 6, 3}, + {"lat", "\xE2\xAA\xAB", 3, 3}, + {"latail", "\xE2\xA4\x99", 6, 3}, + {"late", "\xE2\xAA\xAD", 4, 3}, + {"lates", "\xE2\xAA\xAD\xEF\xB8\x80", 5, 6}, + {"lbarr", "\xE2\xA4\x8C", 5, 3}, + {"lbbrk", "\xE2\x9D\xB2", 5, 3}, + {"lbrace", "\x7B", 6, 1}, + {"lbrack", "\x5B", 6, 1}, + {"lbrke", "\xE2\xA6\x8B", 5, 3}, + {"lbrksld", "\xE2\xA6\x8F", 7, 3}, + {"lbrkslu", "\xE2\xA6\x8D", 7, 3}, + {"lcaron", "\xC4\xBE", 6, 2}, + {"lcedil", "\xC4\xBC", 6, 2}, + {"lceil", "\xE2\x8C\x88", 5, 3}, + {"lcub", "\x7B", 4, 1}, + {"lcy", "\xD0\xBB", 3, 2}, + {"ldca", "\xE2\xA4\xB6", 4, 3}, + {"ldquo", "\xE2\x80\x9C", 5, 3}, + {"ldquor", "\xE2\x80\x9E", 6, 3}, + {"ldrdhar", "\xE2\xA5\xA7", 7, 3}, + {"ldrushar", "\xE2\xA5\x8B", 8, 3}, + {"ldsh", "\xE2\x86\xB2", 4, 3}, + {"le", "\xE2\x89\xA4", 2, 3}, + {"leftarrow", "\xE2\x86\x90", 9, 3}, + {"leftarrowtail", "\xE2\x86\xA2", 13, 3}, + {"leftharpoondown", "\xE2\x86\xBD", 15, 3}, + {"leftharpoonup", "\xE2\x86\xBC", 13, 3}, + {"leftleftarrows", "\xE2\x87\x87", 14, 3}, + {"leftrightarrow", "\xE2\x86\x94", 14, 3}, + {"leftrightarrows", "\xE2\x87\x86", 15, 3}, + {"leftrightharpoons", "\xE2\x87\x8B", 17, 3}, + {"leftrightsquigarrow", "\xE2\x86\xAD", 19, 3}, + {"leftthreetimes", "\xE2\x8B\x8B", 14, 3}, + {"leg", "\xE2\x8B\x9A", 3, 3}, + {"leq", "\xE2\x89\xA4", 3, 3}, + {"leqq", "\xE2\x89\xA6", 4, 3}, + {"leqslant", "\xE2\xA9\xBD", 8, 3}, + {"les", "\xE2\xA9\xBD", 3, 3}, + {"lescc", "\xE2\xAA\xA8", 5, 3}, + {"lesdot", "\xE2\xA9\xBF", 6, 3}, + {"lesdoto", "\xE2\xAA\x81", 7, 3}, + {"lesdotor", "\xE2\xAA\x83", 8, 3}, + {"lesg", "\xE2\x8B\x9A\xEF\xB8\x80", 4, 6}, + {"lesges", "\xE2\xAA\x93", 6, 3}, + {"lessapprox", "\xE2\xAA\x85", 10, 3}, + {"lessdot", "\xE2\x8B\x96", 7, 3}, + {"lesseqgtr", "\xE2\x8B\x9A", 9, 3}, + {"lesseqqgtr", "\xE2\xAA\x8B", 10, 3}, + {"lessgtr", "\xE2\x89\xB6", 7, 3}, + {"lesssim", "\xE2\x89\xB2", 7, 3}, + {"lfisht", "\xE2\xA5\xBC", 6, 3}, + {"lfloor", "\xE2\x8C\x8A", 6, 3}, + {"lfr", "\xF0\x9D\x94\xA9", 3, 4}, + {"lg", "\xE2\x89\xB6", 2, 3}, + {"lgE", "\xE2\xAA\x91", 3, 3}, + {"lhard", "\xE2\x86\xBD", 5, 3}, + {"lharu", "\xE2\x86\xBC", 5, 3}, + {"lharul", "\xE2\xA5\xAA", 6, 3}, + {"lhblk", "\xE2\x96\x84", 5, 3}, + {"ljcy", "\xD1\x99", 4, 2}, + {"ll", "\xE2\x89\xAA", 2, 3}, + {"llarr", "\xE2\x87\x87", 5, 3}, + {"llcorner", "\xE2\x8C\x9E", 8, 3}, + {"llhard", "\xE2\xA5\xAB", 6, 3}, + {"lltri", "\xE2\x97\xBA", 5, 3}, + {"lmidot", "\xC5\x80", 6, 2}, + {"lmoust", "\xE2\x8E\xB0", 6, 3}, + {"lmoustache", "\xE2\x8E\xB0", 10, 3}, + {"lnE", "\xE2\x89\xA8", 3, 3}, + {"lnap", "\xE2\xAA\x89", 4, 3}, + {"lnapprox", "\xE2\xAA\x89", 8, 3}, + {"lne", "\xE2\xAA\x87", 3, 3}, + {"lneq", "\xE2\xAA\x87", 4, 3}, + {"lneqq", "\xE2\x89\xA8", 5, 3}, + {"lnsim", "\xE2\x8B\xA6", 5, 3}, + {"loang", "\xE2\x9F\xAC", 5, 3}, + {"loarr", "\xE2\x87\xBD", 5, 3}, + {"lobrk", "\xE2\x9F\xA6", 5, 3}, + {"longleftarrow", "\xE2\x9F\xB5", 13, 3}, + {"longleftrightarrow", "\xE2\x9F\xB7", 18, 3}, + {"longmapsto", "\xE2\x9F\xBC", 10, 3}, + {"longrightarrow", "\xE2\x9F\xB6", 14, 3}, + {"looparrowleft", "\xE2\x86\xAB", 13, 3}, + {"looparrowright", "\xE2\x86\xAC", 14, 3}, + {"lopar", "\xE2\xA6\x85", 5, 3}, + {"lopf", "\xF0\x9D\x95\x9D", 4, 4}, + {"loplus", "\xE2\xA8\xAD", 6, 3}, + {"lotimes", "\xE2\xA8\xB4", 7, 3}, + {"lowast", "\xE2\x88\x97", 6, 3}, + {"lowbar", "\x5F", 6, 1}, + {"loz", "\xE2\x97\x8A", 3, 3}, + {"lozenge", "\xE2\x97\x8A", 7, 3}, + {"lozf", "\xE2\xA7\xAB", 4, 3}, + {"lpar", "\x28", 4, 1}, + {"lparlt", "\xE2\xA6\x93", 6, 3}, + {"lrarr", "\xE2\x87\x86", 5, 3}, + {"lrcorner", "\xE2\x8C\x9F", 8, 3}, + {"lrhar", "\xE2\x87\x8B", 5, 3}, + {"lrhard", "\xE2\xA5\xAD", 6, 3}, + {"lrm", "\xE2\x80\x8E", 3, 3}, + {"lrtri", "\xE2\x8A\xBF", 5, 3}, + {"lsaquo", "\xE2\x80\xB9", 6, 3}, + {"lscr", "\xF0\x9D\x93\x81", 4, 4}, + {"lsh", "\xE2\x86\xB0", 3, 3}, + {"lsim", "\xE2\x89\xB2", 4, 3}, + {"lsime", "\xE2\xAA\x8D", 5, 3}, + {"lsimg", "\xE2\xAA\x8F", 5, 3}, + {"lsqb", "\x5B", 4, 1}, + {"lsquo", "\xE2\x80\x98", 5, 3}, + {"lsquor", "\xE2\x80\x9A", 6, 3}, + {"lstrok", "\xC5\x82", 6, 2}, + {"lt", "\x3C", 2, 1}, + {"ltcc", "\xE2\xAA\xA6", 4, 3}, + {"ltcir", "\xE2\xA9\xB9", 5, 3}, + {"ltdot", "\xE2\x8B\x96", 5, 3}, + {"lthree", "\xE2\x8B\x8B", 6, 3}, + {"ltimes", "\xE2\x8B\x89", 6, 3}, + {"ltlarr", "\xE2\xA5\xB6", 6, 3}, + {"ltquest", "\xE2\xA9\xBB", 7, 3}, + {"ltrPar", "\xE2\xA6\x96", 6, 3}, + {"ltri", "\xE2\x97\x83", 4, 3}, + {"ltrie", "\xE2\x8A\xB4", 5, 3}, + {"ltrif", "\xE2\x97\x82", 5, 3}, + {"lurdshar", "\xE2\xA5\x8A", 8, 3}, + {"luruhar", "\xE2\xA5\xA6", 7, 3}, + {"lvertneqq", "\xE2\x89\xA8\xEF\xB8\x80", 9, 6}, + {"lvnE", "\xE2\x89\xA8\xEF\xB8\x80", 4, 6}, + {"mDDot", "\xE2\x88\xBA", 5, 3}, + {"macr", "\xC2\xAF", 4, 2}, + {"male", "\xE2\x99\x82", 4, 3}, + {"malt", "\xE2\x9C\xA0", 4, 3}, + {"maltese", "\xE2\x9C\xA0", 7, 3}, + {"map", "\xE2\x86\xA6", 3, 3}, + {"mapsto", "\xE2\x86\xA6", 6, 3}, + {"mapstodown", "\xE2\x86\xA7", 10, 3}, + {"mapstoleft", "\xE2\x86\xA4", 10, 3}, + {"mapstoup", "\xE2\x86\xA5", 8, 3}, + {"marker", "\xE2\x96\xAE", 6, 3}, + {"mcomma", "\xE2\xA8\xA9", 6, 3}, + {"mcy", "\xD0\xBC", 3, 2}, + {"mdash", "\xE2\x80\x94", 5, 3}, + {"measuredangle", "\xE2\x88\xA1", 13, 3}, + {"mfr", "\xF0\x9D\x94\xAA", 3, 4}, + {"mho", "\xE2\x84\xA7", 3, 3}, + {"micro", "\xC2\xB5", 5, 2}, + {"mid", "\xE2\x88\xA3", 3, 3}, + {"midast", "\x2A", 6, 1}, + {"midcir", "\xE2\xAB\xB0", 6, 3}, + {"middot", "\xC2\xB7", 6, 2}, + {"minus", "\xE2\x88\x92", 5, 3}, + {"minusb", "\xE2\x8A\x9F", 6, 3}, + {"minusd", "\xE2\x88\xB8", 6, 3}, + {"minusdu", "\xE2\xA8\xAA", 7, 3}, + {"mlcp", "\xE2\xAB\x9B", 4, 3}, + {"mldr", "\xE2\x80\xA6", 4, 3}, + {"mnplus", "\xE2\x88\x93", 6, 3}, + {"models", "\xE2\x8A\xA7", 6, 3}, + {"mopf", "\xF0\x9D\x95\x9E", 4, 4}, + {"mp", "\xE2\x88\x93", 2, 3}, + {"mscr", "\xF0\x9D\x93\x82", 4, 4}, + {"mstpos", "\xE2\x88\xBE", 6, 3}, + {"mu", "\xCE\xBC", 2, 2}, + {"multimap", "\xE2\x8A\xB8", 8, 3}, + {"mumap", "\xE2\x8A\xB8", 5, 3}, + {"nGg", "\xE2\x8B\x99\xCC\xB8", 3, 5}, + {"nGt", "\xE2\x89\xAB\xE2\x83\x92", 3, 6}, + {"nGtv", "\xE2\x89\xAB\xCC\xB8", 4, 5}, + {"nLeftarrow", "\xE2\x87\x8D", 10, 3}, + {"nLeftrightarrow", "\xE2\x87\x8E", 15, 3}, + {"nLl", "\xE2\x8B\x98\xCC\xB8", 3, 5}, + {"nLt", "\xE2\x89\xAA\xE2\x83\x92", 3, 6}, + {"nLtv", "\xE2\x89\xAA\xCC\xB8", 4, 5}, + {"nRightarrow", "\xE2\x87\x8F", 11, 3}, + {"nVDash", "\xE2\x8A\xAF", 6, 3}, + {"nVdash", "\xE2\x8A\xAE", 6, 3}, + {"nabla", "\xE2\x88\x87", 5, 3}, + {"nacute", "\xC5\x84", 6, 2}, + {"nang", "\xE2\x88\xA0\xE2\x83\x92", 4, 6}, + {"nap", "\xE2\x89\x89", 3, 3}, + {"napE", "\xE2\xA9\xB0\xCC\xB8", 4, 5}, + {"napid", "\xE2\x89\x8B\xCC\xB8", 5, 5}, + {"napos", "\xC5\x89", 5, 2}, + {"napprox", "\xE2\x89\x89", 7, 3}, + {"natur", "\xE2\x99\xAE", 5, 3}, + {"natural", "\xE2\x99\xAE", 7, 3}, + {"naturals", "\xE2\x84\x95", 8, 3}, + {"nbsp", "\xC2\xA0", 4, 2}, + {"nbump", "\xE2\x89\x8E\xCC\xB8", 5, 5}, + {"nbumpe", "\xE2\x89\x8F\xCC\xB8", 6, 5}, + {"ncap", "\xE2\xA9\x83", 4, 3}, + {"ncaron", "\xC5\x88", 6, 2}, + {"ncedil", "\xC5\x86", 6, 2}, + {"ncong", "\xE2\x89\x87", 5, 3}, + {"ncongdot", "\xE2\xA9\xAD\xCC\xB8", 8, 5}, + {"ncup", "\xE2\xA9\x82", 4, 3}, + {"ncy", "\xD0\xBD", 3, 2}, + {"ndash", "\xE2\x80\x93", 5, 3}, + {"ne", "\xE2\x89\xA0", 2, 3}, + {"neArr", "\xE2\x87\x97", 5, 3}, + {"nearhk", "\xE2\xA4\xA4", 6, 3}, + {"nearr", "\xE2\x86\x97", 5, 3}, + {"nearrow", "\xE2\x86\x97", 7, 3}, + {"nedot", "\xE2\x89\x90\xCC\xB8", 5, 5}, + {"nequiv", "\xE2\x89\xA2", 6, 3}, + {"nesear", "\xE2\xA4\xA8", 6, 3}, + {"nesim", "\xE2\x89\x82\xCC\xB8", 5, 5}, + {"nexist", "\xE2\x88\x84", 6, 3}, + {"nexists", "\xE2\x88\x84", 7, 3}, + {"nfr", "\xF0\x9D\x94\xAB", 3, 4}, + {"ngE", "\xE2\x89\xA7\xCC\xB8", 3, 5}, + {"nge", "\xE2\x89\xB1", 3, 3}, + {"ngeq", "\xE2\x89\xB1", 4, 3}, + {"ngeqq", "\xE2\x89\xA7\xCC\xB8", 5, 5}, + {"ngeqslant", "\xE2\xA9\xBE\xCC\xB8", 9, 5}, + {"nges", "\xE2\xA9\xBE\xCC\xB8", 4, 5}, + {"ngsim", "\xE2\x89\xB5", 5, 3}, + {"ngt", "\xE2\x89\xAF", 3, 3}, + {"ngtr", "\xE2\x89\xAF", 4, 3}, + {"nhArr", "\xE2\x87\x8E", 5, 3}, + {"nharr", "\xE2\x86\xAE", 5, 3}, + {"nhpar", "\xE2\xAB\xB2", 5, 3}, + {"ni", "\xE2\x88\x8B", 2, 3}, + {"nis", "\xE2\x8B\xBC", 3, 3}, + {"nisd", "\xE2\x8B\xBA", 4, 3}, + {"niv", "\xE2\x88\x8B", 3, 3}, + {"njcy", "\xD1\x9A", 4, 2}, + {"nlArr", "\xE2\x87\x8D", 5, 3}, + {"nlE", "\xE2\x89\xA6\xCC\xB8", 3, 5}, + {"nlarr", "\xE2\x86\x9A", 5, 3}, + {"nldr", "\xE2\x80\xA5", 4, 3}, + {"nle", "\xE2\x89\xB0", 3, 3}, + {"nleftarrow", "\xE2\x86\x9A", 10, 3}, + {"nleftrightarrow", "\xE2\x86\xAE", 15, 3}, + {"nleq", "\xE2\x89\xB0", 4, 3}, + {"nleqq", "\xE2\x89\xA6\xCC\xB8", 5, 5}, + {"nleqslant", "\xE2\xA9\xBD\xCC\xB8", 9, 5}, + {"nles", "\xE2\xA9\xBD\xCC\xB8", 4, 5}, + {"nless", "\xE2\x89\xAE", 5, 3}, + {"nlsim", "\xE2\x89\xB4", 5, 3}, + {"nlt", "\xE2\x89\xAE", 3, 3}, + {"nltri", "\xE2\x8B\xAA", 5, 3}, + {"nltrie", "\xE2\x8B\xAC", 6, 3}, + {"nmid", "\xE2\x88\xA4", 4, 3}, + {"nopf", "\xF0\x9D\x95\x9F", 4, 4}, + {"not", "\xC2\xAC", 3, 2}, + {"notin", "\xE2\x88\x89", 5, 3}, + {"notinE", "\xE2\x8B\xB9\xCC\xB8", 6, 5}, + {"notindot", "\xE2\x8B\xB5\xCC\xB8", 8, 5}, + {"notinva", "\xE2\x88\x89", 7, 3}, + {"notinvb", "\xE2\x8B\xB7", 7, 3}, + {"notinvc", "\xE2\x8B\xB6", 7, 3}, + {"notni", "\xE2\x88\x8C", 5, 3}, + {"notniva", "\xE2\x88\x8C", 7, 3}, + {"notnivb", "\xE2\x8B\xBE", 7, 3}, + {"notnivc", "\xE2\x8B\xBD", 7, 3}, + {"npar", "\xE2\x88\xA6", 4, 3}, + {"nparallel", "\xE2\x88\xA6", 9, 3}, + {"nparsl", "\xE2\xAB\xBD\xE2\x83\xA5", 6, 6}, + {"npart", "\xE2\x88\x82\xCC\xB8", 5, 5}, + {"npolint", "\xE2\xA8\x94", 7, 3}, + {"npr", "\xE2\x8A\x80", 3, 3}, + {"nprcue", "\xE2\x8B\xA0", 6, 3}, + {"npre", "\xE2\xAA\xAF\xCC\xB8", 4, 5}, + {"nprec", "\xE2\x8A\x80", 5, 3}, + {"npreceq", "\xE2\xAA\xAF\xCC\xB8", 7, 5}, + {"nrArr", "\xE2\x87\x8F", 5, 3}, + {"nrarr", "\xE2\x86\x9B", 5, 3}, + {"nrarrc", "\xE2\xA4\xB3\xCC\xB8", 6, 5}, + {"nrarrw", "\xE2\x86\x9D\xCC\xB8", 6, 5}, + {"nrightarrow", "\xE2\x86\x9B", 11, 3}, + {"nrtri", "\xE2\x8B\xAB", 5, 3}, + {"nrtrie", "\xE2\x8B\xAD", 6, 3}, + {"nsc", "\xE2\x8A\x81", 3, 3}, + {"nsccue", "\xE2\x8B\xA1", 6, 3}, + {"nsce", "\xE2\xAA\xB0\xCC\xB8", 4, 5}, + {"nscr", "\xF0\x9D\x93\x83", 4, 4}, + {"nshortmid", "\xE2\x88\xA4", 9, 3}, + {"nshortparallel", "\xE2\x88\xA6", 14, 3}, + {"nsim", "\xE2\x89\x81", 4, 3}, + {"nsime", "\xE2\x89\x84", 5, 3}, + {"nsimeq", "\xE2\x89\x84", 6, 3}, + {"nsmid", "\xE2\x88\xA4", 5, 3}, + {"nspar", "\xE2\x88\xA6", 5, 3}, + {"nsqsube", "\xE2\x8B\xA2", 7, 3}, + {"nsqsupe", "\xE2\x8B\xA3", 7, 3}, + {"nsub", "\xE2\x8A\x84", 4, 3}, + {"nsubE", "\xE2\xAB\x85\xCC\xB8", 5, 5}, + {"nsube", "\xE2\x8A\x88", 5, 3}, + {"nsubset", "\xE2\x8A\x82\xE2\x83\x92", 7, 6}, + {"nsubseteq", "\xE2\x8A\x88", 9, 3}, + {"nsubseteqq", "\xE2\xAB\x85\xCC\xB8", 10, 5}, + {"nsucc", "\xE2\x8A\x81", 5, 3}, + {"nsucceq", "\xE2\xAA\xB0\xCC\xB8", 7, 5}, + {"nsup", "\xE2\x8A\x85", 4, 3}, + {"nsupE", "\xE2\xAB\x86\xCC\xB8", 5, 5}, + {"nsupe", "\xE2\x8A\x89", 5, 3}, + {"nsupset", "\xE2\x8A\x83\xE2\x83\x92", 7, 6}, + {"nsupseteq", "\xE2\x8A\x89", 9, 3}, + {"nsupseteqq", "\xE2\xAB\x86\xCC\xB8", 10, 5}, + {"ntgl", "\xE2\x89\xB9", 4, 3}, + {"ntilde", "\xC3\xB1", 6, 2}, + {"ntlg", "\xE2\x89\xB8", 4, 3}, + {"ntriangleleft", "\xE2\x8B\xAA", 13, 3}, + {"ntrianglelefteq", "\xE2\x8B\xAC", 15, 3}, + {"ntriangleright", "\xE2\x8B\xAB", 14, 3}, + {"ntrianglerighteq", "\xE2\x8B\xAD", 16, 3}, + {"nu", "\xCE\xBD", 2, 2}, + {"num", "\x23", 3, 1}, + {"numero", "\xE2\x84\x96", 6, 3}, + {"numsp", "\xE2\x80\x87", 5, 3}, + {"nvDash", "\xE2\x8A\xAD", 6, 3}, + {"nvHarr", "\xE2\xA4\x84", 6, 3}, + {"nvap", "\xE2\x89\x8D\xE2\x83\x92", 4, 6}, + {"nvdash", "\xE2\x8A\xAC", 6, 3}, + {"nvge", "\xE2\x89\xA5\xE2\x83\x92", 4, 6}, + {"nvgt", "\x3E\xE2\x83\x92", 4, 4}, + {"nvinfin", "\xE2\xA7\x9E", 7, 3}, + {"nvlArr", "\xE2\xA4\x82", 6, 3}, + {"nvle", "\xE2\x89\xA4\xE2\x83\x92", 4, 6}, + {"nvlt", "\x3C\xE2\x83\x92", 4, 4}, + {"nvltrie", "\xE2\x8A\xB4\xE2\x83\x92", 7, 6}, + {"nvrArr", "\xE2\xA4\x83", 6, 3}, + {"nvrtrie", "\xE2\x8A\xB5\xE2\x83\x92", 7, 6}, + {"nvsim", "\xE2\x88\xBC\xE2\x83\x92", 5, 6}, + {"nwArr", "\xE2\x87\x96", 5, 3}, + {"nwarhk", "\xE2\xA4\xA3", 6, 3}, + {"nwarr", "\xE2\x86\x96", 5, 3}, + {"nwarrow", "\xE2\x86\x96", 7, 3}, + {"nwnear", "\xE2\xA4\xA7", 6, 3}, + {"oS", "\xE2\x93\x88", 2, 3}, + {"oacute", "\xC3\xB3", 6, 2}, + {"oast", "\xE2\x8A\x9B", 4, 3}, + {"ocir", "\xE2\x8A\x9A", 4, 3}, + {"ocirc", "\xC3\xB4", 5, 2}, + {"ocy", "\xD0\xBE", 3, 2}, + {"odash", "\xE2\x8A\x9D", 5, 3}, + {"odblac", "\xC5\x91", 6, 2}, + {"odiv", "\xE2\xA8\xB8", 4, 3}, + {"odot", "\xE2\x8A\x99", 4, 3}, + {"odsold", "\xE2\xA6\xBC", 6, 3}, + {"oelig", "\xC5\x93", 5, 2}, + {"ofcir", "\xE2\xA6\xBF", 5, 3}, + {"ofr", "\xF0\x9D\x94\xAC", 3, 4}, + {"ogon", "\xCB\x9B", 4, 2}, + {"ograve", "\xC3\xB2", 6, 2}, + {"ogt", "\xE2\xA7\x81", 3, 3}, + {"ohbar", "\xE2\xA6\xB5", 5, 3}, + {"ohm", "\xCE\xA9", 3, 2}, + {"oint", "\xE2\x88\xAE", 4, 3}, + {"olarr", "\xE2\x86\xBA", 5, 3}, + {"olcir", "\xE2\xA6\xBE", 5, 3}, + {"olcross", "\xE2\xA6\xBB", 7, 3}, + {"oline", "\xE2\x80\xBE", 5, 3}, + {"olt", "\xE2\xA7\x80", 3, 3}, + {"omacr", "\xC5\x8D", 5, 2}, + {"omega", "\xCF\x89", 5, 2}, + {"omicron", "\xCE\xBF", 7, 2}, + {"omid", "\xE2\xA6\xB6", 4, 3}, + {"ominus", "\xE2\x8A\x96", 6, 3}, + {"oopf", "\xF0\x9D\x95\xA0", 4, 4}, + {"opar", "\xE2\xA6\xB7", 4, 3}, + {"operp", "\xE2\xA6\xB9", 5, 3}, + {"oplus", "\xE2\x8A\x95", 5, 3}, + {"or", "\xE2\x88\xA8", 2, 3}, + {"orarr", "\xE2\x86\xBB", 5, 3}, + {"ord", "\xE2\xA9\x9D", 3, 3}, + {"order", "\xE2\x84\xB4", 5, 3}, + {"orderof", "\xE2\x84\xB4", 7, 3}, + {"ordf", "\xC2\xAA", 4, 2}, + {"ordm", "\xC2\xBA", 4, 2}, + {"origof", "\xE2\x8A\xB6", 6, 3}, + {"oror", "\xE2\xA9\x96", 4, 3}, + {"orslope", "\xE2\xA9\x97", 7, 3}, + {"orv", "\xE2\xA9\x9B", 3, 3}, + {"oscr", "\xE2\x84\xB4", 4, 3}, + {"oslash", "\xC3\xB8", 6, 2}, + {"osol", "\xE2\x8A\x98", 4, 3}, + {"otilde", "\xC3\xB5", 6, 2}, + {"otimes", "\xE2\x8A\x97", 6, 3}, + {"otimesas", "\xE2\xA8\xB6", 8, 3}, + {"ouml", "\xC3\xB6", 4, 2}, + {"ovbar", "\xE2\x8C\xBD", 5, 3}, + {"par", "\xE2\x88\xA5", 3, 3}, + {"para", "\xC2\xB6", 4, 2}, + {"parallel", "\xE2\x88\xA5", 8, 3}, + {"parsim", "\xE2\xAB\xB3", 6, 3}, + {"parsl", "\xE2\xAB\xBD", 5, 3}, + {"part", "\xE2\x88\x82", 4, 3}, + {"pcy", "\xD0\xBF", 3, 2}, + {"percnt", "\x25", 6, 1}, + {"period", "\x2E", 6, 1}, + {"permil", "\xE2\x80\xB0", 6, 3}, + {"perp", "\xE2\x8A\xA5", 4, 3}, + {"pertenk", "\xE2\x80\xB1", 7, 3}, + {"pfr", "\xF0\x9D\x94\xAD", 3, 4}, + {"phi", "\xCF\x86", 3, 2}, + {"phiv", "\xCF\x95", 4, 2}, + {"phmmat", "\xE2\x84\xB3", 6, 3}, + {"phone", "\xE2\x98\x8E", 5, 3}, + {"pi", "\xCF\x80", 2, 2}, + {"pitchfork", "\xE2\x8B\x94", 9, 3}, + {"piv", "\xCF\x96", 3, 2}, + {"planck", "\xE2\x84\x8F", 6, 3}, + {"planckh", "\xE2\x84\x8E", 7, 3}, + {"plankv", "\xE2\x84\x8F", 6, 3}, + {"plus", "\x2B", 4, 1}, + {"plusacir", "\xE2\xA8\xA3", 8, 3}, + {"plusb", "\xE2\x8A\x9E", 5, 3}, + {"pluscir", "\xE2\xA8\xA2", 7, 3}, + {"plusdo", "\xE2\x88\x94", 6, 3}, + {"plusdu", "\xE2\xA8\xA5", 6, 3}, + {"pluse", "\xE2\xA9\xB2", 5, 3}, + {"plusmn", "\xC2\xB1", 6, 2}, + {"plussim", "\xE2\xA8\xA6", 7, 3}, + {"plustwo", "\xE2\xA8\xA7", 7, 3}, + {"pm", "\xC2\xB1", 2, 2}, + {"pointint", "\xE2\xA8\x95", 8, 3}, + {"popf", "\xF0\x9D\x95\xA1", 4, 4}, + {"pound", "\xC2\xA3", 5, 2}, + {"pr", "\xE2\x89\xBA", 2, 3}, + {"prE", "\xE2\xAA\xB3", 3, 3}, + {"prap", "\xE2\xAA\xB7", 4, 3}, + {"prcue", "\xE2\x89\xBC", 5, 3}, + {"pre", "\xE2\xAA\xAF", 3, 3}, + {"prec", "\xE2\x89\xBA", 4, 3}, + {"precapprox", "\xE2\xAA\xB7", 10, 3}, + {"preccurlyeq", "\xE2\x89\xBC", 11, 3}, + {"preceq", "\xE2\xAA\xAF", 6, 3}, + {"precnapprox", "\xE2\xAA\xB9", 11, 3}, + {"precneqq", "\xE2\xAA\xB5", 8, 3}, + {"precnsim", "\xE2\x8B\xA8", 8, 3}, + {"precsim", "\xE2\x89\xBE", 7, 3}, + {"prime", "\xE2\x80\xB2", 5, 3}, + {"primes", "\xE2\x84\x99", 6, 3}, + {"prnE", "\xE2\xAA\xB5", 4, 3}, + {"prnap", "\xE2\xAA\xB9", 5, 3}, + {"prnsim", "\xE2\x8B\xA8", 6, 3}, + {"prod", "\xE2\x88\x8F", 4, 3}, + {"profalar", "\xE2\x8C\xAE", 8, 3}, + {"profline", "\xE2\x8C\x92", 8, 3}, + {"profsurf", "\xE2\x8C\x93", 8, 3}, + {"prop", "\xE2\x88\x9D", 4, 3}, + {"propto", "\xE2\x88\x9D", 6, 3}, + {"prsim", "\xE2\x89\xBE", 5, 3}, + {"prurel", "\xE2\x8A\xB0", 6, 3}, + {"pscr", "\xF0\x9D\x93\x85", 4, 4}, + {"psi", "\xCF\x88", 3, 2}, + {"puncsp", "\xE2\x80\x88", 6, 3}, + {"qfr", "\xF0\x9D\x94\xAE", 3, 4}, + {"qint", "\xE2\xA8\x8C", 4, 3}, + {"qopf", "\xF0\x9D\x95\xA2", 4, 4}, + {"qprime", "\xE2\x81\x97", 6, 3}, + {"qscr", "\xF0\x9D\x93\x86", 4, 4}, + {"quaternions", "\xE2\x84\x8D", 11, 3}, + {"quatint", "\xE2\xA8\x96", 7, 3}, + {"quest", "\x3F", 5, 1}, + {"questeq", "\xE2\x89\x9F", 7, 3}, + {"quot", "\x22", 4, 1}, + {"rAarr", "\xE2\x87\x9B", 5, 3}, + {"rArr", "\xE2\x87\x92", 4, 3}, + {"rAtail", "\xE2\xA4\x9C", 6, 3}, + {"rBarr", "\xE2\xA4\x8F", 5, 3}, + {"rHar", "\xE2\xA5\xA4", 4, 3}, + {"race", "\xE2\x88\xBD\xCC\xB1", 4, 5}, + {"racute", "\xC5\x95", 6, 2}, + {"radic", "\xE2\x88\x9A", 5, 3}, + {"raemptyv", "\xE2\xA6\xB3", 8, 3}, + {"rang", "\xE2\x9F\xA9", 4, 3}, + {"rangd", "\xE2\xA6\x92", 5, 3}, + {"range", "\xE2\xA6\xA5", 5, 3}, + {"rangle", "\xE2\x9F\xA9", 6, 3}, + {"raquo", "\xC2\xBB", 5, 2}, + {"rarr", "\xE2\x86\x92", 4, 3}, + {"rarrap", "\xE2\xA5\xB5", 6, 3}, + {"rarrb", "\xE2\x87\xA5", 5, 3}, + {"rarrbfs", "\xE2\xA4\xA0", 7, 3}, + {"rarrc", "\xE2\xA4\xB3", 5, 3}, + {"rarrfs", "\xE2\xA4\x9E", 6, 3}, + {"rarrhk", "\xE2\x86\xAA", 6, 3}, + {"rarrlp", "\xE2\x86\xAC", 6, 3}, + {"rarrpl", "\xE2\xA5\x85", 6, 3}, + {"rarrsim", "\xE2\xA5\xB4", 7, 3}, + {"rarrtl", "\xE2\x86\xA3", 6, 3}, + {"rarrw", "\xE2\x86\x9D", 5, 3}, + {"ratail", "\xE2\xA4\x9A", 6, 3}, + {"ratio", "\xE2\x88\xB6", 5, 3}, + {"rationals", "\xE2\x84\x9A", 9, 3}, + {"rbarr", "\xE2\xA4\x8D", 5, 3}, + {"rbbrk", "\xE2\x9D\xB3", 5, 3}, + {"rbrace", "\x7D", 6, 1}, + {"rbrack", "\x5D", 6, 1}, + {"rbrke", "\xE2\xA6\x8C", 5, 3}, + {"rbrksld", "\xE2\xA6\x8E", 7, 3}, + {"rbrkslu", "\xE2\xA6\x90", 7, 3}, + {"rcaron", "\xC5\x99", 6, 2}, + {"rcedil", "\xC5\x97", 6, 2}, + {"rceil", "\xE2\x8C\x89", 5, 3}, + {"rcub", "\x7D", 4, 1}, + {"rcy", "\xD1\x80", 3, 2}, + {"rdca", "\xE2\xA4\xB7", 4, 3}, + {"rdldhar", "\xE2\xA5\xA9", 7, 3}, + {"rdquo", "\xE2\x80\x9D", 5, 3}, + {"rdquor", "\xE2\x80\x9D", 6, 3}, + {"rdsh", "\xE2\x86\xB3", 4, 3}, + {"real", "\xE2\x84\x9C", 4, 3}, + {"realine", "\xE2\x84\x9B", 7, 3}, + {"realpart", "\xE2\x84\x9C", 8, 3}, + {"reals", "\xE2\x84\x9D", 5, 3}, + {"rect", "\xE2\x96\xAD", 4, 3}, + {"reg", "\xC2\xAE", 3, 2}, + {"rfisht", "\xE2\xA5\xBD", 6, 3}, + {"rfloor", "\xE2\x8C\x8B", 6, 3}, + {"rfr", "\xF0\x9D\x94\xAF", 3, 4}, + {"rhard", "\xE2\x87\x81", 5, 3}, + {"rharu", "\xE2\x87\x80", 5, 3}, + {"rharul", "\xE2\xA5\xAC", 6, 3}, + {"rho", "\xCF\x81", 3, 2}, + {"rhov", "\xCF\xB1", 4, 2}, + {"rightarrow", "\xE2\x86\x92", 10, 3}, + {"rightarrowtail", "\xE2\x86\xA3", 14, 3}, + {"rightharpoondown", "\xE2\x87\x81", 16, 3}, + {"rightharpoonup", "\xE2\x87\x80", 14, 3}, + {"rightleftarrows", "\xE2\x87\x84", 15, 3}, + {"rightleftharpoons", "\xE2\x87\x8C", 17, 3}, + {"rightrightarrows", "\xE2\x87\x89", 16, 3}, + {"rightsquigarrow", "\xE2\x86\x9D", 15, 3}, + {"rightthreetimes", "\xE2\x8B\x8C", 15, 3}, + {"ring", "\xCB\x9A", 4, 2}, + {"risingdotseq", "\xE2\x89\x93", 12, 3}, + {"rlarr", "\xE2\x87\x84", 5, 3}, + {"rlhar", "\xE2\x87\x8C", 5, 3}, + {"rlm", "\xE2\x80\x8F", 3, 3}, + {"rmoust", "\xE2\x8E\xB1", 6, 3}, + {"rmoustache", "\xE2\x8E\xB1", 10, 3}, + {"rnmid", "\xE2\xAB\xAE", 5, 3}, + {"roang", "\xE2\x9F\xAD", 5, 3}, + {"roarr", "\xE2\x87\xBE", 5, 3}, + {"robrk", "\xE2\x9F\xA7", 5, 3}, + {"ropar", "\xE2\xA6\x86", 5, 3}, + {"ropf", "\xF0\x9D\x95\xA3", 4, 4}, + {"roplus", "\xE2\xA8\xAE", 6, 3}, + {"rotimes", "\xE2\xA8\xB5", 7, 3}, + {"rpar", "\x29", 4, 1}, + {"rpargt", "\xE2\xA6\x94", 6, 3}, + {"rppolint", "\xE2\xA8\x92", 8, 3}, + {"rrarr", "\xE2\x87\x89", 5, 3}, + {"rsaquo", "\xE2\x80\xBA", 6, 3}, + {"rscr", "\xF0\x9D\x93\x87", 4, 4}, + {"rsh", "\xE2\x86\xB1", 3, 3}, + {"rsqb", "\x5D", 4, 1}, + {"rsquo", "\xE2\x80\x99", 5, 3}, + {"rsquor", "\xE2\x80\x99", 6, 3}, + {"rthree", "\xE2\x8B\x8C", 6, 3}, + {"rtimes", "\xE2\x8B\x8A", 6, 3}, + {"rtri", "\xE2\x96\xB9", 4, 3}, + {"rtrie", "\xE2\x8A\xB5", 5, 3}, + {"rtrif", "\xE2\x96\xB8", 5, 3}, + {"rtriltri", "\xE2\xA7\x8E", 8, 3}, + {"ruluhar", "\xE2\xA5\xA8", 7, 3}, + {"rx", "\xE2\x84\x9E", 2, 3}, + {"sacute", "\xC5\x9B", 6, 2}, + {"sbquo", "\xE2\x80\x9A", 5, 3}, + {"sc", "\xE2\x89\xBB", 2, 3}, + {"scE", "\xE2\xAA\xB4", 3, 3}, + {"scap", "\xE2\xAA\xB8", 4, 3}, + {"scaron", "\xC5\xA1", 6, 2}, + {"sccue", "\xE2\x89\xBD", 5, 3}, + {"sce", "\xE2\xAA\xB0", 3, 3}, + {"scedil", "\xC5\x9F", 6, 2}, + {"scirc", "\xC5\x9D", 5, 2}, + {"scnE", "\xE2\xAA\xB6", 4, 3}, + {"scnap", "\xE2\xAA\xBA", 5, 3}, + {"scnsim", "\xE2\x8B\xA9", 6, 3}, + {"scpolint", "\xE2\xA8\x93", 8, 3}, + {"scsim", "\xE2\x89\xBF", 5, 3}, + {"scy", "\xD1\x81", 3, 2}, + {"sdot", "\xE2\x8B\x85", 4, 3}, + {"sdotb", "\xE2\x8A\xA1", 5, 3}, + {"sdote", "\xE2\xA9\xA6", 5, 3}, + {"seArr", "\xE2\x87\x98", 5, 3}, + {"searhk", "\xE2\xA4\xA5", 6, 3}, + {"searr", "\xE2\x86\x98", 5, 3}, + {"searrow", "\xE2\x86\x98", 7, 3}, + {"sect", "\xC2\xA7", 4, 2}, + {"semi", "\x3B", 4, 1}, + {"seswar", "\xE2\xA4\xA9", 6, 3}, + {"setminus", "\xE2\x88\x96", 8, 3}, + {"setmn", "\xE2\x88\x96", 5, 3}, + {"sext", "\xE2\x9C\xB6", 4, 3}, + {"sfr", "\xF0\x9D\x94\xB0", 3, 4}, + {"sfrown", "\xE2\x8C\xA2", 6, 3}, + {"sharp", "\xE2\x99\xAF", 5, 3}, + {"shchcy", "\xD1\x89", 6, 2}, + {"shcy", "\xD1\x88", 4, 2}, + {"shortmid", "\xE2\x88\xA3", 8, 3}, + {"shortparallel", "\xE2\x88\xA5", 13, 3}, + {"shy", "\xC2\xAD", 3, 2}, + {"sigma", "\xCF\x83", 5, 2}, + {"sigmaf", "\xCF\x82", 6, 2}, + {"sigmav", "\xCF\x82", 6, 2}, + {"sim", "\xE2\x88\xBC", 3, 3}, + {"simdot", "\xE2\xA9\xAA", 6, 3}, + {"sime", "\xE2\x89\x83", 4, 3}, + {"simeq", "\xE2\x89\x83", 5, 3}, + {"simg", "\xE2\xAA\x9E", 4, 3}, + {"simgE", "\xE2\xAA\xA0", 5, 3}, + {"siml", "\xE2\xAA\x9D", 4, 3}, + {"simlE", "\xE2\xAA\x9F", 5, 3}, + {"simne", "\xE2\x89\x86", 5, 3}, + {"simplus", "\xE2\xA8\xA4", 7, 3}, + {"simrarr", "\xE2\xA5\xB2", 7, 3}, + {"slarr", "\xE2\x86\x90", 5, 3}, + {"smallsetminus", "\xE2\x88\x96", 13, 3}, + {"smashp", "\xE2\xA8\xB3", 6, 3}, + {"smeparsl", "\xE2\xA7\xA4", 8, 3}, + {"smid", "\xE2\x88\xA3", 4, 3}, + {"smile", "\xE2\x8C\xA3", 5, 3}, + {"smt", "\xE2\xAA\xAA", 3, 3}, + {"smte", "\xE2\xAA\xAC", 4, 3}, + {"smtes", "\xE2\xAA\xAC\xEF\xB8\x80", 5, 6}, + {"softcy", "\xD1\x8C", 6, 2}, + {"sol", "\x2F", 3, 1}, + {"solb", "\xE2\xA7\x84", 4, 3}, + {"solbar", "\xE2\x8C\xBF", 6, 3}, + {"sopf", "\xF0\x9D\x95\xA4", 4, 4}, + {"spades", "\xE2\x99\xA0", 6, 3}, + {"spadesuit", "\xE2\x99\xA0", 9, 3}, + {"spar", "\xE2\x88\xA5", 4, 3}, + {"sqcap", "\xE2\x8A\x93", 5, 3}, + {"sqcaps", "\xE2\x8A\x93\xEF\xB8\x80", 6, 6}, + {"sqcup", "\xE2\x8A\x94", 5, 3}, + {"sqcups", "\xE2\x8A\x94\xEF\xB8\x80", 6, 6}, + {"sqsub", "\xE2\x8A\x8F", 5, 3}, + {"sqsube", "\xE2\x8A\x91", 6, 3}, + {"sqsubset", "\xE2\x8A\x8F", 8, 3}, + {"sqsubseteq", "\xE2\x8A\x91", 10, 3}, + {"sqsup", "\xE2\x8A\x90", 5, 3}, + {"sqsupe", "\xE2\x8A\x92", 6, 3}, + {"sqsupset", "\xE2\x8A\x90", 8, 3}, + {"sqsupseteq", "\xE2\x8A\x92", 10, 3}, + {"squ", "\xE2\x96\xA1", 3, 3}, + {"square", "\xE2\x96\xA1", 6, 3}, + {"squarf", "\xE2\x96\xAA", 6, 3}, + {"squf", "\xE2\x96\xAA", 4, 3}, + {"srarr", "\xE2\x86\x92", 5, 3}, + {"sscr", "\xF0\x9D\x93\x88", 4, 4}, + {"ssetmn", "\xE2\x88\x96", 6, 3}, + {"ssmile", "\xE2\x8C\xA3", 6, 3}, + {"sstarf", "\xE2\x8B\x86", 6, 3}, + {"star", "\xE2\x98\x86", 4, 3}, + {"starf", "\xE2\x98\x85", 5, 3}, + {"straightepsilon", "\xCF\xB5", 15, 2}, + {"straightphi", "\xCF\x95", 11, 2}, + {"strns", "\xC2\xAF", 5, 2}, + {"sub", "\xE2\x8A\x82", 3, 3}, + {"subE", "\xE2\xAB\x85", 4, 3}, + {"subdot", "\xE2\xAA\xBD", 6, 3}, + {"sube", "\xE2\x8A\x86", 4, 3}, + {"subedot", "\xE2\xAB\x83", 7, 3}, + {"submult", "\xE2\xAB\x81", 7, 3}, + {"subnE", "\xE2\xAB\x8B", 5, 3}, + {"subne", "\xE2\x8A\x8A", 5, 3}, + {"subplus", "\xE2\xAA\xBF", 7, 3}, + {"subrarr", "\xE2\xA5\xB9", 7, 3}, + {"subset", "\xE2\x8A\x82", 6, 3}, + {"subseteq", "\xE2\x8A\x86", 8, 3}, + {"subseteqq", "\xE2\xAB\x85", 9, 3}, + {"subsetneq", "\xE2\x8A\x8A", 9, 3}, + {"subsetneqq", "\xE2\xAB\x8B", 10, 3}, + {"subsim", "\xE2\xAB\x87", 6, 3}, + {"subsub", "\xE2\xAB\x95", 6, 3}, + {"subsup", "\xE2\xAB\x93", 6, 3}, + {"succ", "\xE2\x89\xBB", 4, 3}, + {"succapprox", "\xE2\xAA\xB8", 10, 3}, + {"succcurlyeq", "\xE2\x89\xBD", 11, 3}, + {"succeq", "\xE2\xAA\xB0", 6, 3}, + {"succnapprox", "\xE2\xAA\xBA", 11, 3}, + {"succneqq", "\xE2\xAA\xB6", 8, 3}, + {"succnsim", "\xE2\x8B\xA9", 8, 3}, + {"succsim", "\xE2\x89\xBF", 7, 3}, + {"sum", "\xE2\x88\x91", 3, 3}, + {"sung", "\xE2\x99\xAA", 4, 3}, + {"sup", "\xE2\x8A\x83", 3, 3}, + {"sup1", "\xC2\xB9", 4, 2}, + {"sup2", "\xC2\xB2", 4, 2}, + {"sup3", "\xC2\xB3", 4, 2}, + {"supE", "\xE2\xAB\x86", 4, 3}, + {"supdot", "\xE2\xAA\xBE", 6, 3}, + {"supdsub", "\xE2\xAB\x98", 7, 3}, + {"supe", "\xE2\x8A\x87", 4, 3}, + {"supedot", "\xE2\xAB\x84", 7, 3}, + {"suphsol", "\xE2\x9F\x89", 7, 3}, + {"suphsub", "\xE2\xAB\x97", 7, 3}, + {"suplarr", "\xE2\xA5\xBB", 7, 3}, + {"supmult", "\xE2\xAB\x82", 7, 3}, + {"supnE", "\xE2\xAB\x8C", 5, 3}, + {"supne", "\xE2\x8A\x8B", 5, 3}, + {"supplus", "\xE2\xAB\x80", 7, 3}, + {"supset", "\xE2\x8A\x83", 6, 3}, + {"supseteq", "\xE2\x8A\x87", 8, 3}, + {"supseteqq", "\xE2\xAB\x86", 9, 3}, + {"supsetneq", "\xE2\x8A\x8B", 9, 3}, + {"supsetneqq", "\xE2\xAB\x8C", 10, 3}, + {"supsim", "\xE2\xAB\x88", 6, 3}, + {"supsub", "\xE2\xAB\x94", 6, 3}, + {"supsup", "\xE2\xAB\x96", 6, 3}, + {"swArr", "\xE2\x87\x99", 5, 3}, + {"swarhk", "\xE2\xA4\xA6", 6, 3}, + {"swarr", "\xE2\x86\x99", 5, 3}, + {"swarrow", "\xE2\x86\x99", 7, 3}, + {"swnwar", "\xE2\xA4\xAA", 6, 3}, + {"szlig", "\xC3\x9F", 5, 2}, + {"target", "\xE2\x8C\x96", 6, 3}, + {"tau", "\xCF\x84", 3, 2}, + {"tbrk", "\xE2\x8E\xB4", 4, 3}, + {"tcaron", "\xC5\xA5", 6, 2}, + {"tcedil", "\xC5\xA3", 6, 2}, + {"tcy", "\xD1\x82", 3, 2}, + {"tdot", "\xE2\x83\x9B", 4, 3}, + {"telrec", "\xE2\x8C\x95", 6, 3}, + {"tfr", "\xF0\x9D\x94\xB1", 3, 4}, + {"there4", "\xE2\x88\xB4", 6, 3}, + {"therefore", "\xE2\x88\xB4", 9, 3}, + {"theta", "\xCE\xB8", 5, 2}, + {"thetasym", "\xCF\x91", 8, 2}, + {"thetav", "\xCF\x91", 6, 2}, + {"thickapprox", "\xE2\x89\x88", 11, 3}, + {"thicksim", "\xE2\x88\xBC", 8, 3}, + {"thinsp", "\xE2\x80\x89", 6, 3}, + {"thkap", "\xE2\x89\x88", 5, 3}, + {"thksim", "\xE2\x88\xBC", 6, 3}, + {"thorn", "\xC3\xBE", 5, 2}, + {"tilde", "\xCB\x9C", 5, 2}, + {"times", "\xC3\x97", 5, 2}, + {"timesb", "\xE2\x8A\xA0", 6, 3}, + {"timesbar", "\xE2\xA8\xB1", 8, 3}, + {"timesd", "\xE2\xA8\xB0", 6, 3}, + {"tint", "\xE2\x88\xAD", 4, 3}, + {"toea", "\xE2\xA4\xA8", 4, 3}, + {"top", "\xE2\x8A\xA4", 3, 3}, + {"topbot", "\xE2\x8C\xB6", 6, 3}, + {"topcir", "\xE2\xAB\xB1", 6, 3}, + {"topf", "\xF0\x9D\x95\xA5", 4, 4}, + {"topfork", "\xE2\xAB\x9A", 7, 3}, + {"tosa", "\xE2\xA4\xA9", 4, 3}, + {"tprime", "\xE2\x80\xB4", 6, 3}, + {"trade", "\xE2\x84\xA2", 5, 3}, + {"triangle", "\xE2\x96\xB5", 8, 3}, + {"triangledown", "\xE2\x96\xBF", 12, 3}, + {"triangleleft", "\xE2\x97\x83", 12, 3}, + {"trianglelefteq", "\xE2\x8A\xB4", 14, 3}, + {"triangleq", "\xE2\x89\x9C", 9, 3}, + {"triangleright", "\xE2\x96\xB9", 13, 3}, + {"trianglerighteq", "\xE2\x8A\xB5", 15, 3}, + {"tridot", "\xE2\x97\xAC", 6, 3}, + {"trie", "\xE2\x89\x9C", 4, 3}, + {"triminus", "\xE2\xA8\xBA", 8, 3}, + {"triplus", "\xE2\xA8\xB9", 7, 3}, + {"trisb", "\xE2\xA7\x8D", 5, 3}, + {"tritime", "\xE2\xA8\xBB", 7, 3}, + {"trpezium", "\xE2\x8F\xA2", 8, 3}, + {"tscr", "\xF0\x9D\x93\x89", 4, 4}, + {"tscy", "\xD1\x86", 4, 2}, + {"tshcy", "\xD1\x9B", 5, 2}, + {"tstrok", "\xC5\xA7", 6, 2}, + {"twixt", "\xE2\x89\xAC", 5, 3}, + {"twoheadleftarrow", "\xE2\x86\x9E", 16, 3}, + {"twoheadrightarrow", "\xE2\x86\xA0", 17, 3}, + {"uArr", "\xE2\x87\x91", 4, 3}, + {"uHar", "\xE2\xA5\xA3", 4, 3}, + {"uacute", "\xC3\xBA", 6, 2}, + {"uarr", "\xE2\x86\x91", 4, 3}, + {"ubrcy", "\xD1\x9E", 5, 2}, + {"ubreve", "\xC5\xAD", 6, 2}, + {"ucirc", "\xC3\xBB", 5, 2}, + {"ucy", "\xD1\x83", 3, 2}, + {"udarr", "\xE2\x87\x85", 5, 3}, + {"udblac", "\xC5\xB1", 6, 2}, + {"udhar", "\xE2\xA5\xAE", 5, 3}, + {"ufisht", "\xE2\xA5\xBE", 6, 3}, + {"ufr", "\xF0\x9D\x94\xB2", 3, 4}, + {"ugrave", "\xC3\xB9", 6, 2}, + {"uharl", "\xE2\x86\xBF", 5, 3}, + {"uharr", "\xE2\x86\xBE", 5, 3}, + {"uhblk", "\xE2\x96\x80", 5, 3}, + {"ulcorn", "\xE2\x8C\x9C", 6, 3}, + {"ulcorner", "\xE2\x8C\x9C", 8, 3}, + {"ulcrop", "\xE2\x8C\x8F", 6, 3}, + {"ultri", "\xE2\x97\xB8", 5, 3}, + {"umacr", "\xC5\xAB", 5, 2}, + {"uml", "\xC2\xA8", 3, 2}, + {"uogon", "\xC5\xB3", 5, 2}, + {"uopf", "\xF0\x9D\x95\xA6", 4, 4}, + {"uparrow", "\xE2\x86\x91", 7, 3}, + {"updownarrow", "\xE2\x86\x95", 11, 3}, + {"upharpoonleft", "\xE2\x86\xBF", 13, 3}, + {"upharpoonright", "\xE2\x86\xBE", 14, 3}, + {"uplus", "\xE2\x8A\x8E", 5, 3}, + {"upsi", "\xCF\x85", 4, 2}, + {"upsih", "\xCF\x92", 5, 2}, + {"upsilon", "\xCF\x85", 7, 2}, + {"upuparrows", "\xE2\x87\x88", 10, 3}, + {"urcorn", "\xE2\x8C\x9D", 6, 3}, + {"urcorner", "\xE2\x8C\x9D", 8, 3}, + {"urcrop", "\xE2\x8C\x8E", 6, 3}, + {"uring", "\xC5\xAF", 5, 2}, + {"urtri", "\xE2\x97\xB9", 5, 3}, + {"uscr", "\xF0\x9D\x93\x8A", 4, 4}, + {"utdot", "\xE2\x8B\xB0", 5, 3}, + {"utilde", "\xC5\xA9", 6, 2}, + {"utri", "\xE2\x96\xB5", 4, 3}, + {"utrif", "\xE2\x96\xB4", 5, 3}, + {"uuarr", "\xE2\x87\x88", 5, 3}, + {"uuml", "\xC3\xBC", 4, 2}, + {"uwangle", "\xE2\xA6\xA7", 7, 3}, + {"vArr", "\xE2\x87\x95", 4, 3}, + {"vBar", "\xE2\xAB\xA8", 4, 3}, + {"vBarv", "\xE2\xAB\xA9", 5, 3}, + {"vDash", "\xE2\x8A\xA8", 5, 3}, + {"vangrt", "\xE2\xA6\x9C", 6, 3}, + {"varepsilon", "\xCF\xB5", 10, 2}, + {"varkappa", "\xCF\xB0", 8, 2}, + {"varnothing", "\xE2\x88\x85", 10, 3}, + {"varphi", "\xCF\x95", 6, 2}, + {"varpi", "\xCF\x96", 5, 2}, + {"varpropto", "\xE2\x88\x9D", 9, 3}, + {"varr", "\xE2\x86\x95", 4, 3}, + {"varrho", "\xCF\xB1", 6, 2}, + {"varsigma", "\xCF\x82", 8, 2}, + {"varsubsetneq", "\xE2\x8A\x8A\xEF\xB8\x80", 12, 6}, + {"varsubsetneqq", "\xE2\xAB\x8B\xEF\xB8\x80", 13, 6}, + {"varsupsetneq", "\xE2\x8A\x8B\xEF\xB8\x80", 12, 6}, + {"varsupsetneqq", "\xE2\xAB\x8C\xEF\xB8\x80", 13, 6}, + {"vartheta", "\xCF\x91", 8, 2}, + {"vartriangleleft", "\xE2\x8A\xB2", 15, 3}, + {"vartriangleright", "\xE2\x8A\xB3", 16, 3}, + {"vcy", "\xD0\xB2", 3, 2}, + {"vdash", "\xE2\x8A\xA2", 5, 3}, + {"vee", "\xE2\x88\xA8", 3, 3}, + {"veebar", "\xE2\x8A\xBB", 6, 3}, + {"veeeq", "\xE2\x89\x9A", 5, 3}, + {"vellip", "\xE2\x8B\xAE", 6, 3}, + {"verbar", "\x7C", 6, 1}, + {"vert", "\x7C", 4, 1}, + {"vfr", "\xF0\x9D\x94\xB3", 3, 4}, + {"vltri", "\xE2\x8A\xB2", 5, 3}, + {"vnsub", "\xE2\x8A\x82\xE2\x83\x92", 5, 6}, + {"vnsup", "\xE2\x8A\x83\xE2\x83\x92", 5, 6}, + {"vopf", "\xF0\x9D\x95\xA7", 4, 4}, + {"vprop", "\xE2\x88\x9D", 5, 3}, + {"vrtri", "\xE2\x8A\xB3", 5, 3}, + {"vscr", "\xF0\x9D\x93\x8B", 4, 4}, + {"vsubnE", "\xE2\xAB\x8B\xEF\xB8\x80", 6, 6}, + {"vsubne", "\xE2\x8A\x8A\xEF\xB8\x80", 6, 6}, + {"vsupnE", "\xE2\xAB\x8C\xEF\xB8\x80", 6, 6}, + {"vsupne", "\xE2\x8A\x8B\xEF\xB8\x80", 6, 6}, + {"vzigzag", "\xE2\xA6\x9A", 7, 3}, + {"wcirc", "\xC5\xB5", 5, 2}, + {"wedbar", "\xE2\xA9\x9F", 6, 3}, + {"wedge", "\xE2\x88\xA7", 5, 3}, + {"wedgeq", "\xE2\x89\x99", 6, 3}, + {"weierp", "\xE2\x84\x98", 6, 3}, + {"wfr", "\xF0\x9D\x94\xB4", 3, 4}, + {"wopf", "\xF0\x9D\x95\xA8", 4, 4}, + {"wp", "\xE2\x84\x98", 2, 3}, + {"wr", "\xE2\x89\x80", 2, 3}, + {"wreath", "\xE2\x89\x80", 6, 3}, + {"wscr", "\xF0\x9D\x93\x8C", 4, 4}, + {"xcap", "\xE2\x8B\x82", 4, 3}, + {"xcirc", "\xE2\x97\xAF", 5, 3}, + {"xcup", "\xE2\x8B\x83", 4, 3}, + {"xdtri", "\xE2\x96\xBD", 5, 3}, + {"xfr", "\xF0\x9D\x94\xB5", 3, 4}, + {"xhArr", "\xE2\x9F\xBA", 5, 3}, + {"xharr", "\xE2\x9F\xB7", 5, 3}, + {"xi", "\xCE\xBE", 2, 2}, + {"xlArr", "\xE2\x9F\xB8", 5, 3}, + {"xlarr", "\xE2\x9F\xB5", 5, 3}, + {"xmap", "\xE2\x9F\xBC", 4, 3}, + {"xnis", "\xE2\x8B\xBB", 4, 3}, + {"xodot", "\xE2\xA8\x80", 5, 3}, + {"xopf", "\xF0\x9D\x95\xA9", 4, 4}, + {"xoplus", "\xE2\xA8\x81", 6, 3}, + {"xotime", "\xE2\xA8\x82", 6, 3}, + {"xrArr", "\xE2\x9F\xB9", 5, 3}, + {"xrarr", "\xE2\x9F\xB6", 5, 3}, + {"xscr", "\xF0\x9D\x93\x8D", 4, 4}, + {"xsqcup", "\xE2\xA8\x86", 6, 3}, + {"xuplus", "\xE2\xA8\x84", 6, 3}, + {"xutri", "\xE2\x96\xB3", 5, 3}, + {"xvee", "\xE2\x8B\x81", 4, 3}, + {"xwedge", "\xE2\x8B\x80", 6, 3}, + {"yacute", "\xC3\xBD", 6, 2}, + {"yacy", "\xD1\x8F", 4, 2}, + {"ycirc", "\xC5\xB7", 5, 2}, + {"ycy", "\xD1\x8B", 3, 2}, + {"yen", "\xC2\xA5", 3, 2}, + {"yfr", "\xF0\x9D\x94\xB6", 3, 4}, + {"yicy", "\xD1\x97", 4, 2}, + {"yopf", "\xF0\x9D\x95\xAA", 4, 4}, + {"yscr", "\xF0\x9D\x93\x8E", 4, 4}, + {"yucy", "\xD1\x8E", 4, 2}, + {"yuml", "\xC3\xBF", 4, 2}, + {"zacute", "\xC5\xBA", 6, 2}, + {"zcaron", "\xC5\xBE", 6, 2}, + {"zcy", "\xD0\xB7", 3, 2}, + {"zdot", "\xC5\xBC", 4, 2}, + {"zeetrf", "\xE2\x84\xA8", 6, 3}, + {"zeta", "\xCE\xB6", 4, 2}, + {"zfr", "\xF0\x9D\x94\xB7", 3, 4}, + {"zhcy", "\xD0\xB6", 4, 2}, + {"zigrarr", "\xE2\x87\x9D", 7, 3}, + {"zopf", "\xF0\x9D\x95\xAB", 4, 4}, + {"zscr", "\xF0\x9D\x93\x8F", 4, 4}, + {"zwj", "\xE2\x80\x8D", 3, 3}, + {"zwnj", "\xE2\x80\x8C", 4, 3}, +}; + +#endif /* ZEND_MARKUP_ENTITIES_H */ diff --git a/Zend/zend_markup_entities_gen.php b/Zend/zend_markup_entities_gen.php new file mode 100644 index 000000000000..e67ec7e800d8 --- /dev/null +++ b/Zend/zend_markup_entities_gen.php @@ -0,0 +1,101 @@ + Zend/zend_markup_entities.h + * + * entities.json is the WHATWG table from https://html.spec.whatwg.org/entities.json. + * The HTML standard guarantees this list is frozen — no named entity will ever + * be added — so the generated header only changes if this generator does. + * Only the semicolon-terminated forms are included: markup requires the + * well-formed "&name;" spelling and treats legacy forms like "&" as + * literal text. + */ + +if (!isset($argv[1])) { + fwrite(STDERR, "usage: php zend_markup_entities_gen.php entities.json > zend_markup_entities.h\n"); + exit(1); +} + +$json = file_get_contents($argv[1]); +if ($json === false) { + fwrite(STDERR, "cannot read {$argv[1]}\n"); + exit(1); +} +$data = json_decode($json, true, 8, JSON_THROW_ON_ERROR); + +function utf8_bytes(int $cp): string { + if ($cp < 0x80) { + return chr($cp); + } elseif ($cp < 0x800) { + return chr(0xC0 | ($cp >> 6)) . chr(0x80 | ($cp & 0x3F)); + } elseif ($cp < 0x10000) { + return chr(0xE0 | ($cp >> 12)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F)); + } + return chr(0xF0 | ($cp >> 18)) . chr(0x80 | (($cp >> 12) & 0x3F)) + . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F)); +} + +$entities = []; +foreach ($data as $name => $info) { + if (!str_ends_with($name, ';')) { + continue; + } + $bare = substr($name, 1, -1); + if (!preg_match('/^[A-Za-z][A-Za-z0-9]*$/', $bare)) { + fwrite(STDERR, "unexpected entity name: $name\n"); + exit(1); + } + $utf8 = ''; + foreach ($info['codepoints'] as $cp) { + $utf8 .= utf8_bytes($cp); + } + $entities[$bare] = $utf8; +} + +/* Byte order, matching the C-side memcmp binary search. */ +ksort($entities, SORT_STRING); + +$maxName = max(array_map('strlen', array_keys($entities))); +$maxUtf8 = max(array_map('strlen', $entities)); +$count = count($entities); + +echo << $utf8) { + $hex = ''; + foreach (str_split($utf8) as $byte) { + $hex .= sprintf('\\x%02X', ord($byte)); + } + printf("\t{\"%s\", \"%s\", %d, %d},\n", $name, $hex, strlen($name), strlen($utf8)); +} + +echo <<

    +

    Hello, {$name ?: ucfirst($type)}!

    +

    Welcome to PHP, where markup is a first-class expression.

    + ; +} + +echo ; // prints the rendered HTML, dynamic values escaped +``` + +Despite appearances, this is not a new template language grafted onto PHP - the syntax is **pure compile-time sugar**. Every markup expression lowers, during compilation, to a plain `new` expression: + +```php +// The new syntax... +$html = ; + +// ...compiles to exactly this - same AST, same opcodes: +$html = new \Html\Element('button', ['class' => 'btn'], ['Sign in']); +``` + +Nothing downstream of the parser changes, and markup obeys the expression semantics every PHP developer already knows. + +The two headline benefits over every existing option are **composition** (components, attributes, slots) and **escape-by-default safety** - neither of which PHP's existing markup facilities provide. And because markup is ordinary PHP code rather than a string dialect, static analysis reaches inside it: component props are named arguments checked against real signatures, so templates become type-checkable end-to-end. + +### Why this is worth doing + +Generating HTML is not a niche PHP task - it is arguably *the* PHP task, and essentially every major framework has independently built the same answer to it: an escape-by-default, composable template engine. Their adoption is enormous - on Packagist, `laravel/framework` (Blade) has over **540 million** installs and `twig/twig` over **460 million**, before counting Latte, Smarty, Plates, or hand-rolled templates that never appear in a dependency graph. + +That every engine converged on the same two features is strong evidence the need is real and the language does not meet it: developers reach for a third-party compiler, a separate dialect, and a build step to obtain what the runtime could provide natively. And escape-by-default is no mere convenience - cross-site scripting has sat in the OWASP Top 10 for its entire existence, and automatic HTML escaping is one of the disciplines that prevents it. + +## History + +This idea is not new - PHP is where it started. Around 2010, Facebook built **XHP**, a PHP extension that made XML/HTML fragments valid PHP expressions with automatic escaping - `Link` as a real value - and it survives today as a native feature of Hack. **JSX was born directly out of XHP**, and has since become *the* way most frontend developers write user interfaces. The pattern has proven itself at ecosystem scale; this RFC brings it home. + +The approach has surfaced in various forms many times over the years without a formal proposal; discussions about "automatic template escaping" resulted in arguments that "XHP really is the right solution for this problem," though that also raised the central objection - escaping is context-dependent (HTML vs URL vs JS vs CSS) - which this RFC answers by scoping escaping to HTML context only (see Escape-by-default). + +### Why core, and not an extension + +An obvious question - especially given the XHP precedent - is whether this could be proven out as a PECL extension first. It cannot, for one technical reason and one practical one. + +Technically, markup must begin at a bare `<` in operand position, and telling that apart from the comparison and shift operators requires the scanner change described under Parsing - state no extension can reach. The only route open to an extension is rewriting source text before compilation (the original XHP extension's approach), which shifts line numbers in errors and stack traces and leaves the raw file something `token_get_all()` - and therefore every tool built on it - cannot tokenize. + +Practically, a *syntax* lives or dies by its ecosystem. Code using an extension-only syntax cannot be reasonably published to Packagist - it is a parse error on any install without the extension - and no IDE, formatter, or static analyser updates its grammar for syntax that may not exist on a given machine. That chicken-and-egg is part of where XHP for PHP stalled, while the same feature thrived in Hack, where it is part of the language. Shipping in core is what makes markup part of PHP itself: parsers, IDEs, and analysis tools implement it once, and every developer's markup gets the same highlighting, formatting, and static analysis their ordinary code already enjoys. + +## Goals & Non-Goals + +The goal of this RFC is a native, ergonomic syntax for building HTML - the output format the overwhelming majority of PHP produces - with escaping and composition built in. + +This RFC is deliberately narrower than XHP: it targets **HTML specifically**, not general XML. That choice is visible throughout - escaping uses `htmlspecialchars`; void elements serialize as HTML5 (`
    `, not `
    `); text and attribute handling follow HTML, not XML, semantics. There is no XML namespace support, no CDATA, no DTD, and no requirement that output be well-formed XML (`` *looks* like a namespaced XML element but is a compile-time construct, not a namespace). This keeps the surface small and the output aimed at the one thing PHP overwhelmingly produces: web pages. Emitting XML (RSS, SVG, SOAP) remains the job of the existing DOM / `XMLWriter` APIs and is out of scope. + +Semantic validation of attribute *values* is also not a goal - a `javascript:` URL in `href`/`src` escapes cleanly yet is not blocked: sometimes it is intentional, and the policy is the application's to set. (Latte nullifies such URLs and React warns; a framework can enforce the same here via a component decorator or userland wrapper.) Attribute and tag *names*, by contrast, are always validated - see Escape-by-default. + +The aim, in short: enough syntax to make HTML a first-class concern of PHP, with the extension points for frameworks to customize behaviour, and room to grow without breaking backward compatibility (see Future Scope). + +## Proposal + +### 1. A markup expression that evaluates to an object + +The core proposal is that a markup expression evaluates to an instance of a built-in type implementing `Html\Htmlable`. It is **not** a string - but casting it to one will return a string value. + +This is the JSX model (elements are objects, strings appear only after rendering) and is what makes composition and a single, central escaping step possible. + +#### Desugaring at a glance + +Every construct in this proposal lowers the same way - at compile time, to a `new` expression or a function call, behaviour PHP already supports well; no other part of the engine changes. + +```php +// Attributes: literal, ={expr}, bare boolean, {...spread} (see Attributes) +{$label} +// → new \Html\Element('a', ['href' => $url, 'class' => 'btn'], [$label]) + +// Fragments group children with no wrapper element (see Syntax structure) +<>

    {$title}

    +// → new \Html\Fragment([ +// new \Html\Element('h1', [], [$title]), +// new \Html\Element('input', [...$attrs, 'required' => true], []), +// ]) + +// Components dispatch through Html\render_component() (see Tags) + +// → \Html\render_component(Card::class, ['title' => 'Hi'], null, [], 'Card') + +// Component body → anonymous slot; blocks → named slots (see Children & slots) + +

    Title

    +

    Loose content.

    +
    +// → \Html\render_component( +// Layout::class, // class-name candidate +// ['theme' => 'dark'], // props → named arguments +// new \Html\Fragment([/*

    ...

    */]), // anonymous slot +// ['header' => new \Html\Fragment([/*

    ...

    */])], // named slots +// 'Layout', // function-name candidate +// ) + +// Dynamic tags: the value classifies at runtime (see Tags) +<$tag class="box">{$content} +// → \Html\render_dynamic($tag, ['class' => 'box'], [$content], []) +``` + +Two properties fall directly out of this lowering. Markup is **zero-cost sugar** - writing the objects by hand produces the same AST and opcodes. And markup obeys **ordinary expression semantics** - attributes and children are argument expressions, evaluated eagerly, scoped and typed like any other PHP code. + +### 2. Escape-by-default + +This is a *templating* feature, and like every major PHP template engine (Blade, Twig, Latte) it **escapes by default**: + +* Interpolations `{$expr}` and **attribute values** are escaped with `htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401)` - like `htmlspecialchars()` itself, the charset comes from ini `default_charset` (UTF-8 by default), so non-UTF-8 applications keep working. +* Any value implementing `Html\Htmlable` (including elements and fragments) passes through **un-escaped** - it is already assumed to be safe HTML. +* The explicit, greppable opt-out for trusted raw strings is `Html\raw($trustedString)`, which returns an `Html\Htmlable`. `Html\escape($string)` is also provided. + +Escaping is **HTML-context only**. URL/JS/CSS-context escaping is explicitly **out of scope** and remains the author's responsibility. Full context-aware escaping is noted as Future Scope. + +A single escaping context is sound here in a way it is not for string-template engines, because there is no context to *detect*: + +* The grammar admits dynamic data in few positions - child content and attribute values - each escaped by one fixed rule (attribute values are always serialized double-quoted; no unquoted output form exists). +* Dynamically-supplied *names* (spread keys, `new Element($tag)`) are **validated** and can throw a runtime exception, not escaped - an invalid name is a `ValueError`, so a name can never break out of the markup. +* `` breakout is inexpressible. + +### 3. Tags: HTML elements vs components + +Dispatch is purely **syntactic** (the compiler cannot know runtime types): + +| Tag form | Meaning | +|---|---| +| `
    `, `` (lowercase / hyphenated) | literal HTML element → `Html\Element` | +| `` (bare, capitalized) | a **component** - resolved as a name honoring `use` / `use function` / namespace | +| ``, `<\App\Card>` (namespace-qualified) | a **component** - a namespace-qualified or fully-qualified name | +| `` | a **component** - a static method call on a class | +| `<$tag>`, `<{expr}>` (a variable / any expression) | a **dynamic tag** - the runtime string value is classified by the same rule, at runtime | + +Any tag whose name is capitalized, contains a namespace separator (`\`), or names a static method (`::`) is a component; everything else is a literal HTML element. + +A **component is anything that produces `Html\Htmlable`.** There are three forms: + +```php +// Function +function Time(DateTimeInterface $datetime): Html\Htmlable { /* ... */ } +//