| author | |
| committer | |
| log | c17309dbc52c055dfd147ba46c6fa110df6b8227 |
| tree | 3773ed747e002cba9cf8a9b1d9ae1b3edc2e06e4 |
| parent | 17e574fec60b5e675a8ac69d197f1cc28336056c |
7 files changed, 204 insertions(+), 6 deletions(-)
doc/langref.md+1-1| ... | ... | @@ -94,7 +94,7 @@ BlockExpression : IfExpression | Block | WhileExpression | ForExpression | Switc |
| 94 | 94 | |
| 95 | 95 | SwitchExpression : "switch" "(" Expression ")" "{" many(SwitchProng) "}" |
| 96 | 96 | |
| 97 | SwitchProng : (list(SwitchItem, ",") | "else") option("(" "Symbol" ")") "=>" Expression "," | |
| 97 | SwitchProng : (list(SwitchItem, ",") | "else") option("," "(" "Symbol" ")") "=>" Expression "," | |
| 98 | 98 | |
| 99 | 99 | SwitchItem : Expression | (Expression "..." Expression) |
| 100 | 100 |
src/all_types.hpp+25| ... | ... | @@ -139,6 +139,9 @@ enum NodeType { |
| 139 | 139 | NodeTypeIfVarExpr, |
| 140 | 140 | NodeTypeWhileExpr, |
| 141 | 141 | NodeTypeForExpr, |
| 142 | NodeTypeSwitchExpr, | |
| 143 | NodeTypeSwitchProng, | |
| 144 | NodeTypeSwitchRange, | |
| 142 | 145 | NodeTypeLabel, |
| 143 | 146 | NodeTypeGoto, |
| 144 | 147 | NodeTypeBreak, |
| ... | ... | @@ -411,6 +414,25 @@ struct AstNodeForExpr { |
| 411 | 414 | VariableTableEntry *index_var; |
| 412 | 415 | }; |
| 413 | 416 | |
| 417 | struct AstNodeSwitchExpr { | |
| 418 | AstNode *expr; | |
| 419 | ZigList<AstNode *> prongs; | |
| 420 | ||
| 421 | // populated by semantic analyzer | |
| 422 | Expr resolved_expr; | |
| 423 | }; | |
| 424 | ||
| 425 | struct AstNodeSwitchProng { | |
| 426 | ZigList<AstNode *> items; | |
| 427 | AstNode *var_symbol; | |
| 428 | AstNode *expr; | |
| 429 | }; | |
| 430 | ||
| 431 | struct AstNodeSwitchRange { | |
| 432 | AstNode *start; | |
| 433 | AstNode *end; | |
| 434 | }; | |
| 435 | ||
| 414 | 436 | struct AstNodeLabel { |
| 415 | 437 | Buf name; |
| 416 | 438 | |
| ... | ... | @@ -623,6 +645,9 @@ struct AstNode { |
| 623 | 645 | AstNodeIfVarExpr if_var_expr; |
| 624 | 646 | AstNodeWhileExpr while_expr; |
| 625 | 647 | AstNodeForExpr for_expr; |
| 648 | AstNodeSwitchExpr switch_expr; | |
| 649 | AstNodeSwitchProng switch_prong; | |
| 650 | AstNodeSwitchRange switch_range; | |
| 626 | 651 | AstNodeLabel label; |
| 627 | 652 | AstNodeGoto goto_expr; |
| 628 | 653 | AstNodeAsmExpr asm_expr; |
src/analyze.cpp+52| ... | ... | @@ -30,6 +30,8 @@ static AstNode *first_executing_node(AstNode *node) { |
| 30 | 30 | return first_executing_node(node->data.slice_expr.array_ref_expr); |
| 31 | 31 | case NodeTypeFieldAccessExpr: |
| 32 | 32 | return first_executing_node(node->data.field_access_expr.struct_expr); |
| 33 | case NodeTypeSwitchRange: | |
| 34 | return first_executing_node(node->data.switch_range.start); | |
| 33 | 35 | case NodeTypeRoot: |
| 34 | 36 | case NodeTypeRootExportDecl: |
| 35 | 37 | case NodeTypeFnProto: |
| ... | ... | @@ -61,6 +63,8 @@ static AstNode *first_executing_node(AstNode *node) { |
| 61 | 63 | case NodeTypeStructValueField: |
| 62 | 64 | case NodeTypeWhileExpr: |
| 63 | 65 | case NodeTypeForExpr: |
| 66 | case NodeTypeSwitchExpr: | |
| 67 | case NodeTypeSwitchProng: | |
| 64 | 68 | case NodeTypeContainerInitExpr: |
| 65 | 69 | case NodeTypeArrayType: |
| 66 | 70 | return node; |
| ... | ... | @@ -943,6 +947,9 @@ static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode |
| 943 | 947 | case NodeTypeIfVarExpr: |
| 944 | 948 | case NodeTypeWhileExpr: |
| 945 | 949 | case NodeTypeForExpr: |
| 950 | case NodeTypeSwitchExpr: | |
| 951 | case NodeTypeSwitchProng: | |
| 952 | case NodeTypeSwitchRange: | |
| 946 | 953 | case NodeTypeLabel: |
| 947 | 954 | case NodeTypeGoto: |
| 948 | 955 | case NodeTypeBreak: |
| ... | ... | @@ -3007,6 +3014,12 @@ static TypeTableEntry *analyze_prefix_op_expr(CodeGen *g, ImportTableEntry *impo |
| 3007 | 3014 | zig_unreachable(); |
| 3008 | 3015 | } |
| 3009 | 3016 | |
| 3017 | static TypeTableEntry *analyze_switch_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, | |
| 3018 | TypeTableEntry *expected_type, AstNode *node) | |
| 3019 | { | |
| 3020 | zig_panic("TODO analyze_switch_expr"); | |
| 3021 | } | |
| 3022 | ||
| 3010 | 3023 | static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context, |
| 3011 | 3024 | TypeTableEntry *expected_type, AstNode *node) |
| 3012 | 3025 | { |
| ... | ... | @@ -3184,6 +3197,11 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 3184 | 3197 | case NodeTypeArrayType: |
| 3185 | 3198 | return_type = analyze_array_type(g, import, context, expected_type, node); |
| 3186 | 3199 | break; |
| 3200 | case NodeTypeSwitchExpr: | |
| 3201 | return_type = analyze_switch_expr(g, import, context, expected_type, node); | |
| 3202 | break; | |
| 3203 | case NodeTypeSwitchProng: | |
| 3204 | case NodeTypeSwitchRange: | |
| 3187 | 3205 | case NodeTypeDirective: |
| 3188 | 3206 | case NodeTypeFnDecl: |
| 3189 | 3207 | case NodeTypeFnProto: |
| ... | ... | @@ -3338,6 +3356,9 @@ static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode |
| 3338 | 3356 | case NodeTypeIfVarExpr: |
| 3339 | 3357 | case NodeTypeWhileExpr: |
| 3340 | 3358 | case NodeTypeForExpr: |
| 3359 | case NodeTypeSwitchExpr: | |
| 3360 | case NodeTypeSwitchProng: | |
| 3361 | case NodeTypeSwitchRange: | |
| 3341 | 3362 | case NodeTypeLabel: |
| 3342 | 3363 | case NodeTypeGoto: |
| 3343 | 3364 | case NodeTypeBreak: |
| ... | ... | @@ -3472,6 +3493,24 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode |
| 3472 | 3493 | } |
| 3473 | 3494 | collect_expr_decl_deps(g, import, node->data.array_type.child_type, decl_node); |
| 3474 | 3495 | break; |
| 3496 | case NodeTypeSwitchExpr: | |
| 3497 | collect_expr_decl_deps(g, import, node->data.switch_expr.expr, decl_node); | |
| 3498 | for (int i = 0; i < node->data.switch_expr.prongs.length; i += 1) { | |
| 3499 | AstNode *prong = node->data.switch_expr.prongs.at(i); | |
| 3500 | collect_expr_decl_deps(g, import, prong, decl_node); | |
| 3501 | } | |
| 3502 | break; | |
| 3503 | case NodeTypeSwitchProng: | |
| 3504 | for (int i = 0; i < node->data.switch_prong.items.length; i += 1) { | |
| 3505 | AstNode *child = node->data.switch_prong.items.at(i); | |
| 3506 | collect_expr_decl_deps(g, import, child, decl_node); | |
| 3507 | } | |
| 3508 | collect_expr_decl_deps(g, import, node->data.switch_prong.expr, decl_node); | |
| 3509 | break; | |
| 3510 | case NodeTypeSwitchRange: | |
| 3511 | collect_expr_decl_deps(g, import, node->data.switch_range.start, decl_node); | |
| 3512 | collect_expr_decl_deps(g, import, node->data.switch_range.end, decl_node); | |
| 3513 | break; | |
| 3475 | 3514 | case NodeTypeVariableDeclaration: |
| 3476 | 3515 | case NodeTypeFnProto: |
| 3477 | 3516 | case NodeTypeExternBlock: |
| ... | ... | @@ -3661,6 +3700,9 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast |
| 3661 | 3700 | case NodeTypeIfVarExpr: |
| 3662 | 3701 | case NodeTypeWhileExpr: |
| 3663 | 3702 | case NodeTypeForExpr: |
| 3703 | case NodeTypeSwitchExpr: | |
| 3704 | case NodeTypeSwitchProng: | |
| 3705 | case NodeTypeSwitchRange: | |
| 3664 | 3706 | case NodeTypeLabel: |
| 3665 | 3707 | case NodeTypeGoto: |
| 3666 | 3708 | case NodeTypeBreak: |
| ... | ... | @@ -3869,6 +3911,10 @@ Expr *get_resolved_expr(AstNode *node) { |
| 3869 | 3911 | return &node->data.label.resolved_expr; |
| 3870 | 3912 | case NodeTypeArrayType: |
| 3871 | 3913 | return &node->data.array_type.resolved_expr; |
| 3914 | case NodeTypeSwitchExpr: | |
| 3915 | return &node->data.switch_expr.resolved_expr; | |
| 3916 | case NodeTypeSwitchProng: | |
| 3917 | case NodeTypeSwitchRange: | |
| 3872 | 3918 | case NodeTypeRoot: |
| 3873 | 3919 | case NodeTypeRootExportDecl: |
| 3874 | 3920 | case NodeTypeFnProto: |
| ... | ... | @@ -3902,6 +3948,9 @@ NumLitCodeGen *get_resolved_num_lit(AstNode *node) { |
| 3902 | 3948 | case NodeTypeIfVarExpr: |
| 3903 | 3949 | case NodeTypeWhileExpr: |
| 3904 | 3950 | case NodeTypeForExpr: |
| 3951 | case NodeTypeSwitchExpr: | |
| 3952 | case NodeTypeSwitchProng: | |
| 3953 | case NodeTypeSwitchRange: | |
| 3905 | 3954 | case NodeTypeAsmExpr: |
| 3906 | 3955 | case NodeTypeContainerInitExpr: |
| 3907 | 3956 | case NodeTypeRoot: |
| ... | ... | @@ -3953,6 +4002,9 @@ TopLevelDecl *get_resolved_top_level_decl(AstNode *node) { |
| 3953 | 4002 | case NodeTypeIfVarExpr: |
| 3954 | 4003 | case NodeTypeWhileExpr: |
| 3955 | 4004 | case NodeTypeForExpr: |
| 4005 | case NodeTypeSwitchExpr: | |
| 4006 | case NodeTypeSwitchProng: | |
| 4007 | case NodeTypeSwitchRange: | |
| 3956 | 4008 | case NodeTypeAsmExpr: |
| 3957 | 4009 | case NodeTypeContainerInitExpr: |
| 3958 | 4010 | case NodeTypeRoot: |
src/codegen.cpp+10| ... | ... | @@ -1965,6 +1965,12 @@ static LLVMValueRef gen_symbol(CodeGen *g, AstNode *node) { |
| 1965 | 1965 | return fn_entry->fn_value; |
| 1966 | 1966 | } |
| 1967 | 1967 | |
| 1968 | static LLVMValueRef gen_switch_expr(CodeGen *g, AstNode *node) { | |
| 1969 | assert(node->type == NodeTypeSwitchExpr); | |
| 1970 | ||
| 1971 | zig_panic("TODO gen_switch_expr"); | |
| 1972 | } | |
| 1973 | ||
| 1968 | 1974 | static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) { |
| 1969 | 1975 | switch (node->type) { |
| 1970 | 1976 | case NodeTypeBinOpExpr: |
| ... | ... | @@ -2040,6 +2046,8 @@ static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) { |
| 2040 | 2046 | } |
| 2041 | 2047 | case NodeTypeContainerInitExpr: |
| 2042 | 2048 | return gen_container_init_expr(g, node); |
| 2049 | case NodeTypeSwitchExpr: | |
| 2050 | return gen_switch_expr(g, node); | |
| 2043 | 2051 | case NodeTypeRoot: |
| 2044 | 2052 | case NodeTypeRootExportDecl: |
| 2045 | 2053 | case NodeTypeFnProto: |
| ... | ... | @@ -2053,6 +2061,8 @@ static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) { |
| 2053 | 2061 | case NodeTypeStructField: |
| 2054 | 2062 | case NodeTypeStructValueField: |
| 2055 | 2063 | case NodeTypeArrayType: |
| 2064 | case NodeTypeSwitchProng: | |
| 2065 | case NodeTypeSwitchRange: | |
| 2056 | 2066 | zig_unreachable(); |
| 2057 | 2067 | } |
| 2058 | 2068 | zig_unreachable(); |
src/parser.cpp+112-5| ... | ... | @@ -123,6 +123,12 @@ const char *node_type_str(NodeType node_type) { |
| 123 | 123 | return "WhileExpr"; |
| 124 | 124 | case NodeTypeForExpr: |
| 125 | 125 | return "ForExpr"; |
| 126 | case NodeTypeSwitchExpr: | |
| 127 | return "SwitchExpr"; | |
| 128 | case NodeTypeSwitchProng: | |
| 129 | return "SwitchProng"; | |
| 130 | case NodeTypeSwitchRange: | |
| 131 | return "SwitchRange"; | |
| 126 | 132 | case NodeTypeLabel: |
| 127 | 133 | return "Label"; |
| 128 | 134 | case NodeTypeGoto: |
| ... | ... | @@ -342,6 +348,30 @@ void ast_print(AstNode *node, int indent) { |
| 342 | 348 | } |
| 343 | 349 | ast_print(node->data.for_expr.body, indent + 2); |
| 344 | 350 | break; |
| 351 | case NodeTypeSwitchExpr: | |
| 352 | fprintf(stderr, "%s\n", node_type_str(node->type)); | |
| 353 | ast_print(node->data.switch_expr.expr, indent + 2); | |
| 354 | for (int i = 0; i < node->data.switch_expr.prongs.length; i += 1) { | |
| 355 | AstNode *child_node = node->data.switch_expr.prongs.at(i); | |
| 356 | ast_print(child_node, indent + 2); | |
| 357 | } | |
| 358 | break; | |
| 359 | case NodeTypeSwitchProng: | |
| 360 | fprintf(stderr, "%s\n", node_type_str(node->type)); | |
| 361 | for (int i = 0; i < node->data.switch_prong.items.length; i += 1) { | |
| 362 | AstNode *child_node = node->data.switch_prong.items.at(i); | |
| 363 | ast_print(child_node, indent + 2); | |
| 364 | } | |
| 365 | if (node->data.switch_prong.var_symbol) { | |
| 366 | ast_print(node->data.switch_prong.var_symbol, indent + 2); | |
| 367 | } | |
| 368 | ast_print(node->data.switch_prong.expr, indent + 2); | |
| 369 | break; | |
| 370 | case NodeTypeSwitchRange: | |
| 371 | fprintf(stderr, "%s\n", node_type_str(node->type)); | |
| 372 | ast_print(node->data.switch_range.start, indent + 2); | |
| 373 | ast_print(node->data.switch_range.end, indent + 2); | |
| 374 | break; | |
| 345 | 375 | case NodeTypeLabel: |
| 346 | 376 | fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.label.name)); |
| 347 | 377 | break; |
| ... | ... | @@ -2167,7 +2197,80 @@ static AstNode *ast_parse_for_expr(ParseContext *pc, int *token_index, bool mand |
| 2167 | 2197 | } |
| 2168 | 2198 | |
| 2169 | 2199 | /* |
| 2170 | BlockExpression : IfExpression | Block | WhileExpression | ForExpression | |
| 2200 | SwitchExpression : "switch" "(" Expression ")" "{" many(SwitchProng) "}" | |
| 2201 | SwitchProng : (list(SwitchItem, ",") | "else") option("," "(" "Symbol" ")") "=>" Expression "," | |
| 2202 | SwitchItem : Expression | (Expression "..." Expression) | |
| 2203 | */ | |
| 2204 | static AstNode *ast_parse_switch_expr(ParseContext *pc, int *token_index, bool mandatory) { | |
| 2205 | Token *token = &pc->tokens->at(*token_index); | |
| 2206 | ||
| 2207 | if (token->id != TokenIdKeywordSwitch) { | |
| 2208 | if (mandatory) { | |
| 2209 | ast_invalid_token_error(pc, token); | |
| 2210 | } else { | |
| 2211 | return nullptr; | |
| 2212 | } | |
| 2213 | } | |
| 2214 | *token_index += 1; | |
| 2215 | ||
| 2216 | AstNode *node = ast_create_node(pc, NodeTypeSwitchExpr, token); | |
| 2217 | ||
| 2218 | ast_eat_token(pc, token_index, TokenIdLParen); | |
| 2219 | node->data.switch_expr.expr = ast_parse_expression(pc, token_index, true); | |
| 2220 | ast_eat_token(pc, token_index, TokenIdRParen); | |
| 2221 | ast_eat_token(pc, token_index, TokenIdLBrace); | |
| 2222 | ||
| 2223 | for (;;) { | |
| 2224 | Token *token = &pc->tokens->at(*token_index); | |
| 2225 | ||
| 2226 | if (token->id == TokenIdRBrace) { | |
| 2227 | *token_index += 1; | |
| 2228 | return node; | |
| 2229 | } | |
| 2230 | ||
| 2231 | AstNode *prong_node = ast_create_node(pc, NodeTypeSwitchProng, token); | |
| 2232 | node->data.switch_expr.prongs.append(prong_node); | |
| 2233 | ||
| 2234 | if (token->id == TokenIdKeywordElse) { | |
| 2235 | *token_index += 1; | |
| 2236 | } else for (;;) { | |
| 2237 | AstNode *expr1 = ast_parse_expression(pc, token_index, true); | |
| 2238 | Token *ellipsis_tok = &pc->tokens->at(*token_index); | |
| 2239 | if (ellipsis_tok->id == TokenIdEllipsis) { | |
| 2240 | *token_index += 1; | |
| 2241 | ||
| 2242 | AstNode *range_node = ast_create_node(pc, NodeTypeSwitchRange, ellipsis_tok); | |
| 2243 | prong_node->data.switch_prong.items.append(range_node); | |
| 2244 | ||
| 2245 | range_node->data.switch_range.start = expr1; | |
| 2246 | range_node->data.switch_range.end = ast_parse_expression(pc, token_index, true); | |
| 2247 | } else { | |
| 2248 | prong_node->data.switch_prong.items.append(expr1); | |
| 2249 | } | |
| 2250 | Token *comma_tok = &pc->tokens->at(*token_index); | |
| 2251 | if (comma_tok->id == TokenIdComma) { | |
| 2252 | *token_index += 1; | |
| 2253 | continue; | |
| 2254 | } | |
| 2255 | break; | |
| 2256 | } | |
| 2257 | ||
| 2258 | Token *arrow_or_comma = &pc->tokens->at(*token_index); | |
| 2259 | if (arrow_or_comma->id == TokenIdComma) { | |
| 2260 | *token_index += 1; | |
| 2261 | ast_eat_token(pc, token_index, TokenIdLParen); | |
| 2262 | prong_node->data.switch_prong.var_symbol = ast_parse_symbol(pc, token_index); | |
| 2263 | ast_eat_token(pc, token_index, TokenIdRParen); | |
| 2264 | } | |
| 2265 | ||
| 2266 | ast_eat_token(pc, token_index, TokenIdFatArrow); | |
| 2267 | prong_node->data.switch_prong.expr = ast_parse_expression(pc, token_index, true); | |
| 2268 | ast_eat_token(pc, token_index, TokenIdComma); | |
| 2269 | } | |
| 2270 | } | |
| 2271 | ||
| 2272 | /* | |
| 2273 | BlockExpression : IfExpression | Block | WhileExpression | ForExpression | SwitchExpression | |
| 2171 | 2274 | */ |
| 2172 | 2275 | static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool mandatory) { |
| 2173 | 2276 | Token *token = &pc->tokens->at(*token_index); |
| ... | ... | @@ -2176,10 +2279,6 @@ static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool ma |
| 2176 | 2279 | if (if_expr) |
| 2177 | 2280 | return if_expr; |
| 2178 | 2281 | |
| 2179 | AstNode *block = ast_parse_block(pc, token_index, false); | |
| 2180 | if (block) | |
| 2181 | return block; | |
| 2182 | ||
| 2183 | 2282 | AstNode *while_expr = ast_parse_while_expr(pc, token_index, false); |
| 2184 | 2283 | if (while_expr) |
| 2185 | 2284 | return while_expr; |
| ... | ... | @@ -2188,6 +2287,14 @@ static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool ma |
| 2188 | 2287 | if (for_expr) |
| 2189 | 2288 | return for_expr; |
| 2190 | 2289 | |
| 2290 | AstNode *switch_expr = ast_parse_switch_expr(pc, token_index, false); | |
| 2291 | if (switch_expr) | |
| 2292 | return switch_expr; | |
| 2293 | ||
| 2294 | AstNode *block = ast_parse_block(pc, token_index, false); | |
| 2295 | if (block) | |
| 2296 | return block; | |
| 2297 | ||
| 2191 | 2298 | if (mandatory) |
| 2192 | 2299 | ast_invalid_token_error(pc, token); |
| 2193 | 2300 |
src/tokenizer.cpp+3| ... | ... | @@ -243,6 +243,8 @@ static void end_token(Tokenize *t) { |
| 243 | 243 | t->cur_tok->id = TokenIdKeywordNull; |
| 244 | 244 | } else if (mem_eql_str(token_mem, token_len, "noalias")) { |
| 245 | 245 | t->cur_tok->id = TokenIdKeywordNoAlias; |
| 246 | } else if (mem_eql_str(token_mem, token_len, "switch")) { | |
| 247 | t->cur_tok->id = TokenIdKeywordSwitch; | |
| 246 | 248 | } |
| 247 | 249 | |
| 248 | 250 | t->cur_tok = nullptr; |
| ... | ... | @@ -1035,6 +1037,7 @@ const char * token_name(TokenId id) { |
| 1035 | 1037 | case TokenIdKeywordBreak: return "break"; |
| 1036 | 1038 | case TokenIdKeywordNull: return "null"; |
| 1037 | 1039 | case TokenIdKeywordNoAlias: return "noalias"; |
| 1040 | case TokenIdKeywordSwitch: return "switch"; | |
| 1038 | 1041 | case TokenIdLParen: return "("; |
| 1039 | 1042 | case TokenIdRParen: return ")"; |
| 1040 | 1043 | case TokenIdComma: return ","; |
src/tokenizer.hpp+1| ... | ... | @@ -36,6 +36,7 @@ enum TokenId { |
| 36 | 36 | TokenIdKeywordBreak, |
| 37 | 37 | TokenIdKeywordNull, |
| 38 | 38 | TokenIdKeywordNoAlias, |
| 39 | TokenIdKeywordSwitch, | |
| 39 | 40 | TokenIdLParen, |
| 40 | 41 | TokenIdRParen, |
| 41 | 42 | TokenIdComma, |