From dc530338989a087771ba528dd25f0662f482ae37 Mon Sep 17 00:00:00 2001 From: hgranthorner <37941012+hgranthorner@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:37:46 -0400 Subject: [PATCH] Build: regenerate the parser on every build, and always from scratch Two staleness traps cost real debugging time, and both made grammar edits look like they had no effect: - compileJava/compileKotlin read src/main/gen with no task dependency, so an edit to Jai.bnf could be compiled against the previous generated parser. The compiled JaiParser.class was three minutes older than its source while the tests reported green. They now depend on generateParser. - purgeOldFiles is not enough. Grammar-Kit rewrites only the files whose rule changed, so JaiDeclaration kept a getArgumentList() its regenerated Impl no longer had and the build failed inside generated code. generateParser now deletes src/main/gen first. Grammar: named return defaults use a private rule rather than 'initializer'. Giving the default its own PSI element made the enclosing procLiteralExpr end early, so '-> a: int = 1 { }' silently lost its body. Corpus parse 90.9% (649/714), ratchet at 645. --- build.gradle.kts | 24 ++ .../gen/dev/hgh/jai/parser/JaiParser.java | 239 ++++++++++++++---- .../gen/dev/hgh/jai/psi/JaiDeclaration.java | 3 - .../hgh/jai/psi/impl/JaiDeclarationImpl.java | 6 - src/main/grammar/Jai.bnf | 18 +- .../dev/hgh/jai/parser/JaiParserUtil.kt | 16 +- .../dev/hgh/jai/parser/DebugParseTest.kt | 17 +- .../dev/hgh/jai/parser/JaiCorpusParserTest.kt | 2 +- 8 files changed, 245 insertions(+), 80 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d07111b..4e2ae5c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { id("org.jetbrains.intellij.platform.grammarkit") } +// Grammar-Kit output. Committed so diffs are reviewable, never hand-edited. sourceSets { main { java.srcDirs("src/main/gen") @@ -33,4 +34,27 @@ tasks.generateParser { pathToParser.set("dev/hgh/jai/parser/JaiParser.java") pathToPsiRoot.set("dev/hgh/jai/psi") purgeOldFiles.set(true) + + // purgeOldFiles is not enough: Grammar-Kit rewrites only the files whose rule + // changed, so a PSI interface can keep an accessor its regenerated Impl no longer + // has (`JaiDeclaration.getArgumentList()` with no override) and the build fails on + // generated code. Always regenerate the whole tree. + doFirst { + delete(file("src/main/gen")) + } +} + +// Without this the compile tasks read src/main/gen with no ordering guarantee, so an +// edit to Jai.bnf could be compiled against the *previous* generated parser and the +// tests would quietly assert the old grammar. Editing Jai.bnf must be enough. +tasks.compileJava { dependsOn(tasks.generateParser) } + +tasks.compileKotlin { dependsOn(tasks.generateParser) } + +// Forward -Djai.debug.* to the test JVM for the scratch parser harness. +tasks.test { + System.getProperties().forEach { key, value -> + val name = key.toString() + if (name.startsWith("jai.debug.")) systemProperty(name, value.toString()) + } } diff --git a/src/main/gen/dev/hgh/jai/parser/JaiParser.java b/src/main/gen/dev/hgh/jai/parser/JaiParser.java index 4a6c3e9..2554f88 100644 --- a/src/main/gen/dev/hgh/jai/parser/JaiParser.java +++ b/src/main/gen/dev/hgh/jai/parser/JaiParser.java @@ -51,43 +51,98 @@ public class JaiParser implements PsiParser, LightPsiParser { }; /* ********************************************************** */ - // argument ((',' | ',,') argument)* [','] + // ',,' argument ((',' | ',,') argument)* + // | argument ((',' | ',,') argument)* [','] static boolean argList(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "argList")) return false; boolean result_; Marker marker_ = enter_section_(builder_); - result_ = argument(builder_, level_ + 1); - result_ = result_ && argList_1(builder_, level_ + 1); - result_ = result_ && argList_2(builder_, level_ + 1); + result_ = argList_0(builder_, level_ + 1); + if (!result_) result_ = argList_1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // ',,' argument ((',' | ',,') argument)* + private static boolean argList_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, DOUBLE_COMMA); + result_ = result_ && argument(builder_, level_ + 1); + result_ = result_ && argList_0_2(builder_, level_ + 1); exit_section_(builder_, marker_, null, result_); return result_; } // ((',' | ',,') argument)* - private static boolean argList_1(PsiBuilder builder_, int level_) { - if (!recursion_guard_(builder_, level_, "argList_1")) return false; + private static boolean argList_0_2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_0_2")) return false; while (true) { int pos_ = current_position_(builder_); - if (!argList_1_0(builder_, level_ + 1)) break; - if (!empty_element_parsed_guard_(builder_, "argList_1", pos_)) break; + if (!argList_0_2_0(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "argList_0_2", pos_)) break; } return true; } // (',' | ',,') argument - private static boolean argList_1_0(PsiBuilder builder_, int level_) { - if (!recursion_guard_(builder_, level_, "argList_1_0")) return false; + private static boolean argList_0_2_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_0_2_0")) return false; boolean result_; Marker marker_ = enter_section_(builder_); - result_ = argList_1_0_0(builder_, level_ + 1); + result_ = argList_0_2_0_0(builder_, level_ + 1); result_ = result_ && argument(builder_, level_ + 1); exit_section_(builder_, marker_, null, result_); return result_; } // ',' | ',,' - private static boolean argList_1_0_0(PsiBuilder builder_, int level_) { - if (!recursion_guard_(builder_, level_, "argList_1_0_0")) return false; + private static boolean argList_0_2_0_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_0_2_0_0")) return false; + boolean result_; + result_ = consumeToken(builder_, COMMA); + if (!result_) result_ = consumeToken(builder_, DOUBLE_COMMA); + return result_; + } + + // argument ((',' | ',,') argument)* [','] + private static boolean argList_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = argument(builder_, level_ + 1); + result_ = result_ && argList_1_1(builder_, level_ + 1); + result_ = result_ && argList_1_2(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // ((',' | ',,') argument)* + private static boolean argList_1_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_1_1")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!argList_1_1_0(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "argList_1_1", pos_)) break; + } + return true; + } + + // (',' | ',,') argument + private static boolean argList_1_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_1_1_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = argList_1_1_0_0(builder_, level_ + 1); + result_ = result_ && argument(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // ',' | ',,' + private static boolean argList_1_1_0_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_1_1_0_0")) return false; boolean result_; result_ = consumeToken(builder_, COMMA); if (!result_) result_ = consumeToken(builder_, DOUBLE_COMMA); @@ -95,8 +150,8 @@ public class JaiParser implements PsiParser, LightPsiParser { } // [','] - private static boolean argList_2(PsiBuilder builder_, int level_) { - if (!recursion_guard_(builder_, level_, "argList_2")) return false; + private static boolean argList_1_2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "argList_1_2")) return false; consumeToken(builder_, COMMA); return true; } @@ -1510,6 +1565,35 @@ public class JaiParser implements PsiParser, LightPsiParser { return true; } + /* ********************************************************** */ + // ('break' | 'continue' | 'remove') [expr] + static boolean jumpArgument_(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "jumpArgument_")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = jumpArgument__0(builder_, level_ + 1); + result_ = result_ && jumpArgument__1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // 'break' | 'continue' | 'remove' + private static boolean jumpArgument__0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "jumpArgument__0")) return false; + boolean result_; + result_ = consumeToken(builder_, KW_BREAK); + if (!result_) result_ = consumeToken(builder_, KW_CONTINUE); + if (!result_) result_ = consumeToken(builder_, KW_REMOVE); + return result_; + } + + // [expr] + private static boolean jumpArgument__1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "jumpArgument__1")) return false; + expr(builder_, level_ + 1, -1); + return true; + } + /* ********************************************************** */ // 'if' | 'for' | 'case' | 'enum' | 'cast' | 'union' | 'struct' // | 'remove' | 'inline' | 'context' | 'operator' | 'interface' @@ -1576,7 +1660,7 @@ public class JaiParser implements PsiParser, LightPsiParser { } /* ********************************************************** */ - // <> memberName '=' (block | expr) + // <> memberName '=' (block | jumpArgument_ | expr) public static boolean namedArgument(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "namedArgument")) return false; boolean result_; @@ -1589,11 +1673,12 @@ public class JaiParser implements PsiParser, LightPsiParser { return result_; } - // block | expr + // block | jumpArgument_ | expr private static boolean namedArgument_3(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "namedArgument_3")) return false; boolean result_; result_ = block(builder_, level_ + 1); + if (!result_) result_ = jumpArgument_(builder_, level_ + 1); if (!result_) result_ = expr(builder_, level_ + 1, -1); return result_; } @@ -1636,7 +1721,7 @@ public class JaiParser implements PsiParser, LightPsiParser { // | '&' | '|' | '^' | '~' | '<<' | '>>' | '<<<' | '>>>' // | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' // | '<<=' | '>>=' | '<<<=' | '>>>=' - // | '[' ']' '=' | '[' ']' + // | '*' '[' ']' | '[' ']' '=' | '[' ']' public static boolean operatorSymbol(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "operatorSymbol")) return false; boolean result_; @@ -1672,6 +1757,7 @@ public class JaiParser implements PsiParser, LightPsiParser { if (!result_) result_ = consumeToken(builder_, SHR_EQ); if (!result_) result_ = consumeToken(builder_, ROL_EQ); if (!result_) result_ = consumeToken(builder_, ROR_EQ); + if (!result_) result_ = parseTokens(builder_, 0, STAR, LBRACKET, RBRACKET); if (!result_) result_ = parseTokens(builder_, 0, LBRACKET, RBRACKET, EQ); if (!result_) result_ = parseTokens(builder_, 0, LBRACKET, RBRACKET); exit_section_(builder_, level_, marker_, result_, false, null); @@ -2176,6 +2262,18 @@ public class JaiParser implements PsiParser, LightPsiParser { return true; } + /* ********************************************************** */ + // '---' | <> + static boolean returnDefault_(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "returnDefault_")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, TRIPLE_MINUS); + if (!result_) result_ = exprNoAssign(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + /* ********************************************************** */ // '(' [returnItem (',' returnItem)*] ')' public static boolean returnGroup(PsiBuilder builder_, int level_) { @@ -2231,31 +2329,85 @@ public class JaiParser implements PsiParser, LightPsiParser { } /* ********************************************************** */ - // [<> IDENT ':'] <> + // <> IDENT (':' [<>] ['=' returnDefault_] | ':=' returnDefault_) + // | <> public static boolean returnItem(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "returnItem")) return false; boolean result_; Marker marker_ = enter_section_(builder_, level_, _NONE_, RETURN_ITEM, ""); result_ = returnItem_0(builder_, level_ + 1); - result_ = result_ && exprNoAssign(builder_, level_ + 1); + if (!result_) result_ = exprNoAssign(builder_, level_ + 1); exit_section_(builder_, level_, marker_, result_, false, null); return result_; } - // [<> IDENT ':'] + // <> IDENT (':' [<>] ['=' returnDefault_] | ':=' returnDefault_) private static boolean returnItem_0(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "returnItem_0")) return false; - returnItem_0_0(builder_, level_ + 1); - return true; - } - - // <> IDENT ':' - private static boolean returnItem_0_0(PsiBuilder builder_, int level_) { - if (!recursion_guard_(builder_, level_, "returnItem_0_0")) return false; boolean result_; Marker marker_ = enter_section_(builder_); result_ = isParamNameAhead(builder_, level_ + 1); - result_ = result_ && consumeTokens(builder_, 0, IDENT, COLON); + result_ = result_ && consumeToken(builder_, IDENT); + result_ = result_ && returnItem_0_2(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // ':' [<>] ['=' returnDefault_] | ':=' returnDefault_ + private static boolean returnItem_0_2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "returnItem_0_2")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = returnItem_0_2_0(builder_, level_ + 1); + if (!result_) result_ = returnItem_0_2_1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // ':' [<>] ['=' returnDefault_] + private static boolean returnItem_0_2_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "returnItem_0_2_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, COLON); + result_ = result_ && returnItem_0_2_0_1(builder_, level_ + 1); + result_ = result_ && returnItem_0_2_0_2(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // [<>] + private static boolean returnItem_0_2_0_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "returnItem_0_2_0_1")) return false; + exprNoAssign(builder_, level_ + 1); + return true; + } + + // ['=' returnDefault_] + private static boolean returnItem_0_2_0_2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "returnItem_0_2_0_2")) return false; + returnItem_0_2_0_2_0(builder_, level_ + 1); + return true; + } + + // '=' returnDefault_ + private static boolean returnItem_0_2_0_2_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "returnItem_0_2_0_2_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, EQ); + result_ = result_ && returnDefault_(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // ':=' returnDefault_ + private static boolean returnItem_0_2_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "returnItem_0_2_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, COLON_EQ); + result_ = result_ && returnDefault_(builder_, level_ + 1); exit_section_(builder_, marker_, null, result_); return result_; } @@ -2672,7 +2824,7 @@ public class JaiParser implements PsiParser, LightPsiParser { } /* ********************************************************** */ - // 'using' [directiveFlag_ ['(' [argList] ')']] + // 'using' [directiveFlag_ [<>]] static boolean usingModifier_(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "usingModifier_")) return false; if (!nextTokenIs(builder_, KW_USING)) return false; @@ -2684,14 +2836,14 @@ public class JaiParser implements PsiParser, LightPsiParser { return result_; } - // [directiveFlag_ ['(' [argList] ')']] + // [directiveFlag_ [<>]] private static boolean usingModifier__1(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "usingModifier__1")) return false; usingModifier__1_0(builder_, level_ + 1); return true; } - // directiveFlag_ ['(' [argList] ')'] + // directiveFlag_ [<>] private static boolean usingModifier__1_0(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "usingModifier__1_0")) return false; boolean result_; @@ -2702,29 +2854,10 @@ public class JaiParser implements PsiParser, LightPsiParser { return result_; } - // ['(' [argList] ')'] + // [<>] private static boolean usingModifier__1_0_1(PsiBuilder builder_, int level_) { if (!recursion_guard_(builder_, level_, "usingModifier__1_0_1")) return false; - usingModifier__1_0_1_0(builder_, level_ + 1); - return true; - } - - // '(' [argList] ')' - private static boolean usingModifier__1_0_1_0(PsiBuilder builder_, int level_) { - if (!recursion_guard_(builder_, level_, "usingModifier__1_0_1_0")) return false; - boolean result_; - Marker marker_ = enter_section_(builder_); - result_ = consumeToken(builder_, LPAREN); - result_ = result_ && usingModifier__1_0_1_0_1(builder_, level_ + 1); - result_ = result_ && consumeToken(builder_, RPAREN); - exit_section_(builder_, marker_, null, result_); - return result_; - } - - // [argList] - private static boolean usingModifier__1_0_1_0_1(PsiBuilder builder_, int level_) { - if (!recursion_guard_(builder_, level_, "usingModifier__1_0_1_0_1")) return false; - argList(builder_, level_ + 1); + exprNoAssign(builder_, level_ + 1); return true; } diff --git a/src/main/gen/dev/hgh/jai/psi/JaiDeclaration.java b/src/main/gen/dev/hgh/jai/psi/JaiDeclaration.java index fdb1bc6..3df0b4c 100644 --- a/src/main/gen/dev/hgh/jai/psi/JaiDeclaration.java +++ b/src/main/gen/dev/hgh/jai/psi/JaiDeclaration.java @@ -7,9 +7,6 @@ import com.intellij.psi.PsiElement; public interface JaiDeclaration extends PsiElement { - @NotNull - List getArgumentList(); - @NotNull List getBlockList(); diff --git a/src/main/gen/dev/hgh/jai/psi/impl/JaiDeclarationImpl.java b/src/main/gen/dev/hgh/jai/psi/impl/JaiDeclarationImpl.java index 3668771..5649d9e 100644 --- a/src/main/gen/dev/hgh/jai/psi/impl/JaiDeclarationImpl.java +++ b/src/main/gen/dev/hgh/jai/psi/impl/JaiDeclarationImpl.java @@ -27,12 +27,6 @@ public class JaiDeclarationImpl extends ASTWrapperPsiElement implements JaiDecla else super.accept(visitor); } - @Override - @NotNull - public List getArgumentList() { - return PsiTreeUtil.getChildrenOfTypeAsList(this, JaiArgument.class); - } - @Override @NotNull public List getBlockList() { diff --git a/src/main/grammar/Jai.bnf b/src/main/grammar/Jai.bnf index 5a907f2..fa1f6c6 100644 --- a/src/main/grammar/Jai.bnf +++ b/src/main/grammar/Jai.bnf @@ -220,7 +220,7 @@ declaration ::= <> declModifier_* declNames declTail_ semiWi private declModifier_ ::= <> directiveFlag_* block | <> directiveFlag_* [parenExpr | STRING] | usingModifier_ -private usingModifier_ ::= 'using' [directiveFlag_ ['(' [argList] ')']] +private usingModifier_ ::= 'using' [directiveFlag_ [<>]] declNames ::= declName (',' declName)* declName ::= ['`'] IDENT private declTail_ ::= ':' [<>] procModifier* [',' assignTargets_] [('=' | ':') initializerList_] @@ -239,7 +239,7 @@ operatorSymbol ::= '+' | '-' | '*' | '/' | '%' | '==' | '!=' | '<' | '>' | '<=' | '&' | '|' | '^' | '~' | '<<' | '>>' | '<<<' | '>>>' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '<<<=' | '>>>=' - | '[' ']' '=' | '[' ']' + | '*' '[' ']' | '[' ']' '=' | '[' ']' // -------------------------------------------------------------------- expressions @@ -327,9 +327,12 @@ private keywordAsName_ ::= 'if' | 'for' | 'case' | 'enum' | 'cast' | 'union' | ' | 'continue' | 'then' | 'else' | 'while' | 'null' | 'true' | 'false' // `,,` introduces an inline context change: `join(parts,, allocator = temp)`. -private argList ::= argument ((',' | ',,') argument)* [','] +private argList ::= ',,' argument ((',' | ',,') argument)* + | argument ((',' | ',,') argument)* [','] argument ::= '---' | namedArgument | expr -namedArgument ::= <> memberName '=' (block | expr) +namedArgument ::= <> memberName '=' (block | jumpArgument_ | expr) +// `break=break bucket` supplies the loop-exit code to a for_expansion. +private jumpArgument_ ::= ('break' | 'continue' | 'remove') [expr] primaryExpr ::= procLiteralExpr | structExpr @@ -382,7 +385,12 @@ private paramTypeTail_ ::= ':' [<>] ['=' initializer] private returnTypes_ ::= <> returnGroup | returnItem (',' returnItem)* returnGroup ::= '(' [returnItem (',' returnItem)*] ')' -returnItem ::= [<> IDENT ':'] <> +returnItem ::= <> IDENT (':' [<>] ['=' returnDefault_] | ':=' returnDefault_) + | <> +// Deliberately not `initializer`: that rule creates its own PSI element, and doing so +// here makes the enclosing procLiteralExpr end early, so `-> a: int = 1 { }` loses its +// body. A return default cannot be an assignment either way. +private returnDefault_ ::= '---' | <> // `#foreign lib`, `#modify { ... }`, `#c_call`, `#expand`, … // `#foreign lib "symbol"`, `#modify { ... }`, `#c_call`, `#expand`, `#align 8` — the diff --git a/src/main/kotlin/dev/hgh/jai/parser/JaiParserUtil.kt b/src/main/kotlin/dev/hgh/jai/parser/JaiParserUtil.kt index 80e36aa..6005d4e 100644 --- a/src/main/kotlin/dev/hgh/jai/parser/JaiParserUtil.kt +++ b/src/main/kotlin/dev/hgh/jai/parser/JaiParserUtil.kt @@ -347,8 +347,8 @@ object JaiParserUtil : GeneratedParserUtilBase() { inPrefix = true } - T.LPAREN -> { - i = skipBalancedParens(b, i) + T.LPAREN, T.LBRACKET, T.BEGIN_ARRAY_LITERAL, T.BEGIN_STRUCT_LITERAL -> { + i = skipBalancedGroup(b, i) if (i < 0) return false } @@ -377,8 +377,12 @@ object JaiParserUtil : GeneratedParserUtilBase() { } } - /** Index just past the `)` matching the `(` at [start], or -1 if unbalanced. */ - private fun skipBalancedParens( + /** + * Index just past the closer matching the bracket at [start], or -1 if unbalanced. + * Covers `(…)`, `[…]`, `.[…]` and `.{…}`, which all appear in declaration + * prefixes: `#overlay (x) using …`, `using,except .["x", "y"] …`. + */ + private fun skipBalancedGroup( b: PsiBuilder, start: Int, ): Int { @@ -386,11 +390,11 @@ object JaiParserUtil : GeneratedParserUtilBase() { var i = start while (i - start <= MAX_LOOKAHEAD) { when (b.lookAhead(i) ?: return -1) { - T.LPAREN -> { + T.LPAREN, T.LBRACKET, T.BEGIN_ARRAY_LITERAL, T.LBRACE, T.BEGIN_STRUCT_LITERAL -> { depth++ } - T.RPAREN -> { + T.RPAREN, T.RBRACKET, T.RBRACE -> { depth-- if (depth == 0) return i + 1 } diff --git a/src/test/kotlin/dev/hgh/jai/parser/DebugParseTest.kt b/src/test/kotlin/dev/hgh/jai/parser/DebugParseTest.kt index c336e4b..926ea75 100644 --- a/src/test/kotlin/dev/hgh/jai/parser/DebugParseTest.kt +++ b/src/test/kotlin/dev/hgh/jai/parser/DebugParseTest.kt @@ -15,12 +15,11 @@ import java.io.File class DebugParseTest : BasePlatformTestCase() { private val snippets = listOf( - "#scope_file\n\n\nbig_endian :: () -> bool {\n return true;\n}\n", - "S :: struct {\n #as using,except(vtable) iunknown: IUnknown;\n}\n", - "using,except(FT_Init) new_module :: #import \"freetype\";\n", - "operator []= :: inline (a: *B, i: int, v: bool) { }\n", - "inotify_init :: (flags: s32 = 0) -> s32 #foreign libc \"inotify_init1\";\n", - "F :: (a: *L) -> E #foreign ft #deprecated \"msg\";\n", + "f :: () -> a: int = 1;\n", + "f :: () -> a: int = 1 { }\n", + "f :: () -> a: int = xx { }\n", + "f :: () -> a: int = 1 #foreign lib;\n", + "g :: () { x := 1; }\n", ) /** Corpus files to dump the first error of. */ @@ -53,11 +52,17 @@ class DebugParseTest : BasePlatformTestCase() { val error = firstError(file) if (error == null) { println("ok ${text.replace("\n", "\\n")}") + if (System.getProperty("jai.debug.tree") != null) { + println(com.intellij.psi.impl.DebugUtil.psiToString(file, true, false)) + } } else { failures++ val at = text.substring(error.textOffset).take(28).replace("\n", "\\n") println("FAIL ${text.replace("\n", "\\n")}") println(" ${error.errorDescription} | at '$at'") + if (System.getProperty("jai.debug.tree") != null) { + println(com.intellij.psi.impl.DebugUtil.psiToString(file, true, false)) + } } } println("$failures/${snippets.size} snippets failed") diff --git a/src/test/kotlin/dev/hgh/jai/parser/JaiCorpusParserTest.kt b/src/test/kotlin/dev/hgh/jai/parser/JaiCorpusParserTest.kt index 218ffdc..1cbded9 100644 --- a/src/test/kotlin/dev/hgh/jai/parser/JaiCorpusParserTest.kt +++ b/src/test/kotlin/dev/hgh/jai/parser/JaiCorpusParserTest.kt @@ -106,7 +106,7 @@ class JaiCorpusParserTest : BasePlatformTestCase() { private companion object { /** Ratchet. Raise it as the grammar improves; never lower it. */ - const val MIN_CLEAN_FILES = 640 + const val MIN_CLEAN_FILES = 645 const val REPORTED_ERRORS = 25 const val REPORTED_KINDS = 20 const val GOT_WIDTH = 14