diff --git a/.gitignore b/.gitignore index 84f14e4f..89cd7441 100644 --- a/.gitignore +++ b/.gitignore @@ -20,8 +20,4 @@ testgenerated/ .pkl-lsp/ -# :pkl-core:makeIntelliJAntlrPluginHappy -gen/ -PklLexer.tokens - .kotlin/ diff --git a/DEVELOPMENT.adoc b/DEVELOPMENT.adoc index 5d3b7f6c..9b68be0c 100644 --- a/DEVELOPMENT.adoc +++ b/DEVELOPMENT.adoc @@ -82,8 +82,6 @@ based on version information from https://search.maven.org, https://plugins.grad * Truffle code generation is performed by Truffle's annotation processor, which runs as part of task `:pkl-core:compileJava` ** Output dir is `generated/truffle/` -* ANTLR code generation is performed by task `:pkl-core:generateTestGrammarSource` -** Output dir is `testgenerated/antlr/` == Remote JVM Debugging diff --git a/build.gradle.kts b/build.gradle.kts index e0b4475c..1c0facbd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,6 @@ import org.jetbrains.gradle.ext.ActionDelegationConfig import org.jetbrains.gradle.ext.ActionDelegationConfig.TestRunner.PLATFORM import org.jetbrains.gradle.ext.ProjectSettings -import org.jetbrains.gradle.ext.TaskTriggersConfig plugins { pklAllProjects @@ -48,9 +47,6 @@ idea { delegateBuildRunToGradle = true testRunner = PLATFORM } - configure { - afterSync(provider { project(":pkl-parser").tasks.named("makeIntelliJAntlrPluginHappy") }) - } } } } diff --git a/buildSrc/src/main/kotlin/pklFatJar.gradle.kts b/buildSrc/src/main/kotlin/pklFatJar.gradle.kts index 57fe1147..22fbb809 100644 --- a/buildSrc/src/main/kotlin/pklFatJar.gradle.kts +++ b/buildSrc/src/main/kotlin/pklFatJar.gradle.kts @@ -116,9 +116,6 @@ tasks.shadowJar { JavaVersionRange.startingAt(JavaLanguageVersion.of(minimumJvmTarget.majorVersion.toInt() + 1)) .forEach { exclude("META-INF/versions/${it.asInt()}/**") } - // org.antlr.v4.runtime.misc.RuleDependencyProcessor - exclude("META-INF/services/javax.annotation.processing.Processor") - exclude("module-info.*") for ((from, to) in relocations) { diff --git a/docs/docs.gradle.kts b/docs/docs.gradle.kts index 6dbf0e34..0ec7ce18 100644 --- a/docs/docs.gradle.kts +++ b/docs/docs.gradle.kts @@ -39,7 +39,6 @@ dependencies { testImplementation(projects.pklCommonsTest) testImplementation(projects.pklParser) testImplementation(libs.junitEngine) - testImplementation(libs.antlrRuntime) } tasks.test { diff --git a/docs/gradle.lockfile b/docs/gradle.lockfile index 9a4ba39a..b00570d5 100644 --- a/docs/gradle.lockfile +++ b/docs/gradle.lockfile @@ -1,7 +1,6 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -com.tunnelvisionlabs:antlr4-runtime:4.9.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d1a45969..132266c1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,4 @@ [versions] # ordered alphabetically -antlr = "4.+" assertj = "3.+" checksumPlugin = "1.4.0" clikt = "5.+" @@ -50,8 +49,6 @@ spotlessPlugin = "6.25.0" wiremock = "3.+" [libraries] # ordered alphabetically -antlr = { group = "com.tunnelvisionlabs", name = "antlr4", version.ref = "antlr" } -antlrRuntime = { group = "com.tunnelvisionlabs", name = "antlr4-runtime", version.ref = "antlr" } assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" } clikt = { group = "com.github.ajalt.clikt", name = "clikt", version.ref = "clikt" } cliktMarkdown = { group = "com.github.ajalt.clikt", name = "clikt-markdown", version.ref = "clikt" } diff --git a/pkl-parser/gradle.lockfile b/pkl-parser/gradle.lockfile index 85926f0b..8b4938ec 100644 --- a/pkl-parser/gradle.lockfile +++ b/pkl-parser/gradle.lockfile @@ -2,13 +2,7 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.google.code.findbugs:jsr305:3.0.2=compileClasspath,compileOnlyDependenciesMetadata -com.tunnelvisionlabs:antlr4-annotations:4.9.0=antlr -com.tunnelvisionlabs:antlr4-runtime:4.9.0=antlr,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -com.tunnelvisionlabs:antlr4:4.9.0=antlr net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.abego.treelayout:org.abego.treelayout.core:1.0.1=antlr -org.antlr:ST4:4.3=antlr -org.antlr:antlr-runtime:3.5.2=antlr org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata org.assertj:assertj-core:3.27.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-parser/pkl-parser.gradle.kts b/pkl-parser/pkl-parser.gradle.kts index 6e0059b6..d7100c0a 100644 --- a/pkl-parser/pkl-parser.gradle.kts +++ b/pkl-parser/pkl-parser.gradle.kts @@ -17,33 +17,13 @@ plugins { pklAllProjects pklJavaLibrary pklPublishLibrary - antlr idea } -sourceSets { test { java { srcDir(file("testgenerated/antlr")) } } } - -idea { - module { - // mark src/test/antlr as source dir - // mark generated/antlr as generated source dir - generatedSourceDirs = generatedSourceDirs + files("testgenerated/antlr") - testSources.from(files("src/test/antlr", "testgenerated/antlr")) - } -} - -// workaround for https://github.com/gradle/gradle/issues/820 -configurations.api.get().let { apiConfig -> - apiConfig.setExtendsFrom(apiConfig.extendsFrom.filter { it.name != "antlr" }) -} - dependencies { compileOnly(libs.jsr305) - antlr(libs.antlr) - testImplementation(projects.pklCommonsTest) - testImplementation(libs.antlrRuntime) } publishing { @@ -56,30 +36,3 @@ publishing { } } } - -tasks.generateTestGrammarSource { - maxHeapSize = "64m" - - // generate only visitor - arguments = arguments + listOf("-visitor", "-no-listener") - - // Due to https://github.com/antlr/antlr4/issues/2260, - // we can't put .g4 files into src/test/antlr/org/pkl/parser/antlr. - // Instead, we put .g4 files into src/test/antlr, adapt output dir below, - // and use @header directives in .g4 files (instead of setting `-package` argument here) - // and task makeIntelliJAntlrPluginHappy to fix up the IDE story. - outputDirectory = file("testgenerated/antlr/org/pkl/parser/antlr") -} - -tasks.generateGrammarSource { enabled = false } - -tasks.compileTestKotlin { dependsOn(tasks.generateTestGrammarSource) } - -// Satisfy expectations of IntelliJ ANTLR plugin, -// which can't otherwise cope with our ANTLR setup. -val makeIntelliJAntlrPluginHappy by - tasks.registering(Copy::class) { - dependsOn(tasks.generateGrammarSource) - into("test/antlr") - from("testgenerated/antlr/org/pkl/parser/antlr") { include("PklLexer.tokens") } - } diff --git a/pkl-parser/src/test/antlr/PklLexer.g4 b/pkl-parser/src/test/antlr/PklLexer.g4 deleted file mode 100644 index c942b9b2..00000000 --- a/pkl-parser/src/test/antlr/PklLexer.g4 +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -lexer grammar PklLexer; - -@header { -package org.pkl.parser.antlr; -} - -@members { -class StringInterpolationScope { - int parenLevel = 0; - int poundLength = 0; -} - -java.util.Deque interpolationScopes = new java.util.ArrayDeque<>(); -StringInterpolationScope interpolationScope; - -{ pushInterpolationScope(); } - -void pushInterpolationScope() { - interpolationScope = new StringInterpolationScope(); - interpolationScopes.push(interpolationScope); -} - -void incParenLevel() { - interpolationScope.parenLevel += 1; -} - -void decParenLevel() { - if (interpolationScope.parenLevel == 0) { - // guard against syntax errors - if (interpolationScopes.size() > 1) { - interpolationScopes.pop(); - interpolationScope = interpolationScopes.peek(); - popMode(); - } - } else { - interpolationScope.parenLevel -= 1; - } -} - -boolean isPounds() { - // optimize for common cases (0, 1) - switch (interpolationScope.poundLength) { - case 0: return true; - case 1: return _input.LA(1) == '#'; - default: - int poundLength = interpolationScope.poundLength; - for (int i = 1; i <= poundLength; i++) { - if (_input.LA(i) != '#') return false; - } - return true; - } -} - -boolean isQuote() { - return _input.LA(1) == '"'; -} - -boolean endsWithPounds(String text) { - assert text.length() >= 2; - - // optimize for common cases (0, 1) - switch (interpolationScope.poundLength) { - case 0: return true; - case 1: return text.charAt(text.length() - 1) == '#'; - default: - int poundLength = interpolationScope.poundLength; - int textLength = text.length(); - if (textLength < poundLength) return false; - - int stop = textLength - poundLength; - for (int i = textLength - 1; i >= stop; i--) { - if (text.charAt(i) != '#') return false; - } - - return true; - } -} - -void removeBackTicks() { - String text = getText(); - setText(text.substring(1, text.length() - 1)); -} - -// look ahead in predicate rather than consume in grammar so that newlines -// go to NewlineSemicolonChannel, which is important for consumers of that channel -boolean isNewlineOrEof() { - int input = _input.LA(1); - return input == '\n' || input == '\r' || input == IntStream.EOF; -} - -} - -channels { - NewlineSemicolonChannel, - WhitespaceChannel, - CommentsChannel, - ShebangChannel -} - -ABSTRACT : 'abstract'; -AMENDS : 'amends'; -AS : 'as'; -CLASS : 'class'; -CONST : 'const'; -ELSE : 'else'; -EXTENDS : 'extends'; -EXTERNAL : 'external'; -FALSE : 'false'; -FIXED : 'fixed'; -FOR : 'for'; -FUNCTION : 'function'; -HIDDEN_ : 'hidden'; -IF : 'if'; -IMPORT : 'import'; -IMPORT_GLOB : 'import*'; -IN : 'in'; -IS : 'is'; -LET : 'let'; -LOCAL : 'local'; -MODULE : 'module'; -NEW : 'new'; -NOTHING : 'nothing'; -NULL : 'null'; -OPEN : 'open'; -OUT : 'out'; -OUTER : 'outer'; -READ : 'read'; -READ_GLOB : 'read*'; -READ_OR_NULL : 'read?'; -SUPER : 'super'; -THIS : 'this'; -THROW : 'throw'; -TRACE : 'trace'; -TRUE : 'true'; -TYPE_ALIAS : 'typealias'; -UNKNOWN : 'unknown'; -WHEN : 'when'; - -// reserved for future use, but not used today -PROTECTED : 'protected'; -OVERRIDE : 'override'; -RECORD : 'record'; -DELETE : 'delete'; -CASE : 'case'; -SWITCH : 'switch'; -VARARG : 'vararg'; - -LPAREN : '(' { incParenLevel(); }; -RPAREN : ')' { decParenLevel(); }; -LBRACE : '{'; -RBRACE : '}'; -LBRACK : '['; -RBRACK : ']'; -LPRED : '[['; // No RPRED, because that lexes too eager to allow nested index expressions, e.g. foo[bar[baz]] -COMMA : ','; -DOT : '.'; -QDOT : '?.'; -COALESCE : '??'; -NON_NULL : '!!'; - -AT : '@'; -ASSIGN : '='; -GT : '>'; -LT : '<'; -NOT : '!'; -QUESTION : '?'; -COLON : ':'; -ARROW : '->'; -EQUAL : '=='; -NOT_EQUAL : '!='; -LTE : '<='; -GTE : '>='; -AND : '&&'; -OR : '||'; -PLUS : '+'; -MINUS : '-'; -POW : '**'; -STAR : '*'; -DIV : '/'; -INT_DIV : '~/'; -MOD : '%'; -UNION : '|'; -PIPE : '|>'; -SPREAD : '...'; -QSPREAD : '...?'; -UNDERSCORE : '_'; - -SLQuote : '#'* '"' { interpolationScope.poundLength = getText().length() - 1; } -> pushMode(SLString); -MLQuote : '#'* '"""' { interpolationScope.poundLength = getText().length() - 3; } -> pushMode(MLString); - -IntLiteral - : DecimalLiteral - | HexadecimalLiteral - | BinaryLiteral - | OctalLiteral -; - -// leading zeros are allowed (cf. Swift) -fragment DecimalLiteral - : DecimalDigit DecimalDigitCharacters? - ; - -fragment DecimalDigitCharacters - : DecimalDigitCharacter+ - ; - -fragment DecimalDigitCharacter - : DecimalDigit - | '_' - ; - -fragment DecimalDigit - : [0-9] - ; - -fragment HexadecimalLiteral - : '0x' HexadecimalCharacter+ // intentionally allow underscore after '0x'; e.g. `0x_ab`. We will throw an error in AstBuilder. - ; - -fragment HexadecimalCharacter - : [0-9a-fA-F_] - ; - -fragment BinaryLiteral - : '0b' BinaryCharacter+ // intentionally allow underscore after '0b'; e.g. `0b_11`. We will throw an error in AstBuilder. - ; - -fragment BinaryCharacter - : [01_] - ; - -fragment OctalLiteral - : '0o' OctalCharacter+ // intentionally allow underscore after '0o'; e.g. `0o_34`. We will throw an error in AstBuilder. - ; - -fragment OctalCharacter - : [0-7_] - ; - -FloatLiteral - : DecimalLiteral? '.' '_'? DecimalLiteral Exponent? // intentionally allow underscore. We will throw an error in AstBuilder. - | DecimalLiteral Exponent - ; - -fragment Exponent - : [eE] [+-]? '_'? DecimalLiteral // intentionally allow underscore. We will throw an error in AstBuilder. - ; - -Identifier - : RegularIdentifier - | QuotedIdentifier { removeBackTicks(); } - ; - -// Note: Keep in sync with Lexer.isRegularIdentifier() -fragment RegularIdentifier - : IdentifierStart IdentifierPart* - ; - -fragment QuotedIdentifier - : '`' (~'`')+ '`' - ; - -fragment -IdentifierStart - : [a-zA-Z$_] // handle common cases without a predicate - | . {Character.isUnicodeIdentifierStart(_input.LA(-1))}? - ; - -fragment -IdentifierPart - : [a-zA-Z0-9$_] // handle common cases without a predicate - | . {Character.isUnicodeIdentifierPart(_input.LA(-1))}? - ; - -NewlineSemicolon - : [\r\n;]+ -> channel(NewlineSemicolonChannel) - ; - -// Note: Java, Scala, and Swift treat \f as whitespace; Dart doesn't. -// Python and C also include vertical tab. -// C# also includes Unicode class Zs (separator, space). -Whitespace - : [ \t\f]+ -> channel(WhitespaceChannel) - ; - -DocComment - : ([ \t\f]* '///' .*? (Newline|EOF))+ - ; - -BlockComment - : '/*' (BlockComment | .)*? '*/' -> channel(CommentsChannel) - ; - -LineComment - : '//' .*? {isNewlineOrEof()}? -> channel(CommentsChannel) - ; - -ShebangComment - : '#!' .*? {isNewlineOrEof()}? -> channel(ShebangChannel) - ; - -// strict: '\\' Pounds 'u{' HexDigit (HexDigit (HexDigit (HexDigit (HexDigit (HexDigit (HexDigit HexDigit? )?)?)?)?)?)? '}' -fragment UnicodeEscape - : '\\' Pounds 'u{' ~[}\r\n "]* '}'? - ; - -// strict: '\\' Pounds [tnr"\\] -fragment CharacterEscape - : '\\' Pounds . - ; - -fragment Pounds - : { interpolationScope.poundLength == 0 }? - | '#' { interpolationScope.poundLength == 1 }? - | '#'+ { endsWithPounds(getText()) }? - ; - -fragment Newline - : '\n' | '\r' '\n'? - ; - -mode SLString; - -// strict: '"' Pounds -SLEndQuote - : ('"' Pounds | Newline ) -> popMode - ; - -SLInterpolation - : '\\' Pounds '(' { pushInterpolationScope(); } -> pushMode(DEFAULT_MODE) - ; - -SLUnicodeEscape - : UnicodeEscape - ; - -SLCharacterEscape - : CharacterEscape - ; - -SLCharacters - : ~["\\\r\n]+ SLCharacters? - | ["\\] {!isPounds()}? SLCharacters? - ; - -mode MLString; - -MLEndQuote - : '"""' Pounds -> popMode - ; - -MLInterpolation - : '\\' Pounds '(' { pushInterpolationScope(); } -> pushMode(DEFAULT_MODE) - ; - -MLUnicodeEscape - : UnicodeEscape - ; - -MLCharacterEscape - : CharacterEscape - ; - -MLNewline - : Newline - ; - -MLCharacters - : ~["\\\r\n]+ MLCharacters? - | ('\\' | '"""') {!isPounds()}? MLCharacters? - | '"' '"'? {!isQuote()}? MLCharacters? - ; diff --git a/pkl-parser/src/test/antlr/PklParser.g4 b/pkl-parser/src/test/antlr/PklParser.g4 deleted file mode 100644 index b4ef1d79..00000000 --- a/pkl-parser/src/test/antlr/PklParser.g4 +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -parser grammar PklParser; - -@header { -package org.pkl.parser.antlr; -} - -@members { -/** - * Returns true if and only if the next token to be consumed is not preceded by a newline or semicolon. - */ -boolean noNewlineOrSemicolon() { - for (int i = _input.index() - 1; i >= 0; i--) { - Token token = _input.get(i); - int channel = token.getChannel(); - if (channel == PklLexer.DEFAULT_TOKEN_CHANNEL) return true; - if (channel == PklLexer.NewlineSemicolonChannel) return false; - } - return true; -} -} - -options { - tokenVocab = PklLexer; -} - -replInput - : ((moduleDecl - | importClause - | clazz - | typeAlias - | classProperty - | classMethod - | expr))* EOF - ; - -exprInput - : expr EOF - ; - -module - : moduleDecl? (is+=importClause)* ((cs+=clazz | ts+=typeAlias | ps+=classProperty | ms+=classMethod))* EOF - ; - -moduleDecl - : t=DocComment? annotation* moduleHeader - ; - -moduleHeader - : modifier* 'module' qualifiedIdentifier moduleExtendsOrAmendsClause? - | moduleExtendsOrAmendsClause - ; - -moduleExtendsOrAmendsClause - : t=('extends' | 'amends') stringConstant - ; - -importClause - : t=('import' | 'import*') stringConstant ('as' Identifier)? - ; - -clazz - : t=DocComment? annotation* classHeader classBody? - ; - -classHeader - : modifier* 'class' Identifier typeParameterList? ('extends' type)? - ; - -modifier - : t=('external' | 'abstract' | 'open' | 'local' | 'hidden' | 'fixed' | 'const') - ; - -classBody - : '{' ((ps+=classProperty | ms+=classMethod))* err='}'? - ; - -typeAlias - : t=DocComment? annotation* typeAliasHeader '=' type - ; - -typeAliasHeader - : modifier* 'typealias' Identifier typeParameterList? - ; - -// allows `foo: Bar { ... }` s.t. AstBuilder can provide better error message -classProperty - : t=DocComment? annotation* modifier* Identifier (typeAnnotation | typeAnnotation? ('=' expr | objectBody+)) - ; - -classMethod - : t=DocComment? annotation* methodHeader ('=' expr)? - ; - -methodHeader - : modifier* 'function' Identifier typeParameterList? parameterList typeAnnotation? - ; - -parameterList - : '(' (ts+=parameter (errs+=','? ts+=parameter)*)? err=')'? - ; - -argumentList - : {noNewlineOrSemicolon()}? '(' (es+=expr (errs+=','? es+=expr)*)? err=')'? - ; - -annotation - : '@' type objectBody? - ; - -qualifiedIdentifier - : ts+=Identifier ('.' ts+=Identifier)* - ; - -typeAnnotation - : ':' type - ; - -typeParameterList - : '<' ts+=typeParameter (errs+=','? ts+=typeParameter)* err='>'? - ; - -typeParameter - : t=('in' | 'out')? Identifier - ; - -typeArgumentList - : '<' ts+=type (errs+=','? ts+=type)* err='>'? - ; - -type - : 'unknown' # unknownType - | 'nothing' # nothingType - | 'module' # moduleType - | stringConstant # stringLiteralType - | qualifiedIdentifier typeArgumentList? # declaredType - | '(' type err=')'? # parenthesizedType - | type '?' # nullableType - | type {noNewlineOrSemicolon()}? t='(' es+=expr (errs+=','? es+=expr)* err=')'? # constrainedType - | '*' u=type # defaultUnionType - | l=type '|' r=type # unionType - | t='(' (ps+=type (errs+=','? ps+=type)*)? err=')'? '->' r=type # functionType - ; - -typedIdentifier - : Identifier typeAnnotation? - ; - -parameter - : '_' - | typedIdentifier - ; - -// Many languages (e.g., Python) give `**` higher precedence than unary minus. -// The reason is that in Math, `-a^2` means `-(a^2)`. -// To avoid confusion, JS rejects `-a**2` and requires explicit parens. -// `-3.abs()` is a similar problem, handled differently by different languages. -expr - : 'this' # thisExpr - | 'outer' # outerExpr - | 'module' # moduleExpr - | 'null' # nullLiteral - | 'true' # trueLiteral - | 'false' # falseLiteral - | IntLiteral # intLiteral - | FloatLiteral # floatLiteral - | 'throw' '(' expr err=')'? # throwExpr - | 'trace' '(' expr err=')'? # traceExpr - | t=('import' | 'import*') '(' stringConstant err=')'? # importExpr - | t=('read' | 'read?' | 'read*') '(' expr err=')'? # readExpr - | Identifier argumentList? # unqualifiedAccessExpr - | t=SLQuote singleLineStringPart* t2=SLEndQuote # singleLineStringLiteral - | t=MLQuote multiLineStringPart* t2=MLEndQuote # multiLineStringLiteral - | t='new' type? objectBody # newExpr - | expr objectBody # amendExpr - | 'super' '.' Identifier argumentList? # superAccessExpr - | 'super' t='[' e=expr err=']'? # superSubscriptExpr - | expr t=('.' | '?.') Identifier argumentList? # qualifiedAccessExpr - | l=expr {noNewlineOrSemicolon()}? t='[' r=expr err=']'? # subscriptExpr - | expr '!!' # nonNullExpr - | '-' expr # unaryMinusExpr - | '!' expr # logicalNotExpr - | l=expr t='**' r=expr # exponentiationExpr - // for some reason, moving rhs of rules starting with `l=expr` into a - // separate rule (to avoid repeated parsing of `expr`) messes up precedence - | l=expr t=('*' | '/' | '~/' | '%') r=expr # multiplicativeExpr - | l=expr (t='+' | {noNewlineOrSemicolon()}? t='-') r=expr # additiveExpr - | l=expr t=('<' | '>' | '<=' | '>=') r=expr # comparisonExpr - | l=expr t=('is' | 'as') r=type # typeTestExpr - | l=expr t=('==' | '!=') r=expr # equalityExpr - | l=expr t='&&' r=expr # logicalAndExpr - | l=expr t='||' r=expr # logicalOrExpr - | l=expr t='|>' r=expr # pipeExpr - | l=expr t='??' r=expr # nullCoalesceExpr - | 'if' '(' c=expr err=')'? l=expr 'else' r=expr # ifExpr - | 'let' '(' parameter '=' l=expr err=')'? r=expr # letExpr - | parameterList '->' expr # functionLiteral - | '(' expr err=')'? # parenthesizedExpr - ; - -objectBody - : '{' (ps+=parameter (errs+=','? ps+=parameter)* '->')? objectMember* err='}'? - ; - -objectMember - : modifier* Identifier (typeAnnotation? '=' expr | objectBody+) # objectProperty - | methodHeader '=' expr # objectMethod - | t='[[' k=expr err1=']'? err2=']'? ('=' v=expr | objectBody+) # memberPredicate - | t='[' k=expr err1=']'? err2=']'? ('=' v=expr | objectBody+) # objectEntry - | expr # objectElement - | ('...' | '...?') expr # objectSpread - | 'when' '(' e=expr err=')'? (b1=objectBody ('else' b2=objectBody)?) # whenGenerator - | 'for' '(' t1=parameter (',' t2=parameter)? 'in' e=expr err=')'? objectBody # forGenerator - ; - -stringConstant - : t=SLQuote (ts+=SLCharacters | ts+=SLCharacterEscape | ts+=SLUnicodeEscape)* t2=SLEndQuote - ; - -singleLineStringPart - : SLInterpolation e=expr ')' - | (ts+=SLCharacters | ts+=SLCharacterEscape | ts+=SLUnicodeEscape)+ - ; - -multiLineStringPart - : MLInterpolation e=expr ')' - | (ts+=MLCharacters | ts+=MLNewline | ts+=MLCharacterEscape | ts+=MLUnicodeEscape)+ - ; - -// intentionally unused -//TODO: we get a "Mismatched Input" error unless we introduce this parser rule. Why? -reservedKeyword - : 'protected' - | 'override' - | 'record' - | 'delete' - | 'case' - | 'switch' - | 'vararg' - ; diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/ANTLRSexpRenderer.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/ANTLRSexpRenderer.kt deleted file mode 100644 index 06c05acc..00000000 --- a/pkl-parser/src/test/kotlin/org/pkl/parser/ANTLRSexpRenderer.kt +++ /dev/null @@ -1,1067 +0,0 @@ -/* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pkl.parser - -import org.antlr.v4.runtime.ParserRuleContext -import org.pkl.parser.antlr.PklLexer -import org.pkl.parser.antlr.PklParser.* - -@Suppress("MemberVisibilityCanBePrivate") -class ANTLRSexpRenderer { - private var tab = "" - private var buf = StringBuilder() - - fun render(mod: ModuleContext): String { - renderModule(mod) - val res = buf.toString() - reset() - return res - } - - fun renderModule(mod: ModuleContext) { - buf.append(tab) - buf.append("(module") - val oldTab = increaseTab() - if (mod.moduleDecl() != null) { - buf.append('\n') - renderModuleDeclaration(mod.moduleDecl()) - } - for (imp in mod.importClause()) { - buf.append('\n') - renderImport(imp) - } - for (entry in sortModuleEntries(mod)) { - buf.append('\n') - when (entry) { - is ClazzContext -> renderClass(entry) - is TypeAliasContext -> renderTypeAlias(entry) - is ClassPropertyContext -> renderClassProperty(entry) - is ClassMethodContext -> renderClassMethod(entry) - } - } - tab = oldTab - buf.append(')') - } - - fun renderModuleDeclaration(decl: ModuleDeclContext) { - buf.append(tab) - buf.append("(moduleHeader") - val oldTab = increaseTab() - if (decl.DocComment() != null) { - buf.append('\n') - renderDocComment() - } - for (ann in decl.annotation()) { - buf.append('\n') - renderAnnotation(ann) - } - val header = decl.moduleHeader() - if (header != null) { - for (modifier in header.modifier()) { - buf.append('\n') - renderModifier(modifier) - } - if (header.qualifiedIdentifier() != null) { - buf.append('\n') - renderQualifiedIdent(header.qualifiedIdentifier()) - } - if (header.moduleExtendsOrAmendsClause() != null) { - buf.append('\n') - buf.append(tab) - buf.append("(extendsOrAmendsClause)") - } - } - tab = oldTab - buf.append(')') - } - - fun renderImport(imp: ImportClauseContext) { - buf.append(tab) - if (imp.t.type == PklLexer.IMPORT_GLOB) { - buf.append("(importGlobClause") - } else { - buf.append("(importClause") - } - val oldTab = increaseTab() - if (imp.Identifier() != null) { - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - } - buf.append(')') - tab = oldTab - } - - fun renderClass(clazz: ClazzContext) { - buf.append(tab) - buf.append("(clazz") - val oldTab = increaseTab() - if (clazz.DocComment() != null) { - buf.append('\n') - renderDocComment() - } - for (ann in clazz.annotation()) { - buf.append('\n') - renderAnnotation(ann) - } - val header = clazz.classHeader() - for (mod in header.modifier()) { - buf.append('\n') - renderModifier(mod) - } - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - val typePars = header.typeParameterList() - if (typePars != null) { - buf.append('\n') - renderTypeParameterList(typePars) - } - if (header.type() != null) { - buf.append('\n') - renderType(header.type()) - } - val body = clazz.classBody() - if (body != null) { - buf.append('\n') - renderClassBody(body) - } - buf.append(')') - tab = oldTab - } - - fun renderClassBody(body: ClassBodyContext) { - buf.append(tab) - buf.append("(classBody") - val oldTab = increaseTab() - for (entry in sortClassEntries(body)) { - buf.append('\n') - when (entry) { - is ClassPropertyContext -> renderClassProperty(entry) - is ClassMethodContext -> renderClassMethod(entry) - } - } - buf.append(')') - tab = oldTab - } - - fun renderTypeAlias(`typealias`: TypeAliasContext) { - buf.append(tab) - buf.append("(typeAlias") - val oldTab = increaseTab() - if (`typealias`.DocComment() != null) { - buf.append('\n') - renderDocComment() - } - for (ann in `typealias`.annotation()) { - buf.append('\n') - renderAnnotation(ann) - } - val header = `typealias`.typeAliasHeader() - for (mod in header.modifier()) { - buf.append('\n') - renderModifier(mod) - } - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - val typePars = header.typeParameterList() - if (typePars != null) { - renderTypeParameterList(typePars) - } - buf.append('\n') - renderType(`typealias`.type()) - buf.append(')') - tab = oldTab - } - - fun renderClassProperty(classProperty: ClassPropertyContext) { - buf.append(tab) - buf.append("(classProperty") - val oldTab = increaseTab() - if (classProperty.DocComment() != null) { - buf.append('\n') - renderDocComment() - } - for (ann in classProperty.annotation()) { - buf.append('\n') - renderAnnotation(ann) - } - for (mod in classProperty.modifier()) { - buf.append('\n') - renderModifier(mod) - } - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - if (classProperty.typeAnnotation() != null) { - buf.append('\n') - renderTypeAnnotation(classProperty.typeAnnotation()) - } - if (classProperty.expr() != null) { - buf.append('\n') - renderExpr(classProperty.expr()) - } - if (classProperty.objectBody() != null) { - for (body in classProperty.objectBody()) { - buf.append('\n') - renderObjectBody(body) - } - } - buf.append(')') - tab = oldTab - } - - fun renderClassMethod(classMethod: ClassMethodContext) { - buf.append(tab) - buf.append("(classMethod") - val oldTab = increaseTab() - if (classMethod.DocComment() != null) { - buf.append('\n') - renderDocComment() - } - for (ann in classMethod.annotation()) { - buf.append('\n') - renderAnnotation(ann) - } - val header = classMethod.methodHeader() - for (mod in header.modifier()) { - buf.append('\n') - renderModifier(mod) - } - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - if (header.typeParameterList() != null) { - renderTypeParameterList(header.typeParameterList()) - } - buf.append('\n') - renderParameterList(header.parameterList()) - if (header.typeAnnotation() != null) { - buf.append('\n') - renderTypeAnnotation(header.typeAnnotation()) - } - if (classMethod.expr() != null) { - buf.append('\n') - renderExpr(classMethod.expr()) - } - buf.append(')') - tab = oldTab - } - - fun renderAnnotation(ann: AnnotationContext) { - buf.append(tab) - buf.append("(annotation") - val oldTab = increaseTab() - buf.append('\n') - renderType(ann.type()) - if (ann.objectBody() != null) { - buf.append('\n') - renderObjectBody(ann.objectBody()) - } - buf.append(')') - tab = oldTab - } - - fun renderQualifiedIdent(name: QualifiedIdentifierContext) { - buf.append(tab) - buf.append("(qualifiedIdentifier") - val oldTab = increaseTab() - for (i in name.Identifier().indices) { - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - } - buf.append(')') - tab = oldTab - } - - fun renderTypeParameterList(typeParameterList: TypeParameterListContext) { - buf.append(tab) - buf.append("(TypeParameterList\n") - val oldTab = increaseTab() - for (tpar in typeParameterList.typeParameter()) { - buf.append('\n') - renderTypeParameter(tpar) - } - tab = oldTab - } - - @Suppress("UNUSED_PARAMETER") - fun renderTypeParameter(tpar: TypeParameterContext?) { - buf.append(tab) - buf.append("(TypeParameter\n") - val oldTab = increaseTab() - buf.append(tab) - buf.append("(identifier))") - tab = oldTab - } - - fun renderTypeAnnotation(typeAnnotation: TypeAnnotationContext) { - buf.append(tab) - buf.append("(typeAnnotation") - val oldTab = increaseTab() - buf.append('\n') - renderType(typeAnnotation.type()) - buf.append(')') - tab = oldTab - } - - fun renderType(type: TypeContext?) { - when (type) { - is UnknownTypeContext -> { - buf.append(tab) - buf.append("(unknownType)") - } - is NothingTypeContext -> { - buf.append(tab) - buf.append("(nothingType)") - } - is ModuleTypeContext -> { - buf.append(tab) - buf.append("(moduleType)") - } - is StringLiteralTypeContext -> { - buf.append(tab) - buf.append("(stringConstantType)") - } - is DeclaredTypeContext -> renderDeclaredType(type) - is ParenthesizedTypeContext -> renderParenthesizedType(type) - is NullableTypeContext -> renderNullableType(type) - is ConstrainedTypeContext -> renderConstrainedType(type) - is DefaultUnionTypeContext -> renderDefaultUnionType(type) - is UnionTypeContext -> renderUnionType(type) - is FunctionTypeContext -> renderFunctionType(type) - } - } - - fun renderDeclaredType(type: DeclaredTypeContext) { - buf.append(tab) - buf.append("(declaredType") - val oldTab = increaseTab() - buf.append('\n') - renderQualifiedIdent(type.qualifiedIdentifier()) - val args = type.typeArgumentList() - if (args != null) { - buf.append('\n') - renderTypeArgumentList(args) - } - buf.append(')') - tab = oldTab - } - - fun renderTypeArgumentList(ctx: TypeArgumentListContext) { - buf.append(tab) - buf.append("(typeArgumentList") - val oldTab = increaseTab() - for (arg in ctx.type()) { - buf.append('\n') - renderType(arg) - } - buf.append(')') - tab = oldTab - } - - fun renderParenthesizedType(type: ParenthesizedTypeContext) { - buf.append(tab) - buf.append("(parenthesisedType") - val oldTab = increaseTab() - buf.append('\n') - renderType(type.type()) - buf.append(')') - tab = oldTab - } - - fun renderNullableType(type: NullableTypeContext) { - buf.append(tab) - buf.append("(nullableType") - val oldTab = increaseTab() - buf.append('\n') - renderType(type.type()) - buf.append(')') - tab = oldTab - } - - fun renderConstrainedType(type: ConstrainedTypeContext) { - buf.append(tab) - buf.append("(constrainedType") - val oldTab = increaseTab() - buf.append('\n') - renderType(type.type()) - for (expr in type.expr()) { - buf.append('\n') - renderExpr(expr) - } - buf.append(')') - tab = oldTab - } - - fun renderDefaultUnionType(type: DefaultUnionTypeContext) { - buf.append(tab) - buf.append("(defaultUnionType") - val oldTab = increaseTab() - buf.append('\n') - renderType(type.type()) - buf.append(')') - tab = oldTab - } - - fun renderUnionType(type: UnionTypeContext) { - buf.append(tab) - buf.append("(unionType") - val oldTab = increaseTab() - val types = flattenUnion(type) - for (typ in types) { - buf.append('\n') - renderType(typ) - } - buf.append(')') - tab = oldTab - } - - fun renderFunctionType(type: FunctionTypeContext) { - buf.append(tab) - buf.append("(functionType") - val oldTab = increaseTab() - for (arg in type.ps) { - buf.append('\n') - renderType(arg) - } - buf.append('\n') - renderType(type.r) - buf.append(')') - tab = oldTab - } - - fun renderParameterList(parList: ParameterListContext) { - buf.append(tab) - buf.append("(parameterList") - val oldTab = increaseTab() - for (par in parList.parameter()) { - buf.append('\n') - renderParameter(par) - } - buf.append(')') - tab = oldTab - } - - fun renderParameter(par: ParameterContext) { - buf.append(tab) - buf.append("(parameter") - val oldTab = increaseTab() - val typedIdent = par.typedIdentifier() - if (typedIdent != null) { - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - if (typedIdent.typeAnnotation() != null) { - buf.append('\n') - renderTypeAnnotation(typedIdent.typeAnnotation()) - } - } - buf.append(')') - tab = oldTab - } - - fun renderObjectBody(body: ObjectBodyContext) { - buf.append(tab) - buf.append("(objectBody") - val oldTab = increaseTab() - for (par in body.parameter()) { - buf.append('\n') - renderParameter(par) - } - for (member in body.objectMember()) { - buf.append('\n') - renderMember(member) - } - buf.append(')') - tab = oldTab - } - - fun renderMember(member: ObjectMemberContext) { - when (member) { - is ObjectPropertyContext -> renderObjectProperty(member) - is ObjectMethodContext -> renderObjectMethod(member) - is MemberPredicateContext -> renderMemberPredicate(member) - is ObjectEntryContext -> renderObjectEntry(member) - is ObjectElementContext -> renderObjectElement(member) - is ObjectSpreadContext -> renderObjectSpread(member) - is WhenGeneratorContext -> renderWhenGenerator(member) - is ForGeneratorContext -> renderForGenerator(member) - } - } - - fun renderObjectElement(element: ObjectElementContext) { - buf.append(tab) - buf.append("(objectElement") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(element.expr()) - buf.append(')') - tab = oldTab - } - - fun renderObjectProperty(property: ObjectPropertyContext) { - buf.append(tab) - buf.append("(objectProperty") - val oldTab = increaseTab() - for (mod in property.modifier()) { - buf.append('\n') - renderModifier(mod) - } - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - val typeAnn = property.typeAnnotation() - if (typeAnn != null) { - buf.append('\n') - renderTypeAnnotation(typeAnn) - } - if (property.expr() != null) { - buf.append('\n') - renderExpr(property.expr()) - } - if (property.objectBody() != null) { - for (body in property.objectBody()) { - buf.append('\n') - renderObjectBody(body) - } - } - buf.append(')') - tab = oldTab - } - - fun renderObjectMethod(method: ObjectMethodContext) { - buf.append(tab) - buf.append("(objectMethod") - val oldTab = increaseTab() - buf.append('\n') - val header = method.methodHeader() - for (mod in header.modifier()) { - buf.append('\n') - renderModifier(mod) - } - buf.append('\n') - buf.append("(identifier)") - if (header.typeParameterList() != null) { - renderTypeParameterList(header.typeParameterList()) - } - buf.append('\n') - renderParameterList(header.parameterList()) - val typeAnn = header.typeAnnotation() - if (typeAnn != null) { - buf.append('\n') - renderTypeAnnotation(typeAnn) - } - buf.append('\n') - renderExpr(method.expr()) - buf.append(')') - tab = oldTab - } - - fun renderMemberPredicate(predicate: MemberPredicateContext) { - buf.append(tab) - buf.append("(memberPredicate") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(predicate.k) - if (predicate.v != null) { - buf.append('\n') - renderExpr(predicate.v) - } - if (predicate.objectBody() != null) { - for (body in predicate.objectBody()) { - buf.append('\n') - renderObjectBody(body) - } - } - buf.append(')') - tab = oldTab - } - - fun renderObjectEntry(entry: ObjectEntryContext) { - buf.append(tab) - buf.append("(objectEntry") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(entry.k) - if (entry.v != null) { - buf.append('\n') - renderExpr(entry.v) - } - if (entry.objectBody() != null) { - for (body in entry.objectBody()) { - buf.append('\n') - renderObjectBody(body) - } - } - buf.append(')') - tab = oldTab - } - - fun renderObjectSpread(spread: ObjectSpreadContext) { - buf.append(tab) - buf.append("(objectSpread") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(spread.expr()) - buf.append(')') - tab = oldTab - } - - fun renderWhenGenerator(generator: WhenGeneratorContext) { - buf.append(tab) - buf.append("(whenGenerator") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(generator.expr()) - buf.append('\n') - renderObjectBody(generator.b1) - if (generator.b2 != null) { - buf.append('\n') - renderObjectBody(generator.b2) - } - buf.append(')') - tab = oldTab - } - - fun renderForGenerator(generator: ForGeneratorContext) { - buf.append(tab) - buf.append("(forGenerator") - val oldTab = increaseTab() - buf.append('\n') - renderParameter(generator.t1) - if (generator.t2 != null) { - buf.append('\n') - renderParameter(generator.t2) - } - buf.append('\n') - renderExpr(generator.expr()) - buf.append('\n') - renderObjectBody(generator.objectBody()) - buf.append(')') - tab = oldTab - } - - fun renderArgumentList(argumentList: ArgumentListContext) { - buf.append(tab) - buf.append("(argumentList") - val oldTab = increaseTab() - for (arg in argumentList.expr()) { - buf.append('\n') - renderExpr(arg) - } - buf.append(')') - tab = oldTab - } - - fun renderExpr(expr: ExprContext) { - when (expr) { - is ThisExprContext -> { - buf.append(tab) - buf.append("(thisExpr)") - } - is OuterExprContext -> { - buf.append(tab) - buf.append("(outerExpr)") - } - is ModuleExprContext -> { - buf.append(tab) - buf.append("(moduleExpr)") - } - is NullLiteralContext -> { - buf.append(tab) - buf.append("(nullExpr)") - } - is TrueLiteralContext, - is FalseLiteralContext -> { - buf.append(tab) - buf.append("(boolLiteralExpr)") - } - is IntLiteralContext -> { - buf.append(tab) - buf.append("(intLiteralExpr)") - } - is FloatLiteralContext -> { - buf.append(tab) - buf.append("(floatLiteralExpr)") - } - is ThrowExprContext -> renderThrowExpr(expr) - is TraceExprContext -> renderTraceExpr(expr) - is ImportExprContext -> { - buf.append(tab) - val name = if (expr.t.type == PklLexer.IMPORT_GLOB) "(importGlobExpr)" else "(importExpr)" - buf.append(name) - } - is ReadExprContext -> renderReadExpr(expr) - is UnqualifiedAccessExprContext -> renderUnqualifiedAccessExpr(expr) - is SingleLineStringLiteralContext -> renderSingleLineStringExpr(expr) - is MultiLineStringLiteralContext -> renderMultiLineStringExpr(expr) - is NewExprContext -> renderNewExpr(expr) - is AmendExprContext -> renderAmendsExpr(expr) - is SuperAccessExprContext -> renderSuperAccessExpr(expr) - is SuperSubscriptExprContext -> renderSuperSubscriptExpr(expr) - is QualifiedAccessExprContext -> renderQualifiedAccessExpr(expr) - is SubscriptExprContext -> renderSubscriptExpr(expr) - is NonNullExprContext -> renderNonNullExpr(expr) - is UnaryMinusExprContext -> renderUnaryMinusExpr(expr) - is LogicalNotExprContext -> renderLogicalNotExpr(expr) - is ExponentiationExprContext -> renderBinaryOpExpr("exponentiationExpr", expr.expr()) - is MultiplicativeExprContext -> renderBinaryOpExpr("multiplicativeExpr", expr.expr()) - is AdditiveExprContext -> renderBinaryOpExpr("additiveExpr", expr.expr()) - is ComparisonExprContext -> renderBinaryOpExpr("comparisonExpr", expr.expr()) - is TypeTestExprContext -> renderTypeTestExpr(expr) - is EqualityExprContext -> renderBinaryOpExpr("equalityExpr", expr.expr()) - is LogicalAndExprContext -> renderBinaryOpExpr("logicalAndExpr", expr.expr()) - is LogicalOrExprContext -> renderBinaryOpExpr("logicalOrExpr", expr.expr()) - is PipeExprContext -> renderBinaryOpExpr("pipeExpr", expr.expr()) - is NullCoalesceExprContext -> renderBinaryOpExpr("nullCoalesceExpr", expr.expr()) - is IfExprContext -> renderIfExpr(expr) - is LetExprContext -> renderLetExpr(expr) - is FunctionLiteralContext -> renderFunctionLiteralExpr(expr) - is ParenthesizedExprContext -> renderParenthesisedExpr(expr) - } - } - - fun renderThrowExpr(expr: ThrowExprContext) { - buf.append(tab) - buf.append("(throwExpr") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - fun renderTraceExpr(expr: TraceExprContext) { - buf.append(tab) - buf.append("(traceExpr") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - fun renderReadExpr(expr: ReadExprContext) { - buf.append(tab) - var name = "(readExpr" - if (expr.t.type == PklLexer.READ_GLOB) { - name = "(readGlobExpr" - } else if (expr.t.type == PklLexer.READ_OR_NULL) { - name = "(readNullExpr" - } - buf.append(name) - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - fun renderUnqualifiedAccessExpr(expr: UnqualifiedAccessExprContext) { - buf.append(tab) - buf.append("(unqualifiedAccessExpr") - val oldTab = increaseTab() - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - val args = expr.argumentList() - if (args != null) { - buf.append('\n') - renderArgumentList(args) - } - buf.append(')') - tab = oldTab - } - - fun renderSingleLineStringExpr(expr: SingleLineStringLiteralContext) { - buf.append(tab) - buf.append("(singleLineStringLiteralExpr") - val oldTab = increaseTab() - for (part in expr.singleLineStringPart()) { - if (part.expr() != null) { - buf.append('\n') - renderExpr(part.expr()) - } else { - buf.append('\n').append(tab) - buf.append("(stringConstantExpr)") - } - } - buf.append(')') - tab = oldTab - } - - fun renderMultiLineStringExpr(expr: MultiLineStringLiteralContext) { - buf.append(tab) - buf.append("(multiLineStringLiteralExpr") - val oldTab = increaseTab() - // render only interpolated expressions because - // the new parser parses string differently - for (part in expr.multiLineStringPart()) { - if (part.expr() != null) { - buf.append('\n') - renderExpr(part.expr()) - } - } - buf.append(')') - tab = oldTab - } - - fun renderNewExpr(expr: NewExprContext) { - buf.append(tab) - buf.append("(newExpr") - val oldTab = increaseTab() - if (expr.type() != null) { - buf.append('\n') - renderType(expr.type()) - } - buf.append('\n') - renderObjectBody(expr.objectBody()) - buf.append(')') - tab = oldTab - } - - fun renderAmendsExpr(expr: AmendExprContext) { - buf.append(tab) - buf.append("(amendsExpr") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append('\n') - renderObjectBody(expr.objectBody()) - buf.append(')') - tab = oldTab - } - - fun renderSuperAccessExpr(expr: SuperAccessExprContext) { - buf.append(tab) - buf.append("(superAccessExpr") - val oldTab = increaseTab() - buf.append('\n') - buf.append("(identifier)") - val args = expr.argumentList() - if (args != null) { - buf.append('\n') - renderArgumentList(args) - } - buf.append(')') - tab = oldTab - } - - fun renderSuperSubscriptExpr(expr: SuperSubscriptExprContext) { - buf.append(tab) - buf.append("(superSubscriptExpr") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - fun renderQualifiedAccessExpr(expr: QualifiedAccessExprContext) { - buf.append(tab) - val name = - if (expr.t.type == PklLexer.QDOT) "(nullableQualifiedAccessExpr" else "(qualifiedAccessExpr" - buf.append(name) - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append('\n') - buf.append(tab) - buf.append("(identifier)") - val args = expr.argumentList() - if (args != null) { - buf.append('\n') - renderArgumentList(args) - } - buf.append(')') - tab = oldTab - } - - fun renderSubscriptExpr(expr: SubscriptExprContext) { - buf.append(tab) - buf.append("(subscriptExpr") - val oldTab = increaseTab() - for (e in expr.expr()) { - buf.append('\n') - renderExpr(e) - } - buf.append(')') - tab = oldTab - } - - fun renderNonNullExpr(expr: NonNullExprContext) { - buf.append(tab) - buf.append("(nonNullExpr") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - fun renderUnaryMinusExpr(expr: UnaryMinusExprContext) { - buf.append(tab) - buf.append("(unaryMinusExpr") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - fun renderLogicalNotExpr(expr: LogicalNotExprContext) { - buf.append(tab) - buf.append("(logicalNotExpr") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - fun renderBinaryOpExpr(name: String?, exprs: List) { - buf.append(tab) - buf.append("(").append(name) - val oldTab = increaseTab() - for (expr in exprs) { - buf.append('\n') - renderExpr(expr) - } - buf.append(')') - tab = oldTab - } - - fun renderTypeTestExpr(expr: TypeTestExprContext) { - buf.append(tab) - val name = if (expr.t.type == PklLexer.IS) "(typeCheckExpr" else "(typeCastExpr" - buf.append(name) - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append('\n') - renderType(expr.type()) - buf.append(')') - tab = oldTab - } - - fun renderIfExpr(expr: IfExprContext) { - buf.append(tab) - buf.append("(ifExpr") - val oldTab = increaseTab() - for (e in expr.expr()) { - buf.append('\n') - renderExpr(e) - } - buf.append(')') - tab = oldTab - } - - fun renderLetExpr(expr: LetExprContext) { - buf.append(tab) - buf.append("(letExpr") - val oldTab = increaseTab() - buf.append('\n') - renderParameter(expr.parameter()) - for (e in expr.expr()) { - buf.append('\n') - renderExpr(e) - } - buf.append(')') - tab = oldTab - } - - fun renderFunctionLiteralExpr(expr: FunctionLiteralContext) { - buf.append(tab) - buf.append("(functionLiteralExpr") - val oldTab = increaseTab() - buf.append('\n') - renderParameterList(expr.parameterList()) - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - fun renderParenthesisedExpr(expr: ParenthesizedExprContext) { - buf.append(tab) - buf.append("(parenthesizedExpr") - val oldTab = increaseTab() - buf.append('\n') - renderExpr(expr.expr()) - buf.append(')') - tab = oldTab - } - - @Suppress("UNUSED_PARAMETER") - fun renderModifier(mod: ModifierContext?) { - buf.append(tab) - buf.append("(modifier)") - } - - fun renderDocComment() { - buf.append(tab) - buf.append("(docComment)") - } - - fun reset() { - tab = "" - buf = StringBuilder() - } - - private fun increaseTab(): String { - val old = tab - tab += " " - return old - } - - companion object { - private fun sortModuleEntries(mod: ModuleContext): List { - val res = mutableListOf() - res += mod.clazz() - res += mod.typeAlias() - res += mod.classProperty() - res += mod.classMethod() - res.sortWith(compareBy { it.sourceInterval.a }) - return res - } - - private fun sortClassEntries(body: ClassBodyContext): List { - val res = mutableListOf() - res += body.classProperty() - res += body.classMethod() - res.sortWith(compareBy { it.sourceInterval.a }) - return res - } - - fun flattenUnion(type: UnionTypeContext): List { - val types = mutableListOf() - val toCheck = mutableListOf(type.l, type.r) - while (toCheck.isNotEmpty()) { - val typ = toCheck.removeAt(0) - if (typ is UnionTypeContext) { - toCheck.add(0, typ.r) - toCheck.add(0, typ.l) - } else { - types += typ - } - } - return types - } - } -} diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt deleted file mode 100644 index 41ef3e64..00000000 --- a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pkl.parser - -import java.nio.file.Path -import kotlin.io.path.Path -import kotlin.io.path.extension -import org.junit.jupiter.api.Test -import org.pkl.commons.walk - -class ParserComparisonTest : ParserComparisonTestInterface { - - @Test - fun testAsPrecedence() { - compare("prop = 3 + bar as Int * 5") - } - - @Test - fun testStringInterpolation() { - compare( - """ - prop = "\(bar)" - prop2 = "foo \(bar)" - prop3 = "\(bar) foo" - prop4 = "foo \(bar + baz) foo" - """ - .trimIndent() - ) - - compare( - """ - prop = ""${'"'} - \(bar) - ""${'"'} - prop2 = ""${'"'} - foo \(bar) - ""${'"'} - prop3 = ""${'"'} - \(bar) foo - ""${'"'} - prop4 = ""${'"'} - foo \(bar + baz) foo - ""${'"'} - """ - .trimIndent() - ) - } - - override fun getSnippets(): List { - return Path("../pkl-core/src/test/files/LanguageSnippetTests/input") - .walk() - .filter { path -> - val pathStr = path.toString().replace("\\", "/") - path.extension == "pkl" && - !exceptions.any { pathStr.endsWith(it) } && - !regexExceptions.any { it.matches(pathStr) } - } - .toList() - } - - companion object { - // tests that are not syntactically valid Pkl - private val exceptions = - setOf( - "stringError1.pkl", - "annotationIsNotExpression2.pkl", - "amendsRequiresParens.pkl", - "errors/parser18.pkl", - "errors/nested1.pkl", - "errors/invalidCharacterEscape.pkl", - "errors/invalidUnicodeEscape.pkl", - "errors/unterminatedUnicodeEscape.pkl", - "errors/keywordNotAllowedHere1.pkl", - "errors/keywordNotAllowedHere2.pkl", - "errors/keywordNotAllowedHere3.pkl", - "errors/keywordNotAllowedHere4.pkl", - "errors/moduleWithHighMinPklVersionAndParseErrors.pkl", - "errors/underscore.pkl", - "notAUnionDefault.pkl", - "multipleDefaults.pkl", - "modules/invalidModule1.pkl", - ) - - private val regexExceptions = - setOf( - Regex(".*/errors/delimiters/.*"), - Regex(".*/errors/parser\\d+\\.pkl"), - Regex(".*/parser/.*"), - ) - } -} diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTestInterface.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTestInterface.kt deleted file mode 100644 index bdb079fc..00000000 --- a/pkl-parser/src/test/kotlin/org/pkl/parser/ParserComparisonTestInterface.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pkl.parser - -import java.nio.file.Path -import kotlin.io.path.pathString -import kotlin.io.path.readText -import org.antlr.v4.runtime.ANTLRInputStream -import org.antlr.v4.runtime.CommonTokenStream -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.SoftAssertions -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.parallel.Execution -import org.junit.jupiter.api.parallel.ExecutionMode -import org.pkl.parser.antlr.PklLexer -import org.pkl.parser.antlr.PklParser - -@Execution(ExecutionMode.CONCURRENT) -interface ParserComparisonTestInterface { - @Test - @Execution(ExecutionMode.CONCURRENT) - fun compareSnippetTests() { - SoftAssertions.assertSoftly { softly -> - getSnippets() - .parallelStream() - .map { Pair(it.pathString, it.readText()) } - .forEach { (path, snippet) -> - try { - compare(snippet, path, softly) - } catch (e: ParserError) { - softly.fail("path: $path. Message: ${e.message}", e) - } - } - } - } - - @Test - @Execution(ExecutionMode.CONCURRENT) - fun compareSnippetTestsSpans() { - SoftAssertions.assertSoftly { softly -> - getSnippets() - .parallelStream() - .map { Pair(it.pathString, it.readText()) } - .forEach { (path, snippet) -> - try { - compareSpans(snippet, path, softly) - } catch (e: ParserError) { - softly.fail("path: $path. Message: ${e.message}", e) - } - } - } - } - - fun getSnippets(): List - - fun compare(code: String, path: String? = null, softly: SoftAssertions? = null) { - val (sexp, antlrExp) = renderBoth(code) - when { - (path != null && softly != null) -> - softly.assertThat(sexp).`as`("path: $path").isEqualTo(antlrExp) - else -> assertThat(sexp).isEqualTo(antlrExp) - } - } - - fun compareSpans(code: String, path: String, softly: SoftAssertions) { - // Our ANTLR grammar always start doc comment spans in the beginning of the line, - // even though they may have leading spaces. - // This is a regression, but it's a bugfix - if (path.endsWith("annotation1.pkl")) return - - val parser = Parser() - val mod = parser.parseModule(code) - val lexer = PklLexer(ANTLRInputStream(code)) - val antlr = PklParser(CommonTokenStream(lexer)) - val antlrMod = antlr.module() - - val comparer = SpanComparison(path, softly) - comparer.compare(mod, antlrMod) - } - - fun renderBoth(code: String): Pair = Pair(renderCode(code), renderANTLRCode(code)) - - companion object { - private fun renderCode(code: String): String { - val parser = Parser() - val mod = parser.parseModule(code) - val renderer = SexpRenderer() - return renderer.render(mod) - } - - private fun renderANTLRCode(code: String): String { - val lexer = PklLexer(ANTLRInputStream(code)) - val parser = PklParser(CommonTokenStream(lexer)) - val mod = parser.module() - val renderer = ANTLRSexpRenderer() - return renderer.render(mod) - } - } -} diff --git a/pkl-parser/src/test/kotlin/org/pkl/parser/SpanComparison.kt b/pkl-parser/src/test/kotlin/org/pkl/parser/SpanComparison.kt deleted file mode 100644 index b1cdc6e8..00000000 --- a/pkl-parser/src/test/kotlin/org/pkl/parser/SpanComparison.kt +++ /dev/null @@ -1,464 +0,0 @@ -/* - * Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pkl.parser - -import org.antlr.v4.runtime.ParserRuleContext -import org.antlr.v4.runtime.tree.TerminalNode -import org.assertj.core.api.SoftAssertions -import org.pkl.parser.antlr.PklParser.* -import org.pkl.parser.syntax.* -import org.pkl.parser.syntax.Annotation -import org.pkl.parser.syntax.Expr.AmendsExpr -import org.pkl.parser.syntax.Expr.BinaryOperatorExpr -import org.pkl.parser.syntax.Expr.FunctionLiteralExpr -import org.pkl.parser.syntax.Expr.IfExpr -import org.pkl.parser.syntax.Expr.ImportExpr -import org.pkl.parser.syntax.Expr.LetExpr -import org.pkl.parser.syntax.Expr.LogicalNotExpr -import org.pkl.parser.syntax.Expr.MultiLineStringLiteralExpr -import org.pkl.parser.syntax.Expr.NewExpr -import org.pkl.parser.syntax.Expr.NonNullExpr -import org.pkl.parser.syntax.Expr.ParenthesizedExpr -import org.pkl.parser.syntax.Expr.QualifiedAccessExpr -import org.pkl.parser.syntax.Expr.ReadExpr -import org.pkl.parser.syntax.Expr.SingleLineStringLiteralExpr -import org.pkl.parser.syntax.Expr.SubscriptExpr -import org.pkl.parser.syntax.Expr.SuperAccessExpr -import org.pkl.parser.syntax.Expr.SuperSubscriptExpr -import org.pkl.parser.syntax.Expr.ThrowExpr -import org.pkl.parser.syntax.Expr.TraceExpr -import org.pkl.parser.syntax.Expr.TypeCastExpr -import org.pkl.parser.syntax.Expr.TypeCheckExpr -import org.pkl.parser.syntax.Expr.UnaryMinusExpr -import org.pkl.parser.syntax.Expr.UnqualifiedAccessExpr -import org.pkl.parser.syntax.ObjectMember.ForGenerator -import org.pkl.parser.syntax.ObjectMember.MemberPredicate -import org.pkl.parser.syntax.ObjectMember.ObjectElement -import org.pkl.parser.syntax.ObjectMember.ObjectEntry -import org.pkl.parser.syntax.ObjectMember.ObjectMethod -import org.pkl.parser.syntax.ObjectMember.ObjectProperty -import org.pkl.parser.syntax.ObjectMember.ObjectSpread -import org.pkl.parser.syntax.ObjectMember.WhenGenerator -import org.pkl.parser.syntax.Parameter.TypedIdentifier -import org.pkl.parser.syntax.Type.ConstrainedType -import org.pkl.parser.syntax.Type.DeclaredType -import org.pkl.parser.syntax.Type.FunctionType -import org.pkl.parser.syntax.Type.NullableType -import org.pkl.parser.syntax.Type.ParenthesizedType -import org.pkl.parser.syntax.Type.StringConstantType -import org.pkl.parser.syntax.Type.UnionType - -class SpanComparison(val path: String, private val softly: SoftAssertions) { - - fun compare(module: Module, modCtx: ModuleContext) { - compareSpan(module, modCtx) - if (module.decl !== null) { - compareModuleDecl(module.decl!!, modCtx.moduleDecl()) - } - module.imports.zip(modCtx.importClause()).forEach { (i1, i2) -> compareImport(i1, i2) } - module.classes.zip(modCtx.clazz()).forEach { (class1, class2) -> compareClass(class1, class2) } - module.typeAliases.zip(modCtx.typeAlias()).forEach { (ta1, ta2) -> compareTypealias(ta1, ta2) } - module.properties.zip(modCtx.classProperty()).forEach { (prop1, prop2) -> - compareProperty(prop1, prop2) - } - module.methods.zip(modCtx.classMethod()).forEach { (m1, m2) -> compareMethod(m1, m2) } - } - - private fun compareModuleDecl(node: ModuleDecl, ctx: ModuleDeclContext) { - compareSpan(node, ctx) - compareDocComment(node.docComment, ctx.DocComment()) - node.annotations.zip(ctx.annotation()).forEach { (a1, a2) -> compareAnnotation(a1, a2) } - val header = ctx.moduleHeader() - node.modifiers.zip(header.modifier()).forEach { (m1, m2) -> compareSpan(m1, m2) } - compareQualifiedIdentifier(node.name, header.qualifiedIdentifier()) - compareExtendsOrAmendsClause(node.extendsOrAmendsDecl, header.moduleExtendsOrAmendsClause()) - } - - private fun compareImport(node: ImportClause, ctx: ImportClauseContext) { - compareSpan(node, ctx) - compareSpan(node.importStr, ctx.stringConstant()) - compareSpan(node.alias, ctx.Identifier()) - } - - private fun compareClass(node: Class, ctx: ClazzContext) { - compareSpan(node, ctx) - compareDocComment(node.docComment, ctx.DocComment()) - node.annotations.zip(ctx.annotation()).forEach { (a1, a2) -> compareAnnotation(a1, a2) } - val header = ctx.classHeader() - node.modifiers.zip(header.modifier()).forEach { (m1, m2) -> compareSpan(m1, m2) } - compareSpan(node.classKeyword, header.CLASS()) - compareSpan(node.name, header.Identifier()) - compareTypeParameterList(node.typeParameterList, header.typeParameterList()) - compareType(node.superClass, header.type()) - compareClassBody(node.body, ctx.classBody()) - compareSpan(node.headerSpan, header) - } - - private fun compareTypealias(node: TypeAlias, ctx: TypeAliasContext) { - compareSpan(node, ctx) - compareDocComment(node.docComment, ctx.DocComment()) - node.annotations.zip(ctx.annotation()).forEach { (a1, a2) -> compareAnnotation(a1, a2) } - val header = ctx.typeAliasHeader() - node.modifiers.zip(header.modifier()).forEach { (m1, m2) -> compareSpan(m1, m2) } - compareSpan(node.typealiasKeyword, header.TYPE_ALIAS()) - compareSpan(node.name, header.Identifier()) - compareTypeParameterList(node.typeParameterList, header.typeParameterList()) - compareType(node.type, ctx.type()) - compareSpan(node.headerSpan, header) - } - - private fun compareProperty(node: ClassProperty, ctx: ClassPropertyContext) { - if (node.docComment === null) { - compareSpan(node, ctx) - } - compareDocComment(node.docComment, ctx.DocComment()) - node.annotations.zip(ctx.annotation()).forEach { (a1, a2) -> compareAnnotation(a1, a2) } - node.modifiers.zip(ctx.modifier()).forEach { (m1, m2) -> compareSpan(m1, m2) } - compareSpan(node.name, ctx.Identifier()) - compareTypeAnnotation(node.typeAnnotation, ctx.typeAnnotation()) - compareExpr(node.expr, ctx.expr()) - node.bodyList.zip(ctx.objectBody()).forEach { (b1, b2) -> compareObjectBody(b1, b2) } - } - - private fun compareMethod(node: ClassMethod, ctx: ClassMethodContext) { - compareSpan(node, ctx) - compareDocComment(node.docComment, ctx.DocComment()) - node.annotations.zip(ctx.annotation()).forEach { (a1, a2) -> compareAnnotation(a1, a2) } - val header = ctx.methodHeader() - compareSpan(node.headerSpan, header) - node.modifiers.zip(header.modifier()).forEach { (m1, m2) -> compareSpan(m1, m2) } - compareSpan(node.name, header.Identifier()) - compareTypeParameterList(node.typeParameterList, header.typeParameterList()) - compareParameterList(node.parameterList, header.parameterList()) - compareTypeAnnotation(node.typeAnnotation, header.typeAnnotation()) - compareExpr(node.expr, ctx.expr()) - } - - private fun compareAnnotation(node: Annotation, ctx: AnnotationContext) { - compareSpan(node, ctx) - compareType(node.type, ctx.type()) - compareObjectBody(node.body, ctx.objectBody()) - } - - private fun compareParameterList(node: ParameterList?, ctx: ParameterListContext?) { - if (node === null) return - compareSpan(node, ctx!!) - node.parameters.zip(ctx.parameter()).forEach { (p1, p2) -> compareParameter(p1, p2) } - } - - private fun compareParameter(node: Parameter?, ctx: ParameterContext?) { - if (node === null) return - compareSpan(node, ctx!!) - if (node is TypedIdentifier) { - val tident = ctx.typedIdentifier() - compareSpan(node.identifier, tident.Identifier()) - compareTypeAnnotation(node.typeAnnotation, tident.typeAnnotation()) - } - } - - private fun compareTypeParameterList(node: TypeParameterList?, ctx: TypeParameterListContext?) { - if (node === null) return - compareSpan(node, ctx!!) - node.parameters.zip(ctx.typeParameter()).forEach { (p1, p2) -> compareTypeParameter(p1, p2) } - } - - private fun compareTypeParameter(node: TypeParameter, ctx: TypeParameterContext) { - compareSpan(node, ctx) - compareSpan(node.identifier, ctx.Identifier()) - } - - private fun compareClassBody(node: ClassBody?, ctx: ClassBodyContext?) { - if (node === null) return - compareSpan(node, ctx!!) - node.properties.zip(ctx.classProperty()).forEach { (p1, p2) -> compareProperty(p1, p2) } - node.methods.zip(ctx.classMethod()).forEach { (m1, m2) -> compareMethod(m1, m2) } - } - - private fun compareTypeAnnotation(node: TypeAnnotation?, ctx: TypeAnnotationContext?) { - if (node === null) return - compareSpan(node, ctx!!) - compareSpan(node.type, ctx.type()) - } - - private fun compareObjectBody(node: ObjectBody?, ctx: ObjectBodyContext?) { - if (node === null) return - compareSpan(node, ctx!!) - node.parameters.zip(ctx.parameter()).forEach { (p1, p2) -> compareParameter(p1, p2) } - node.members.zip(ctx.objectMember()).forEach { (m1, m2) -> compareObjectMember(m1, m2) } - } - - private fun compareType(node: Type?, actx: TypeContext?) { - if (node === null) return - val ctx = if (actx is DefaultUnionTypeContext) actx.type() else actx - compareSpan(node, ctx!!) - when (node) { - is StringConstantType -> - compareSpan(node.str, (ctx as StringLiteralTypeContext).stringConstant()) - is DeclaredType -> { - val decl = ctx as DeclaredTypeContext - compareQualifiedIdentifier(node.name, decl.qualifiedIdentifier()) - compareTypeArgumentList(node.args, ctx.typeArgumentList()) - } - is ParenthesizedType -> compareType(node.type, (ctx as ParenthesizedTypeContext).type()) - is NullableType -> compareType(node.type, (ctx as NullableTypeContext).type()) - is ConstrainedType -> { - val cons = ctx as ConstrainedTypeContext - compareType(node.type, cons.type()) - node.exprs.zip(cons.expr()).forEach { (e1, e2) -> compareExpr(e1, e2) } - } - is UnionType -> { - val flattened = ANTLRSexpRenderer.flattenUnion(ctx as UnionTypeContext) - node.types.zip(flattened).forEach { (t1, t2) -> compareType(t1, t2) } - } - is FunctionType -> { - val func = ctx as FunctionTypeContext - node.args.zip(func.ps).forEach { (t1, t2) -> compareType(t1, t2) } - compareType(node.ret, func.r) - } - else -> {} - } - } - - private fun compareObjectMember(node: ObjectMember, ctx: ObjectMemberContext) { - compareSpan(node, ctx) - when (node) { - is ObjectElement -> compareExpr(node.expr, (ctx as ObjectElementContext).expr()) - is ObjectProperty -> { - ctx as ObjectPropertyContext - node.modifiers.zip(ctx.modifier()).forEach { (m1, m2) -> compareSpan(m1, m2) } - compareSpan(node.identifier, ctx.Identifier()) - compareTypeAnnotation(node.typeAnnotation, ctx.typeAnnotation()) - compareExpr(node.expr, ctx.expr()) - if (node.bodyList.isNotEmpty()) { - node.bodyList.zip(ctx.objectBody()).forEach { (b1, b2) -> compareObjectBody(b1, b2) } - } - } - is ObjectMethod -> { - ctx as ObjectMethodContext - val header = ctx.methodHeader() - node.modifiers.zip(header.modifier()).forEach { (m1, m2) -> compareSpan(m1, m2) } - compareSpan(node.functionKeyword, header.FUNCTION()) - compareSpan(node.identifier, header.Identifier()) - compareTypeParameterList(node.typeParameterList, header.typeParameterList()) - compareParameterList(node.paramList, header.parameterList()) - compareTypeAnnotation(node.typeAnnotation, header.typeAnnotation()) - compareExpr(node.expr, ctx.expr()) - compareSpan(node.headerSpan(), header) - } - is MemberPredicate -> { - ctx as MemberPredicateContext - compareExpr(node.pred, ctx.k) - compareExpr(node.expr, ctx.v) - if (node.bodyList.isNotEmpty()) { - node.bodyList.zip(ctx.objectBody()).forEach { (b1, b2) -> compareObjectBody(b1, b2) } - } - } - is ObjectEntry -> { - ctx as ObjectEntryContext - compareExpr(node.key, ctx.k) - compareExpr(node.value, ctx.v) - if (node.bodyList.isNotEmpty()) { - node.bodyList.zip(ctx.objectBody()).forEach { (b1, b2) -> compareObjectBody(b1, b2) } - } - } - is ObjectSpread -> compareExpr(node.expr, (ctx as ObjectSpreadContext).expr()) - is WhenGenerator -> { - ctx as WhenGeneratorContext - compareExpr(node.predicate, ctx.expr()) - compareObjectBody(node.thenClause, ctx.b1) - compareObjectBody(node.elseClause, ctx.b2) - } - is ForGenerator -> { - ctx as ForGeneratorContext - compareParameter(node.p1, ctx.t1) - compareParameter(node.p2, ctx.t2) - compareExpr(node.expr, ctx.expr()) - compareObjectBody(node.body, ctx.objectBody()) - } - } - } - - private fun compareExpr(node: Expr?, ctx: ExprContext?) { - if (node === null) return - compareSpan(node, ctx!!) - when (node) { - is SingleLineStringLiteralExpr -> { - node.parts.zip((ctx as SingleLineStringLiteralContext).singleLineStringPart()).forEach { - (s1, s2) -> - compareSpan(s1, s2) - } - } - is MultiLineStringLiteralExpr -> { - // only compare interpolated expressions - val exprs = node.parts.filterIsInstance() - val antlrExprs = - (ctx as MultiLineStringLiteralContext).multiLineStringPart().mapNotNull { it.expr() } - exprs.zip(antlrExprs).forEach { (s1, s2) -> compareExpr(s1.expr, s2) } - } - is ThrowExpr -> compareExpr(node.expr, (ctx as ThrowExprContext).expr()) - is TraceExpr -> compareExpr(node.expr, (ctx as TraceExprContext).expr()) - is ImportExpr -> compareSpan(node.importStr, (ctx as ImportExprContext).stringConstant()) - is ReadExpr -> compareExpr(node.expr, (ctx as ReadExprContext).expr()) - is UnqualifiedAccessExpr -> { - ctx as UnqualifiedAccessExprContext - compareSpan(node.identifier, ctx.Identifier()) - compareArgumentList(node.argumentList, ctx.argumentList()) - } - is QualifiedAccessExpr -> { - ctx as QualifiedAccessExprContext - compareExpr(node.expr, ctx.expr()) - compareSpan(node.identifier, ctx.Identifier()) - compareArgumentList(node.argumentList, ctx.argumentList()) - } - is SuperAccessExpr -> { - ctx as SuperAccessExprContext - compareSpan(node.identifier, ctx.Identifier()) - compareArgumentList(node.argumentList, ctx.argumentList()) - } - is SuperSubscriptExpr -> compareExpr(node.arg, (ctx as SuperSubscriptExprContext).expr()) - is SubscriptExpr -> { - ctx as SubscriptExprContext - compareExpr(node.expr, ctx.l) - compareExpr(node.arg, ctx.r) - } - is IfExpr -> { - ctx as IfExprContext - compareExpr(node.cond, ctx.c) - compareExpr(node.then, ctx.l) - compareExpr(node.els, ctx.r) - } - is LetExpr -> { - ctx as LetExprContext - compareParameter(node.parameter, ctx.parameter()) - compareExpr(node.bindingExpr, ctx.l) - compareExpr(node.expr, ctx.r) - } - is FunctionLiteralExpr -> { - ctx as FunctionLiteralContext - compareParameterList(node.parameterList, ctx.parameterList()) - compareExpr(node.expr, ctx.expr()) - } - is ParenthesizedExpr -> compareExpr(node.expr, (ctx as ParenthesizedExprContext).expr()) - is NewExpr -> { - ctx as NewExprContext - compareType(node.type, ctx.type()) - compareObjectBody(node.body, ctx.objectBody()) - } - is AmendsExpr -> { - ctx as AmendExprContext - compareExpr(node.expr, ctx.expr()) - compareObjectBody(node.body, ctx.objectBody()) - } - is NonNullExpr -> compareExpr(node.expr, (ctx as NonNullExprContext).expr()) - is UnaryMinusExpr -> compareExpr(node.expr, (ctx as UnaryMinusExprContext).expr()) - is LogicalNotExpr -> compareExpr(node.expr, (ctx as LogicalNotExprContext).expr()) - is BinaryOperatorExpr -> { - val (l, r) = - when (ctx) { - is ExponentiationExprContext -> ctx.l to ctx.r - is MultiplicativeExprContext -> ctx.l to ctx.r - is AdditiveExprContext -> ctx.l to ctx.r - is ComparisonExprContext -> ctx.l to ctx.r - is EqualityExprContext -> ctx.l to ctx.r - is LogicalAndExprContext -> ctx.l to ctx.r - is LogicalOrExprContext -> ctx.l to ctx.r - is PipeExprContext -> ctx.l to ctx.r - is NullCoalesceExprContext -> ctx.l to ctx.r - else -> throw RuntimeException("unreacheable code") - } - compareExpr(node.left, l) - compareExpr(node.right, r) - } - is TypeCheckExpr -> { - ctx as TypeTestExprContext - compareExpr(node.expr, ctx.expr()) - compareType(node.type, ctx.type()) - } - is TypeCastExpr -> { - ctx as TypeTestExprContext - compareExpr(node.expr, ctx.expr()) - compareType(node.type, ctx.type()) - } - else -> {} - } - } - - private fun compareArgumentList(node: ArgumentList?, ctx: ArgumentListContext?) { - if (node === null) return - compareSpan(node, ctx!!) - node.arguments.zip(ctx.expr()).forEach { (e1, e2) -> compareExpr(e1, e2) } - } - - private fun compareTypeArgumentList(node: TypeArgumentList?, ctx: TypeArgumentListContext?) { - if (node === null) return - compareSpan(node, ctx!!) - node.types.zip(ctx.type()).forEach { (t1, t2) -> compareType(t1, t2) } - } - - private fun compareQualifiedIdentifier( - node: QualifiedIdentifier?, - ctx: QualifiedIdentifierContext?, - ) { - if (node === null) return - compareSpan(node, ctx!!) - node.identifiers.zip(ctx.Identifier()).forEach { (id1, id2) -> compareSpan(id1, id2) } - } - - private fun compareExtendsOrAmendsClause( - node: ExtendsOrAmendsClause?, - ctx: ModuleExtendsOrAmendsClauseContext?, - ) { - if (node === null) return - compareSpan(node, ctx!!) - compareSpan(node.url, ctx.stringConstant()) - } - - private fun compareSpan(node: Node?, ctx: ParserRuleContext) { - if (node != null) { - compareSpan(node, ctx.start.startIndex, ctx.stop.stopIndex) - } - } - - private fun compareSpan(node: Node?, ctx: TerminalNode?) { - if (node != null) { - compareSpan(node, ctx!!.symbol.startIndex, ctx.symbol.stopIndex) - } - } - - private fun compareSpan(node: Node, charIndex: Int, tokenStop: Int) { - compareSpan(node.span(), charIndex, tokenStop) - } - - private fun compareSpan(span: Span, ctx: ParserRuleContext) { - compareSpan(span, ctx.start.startIndex, ctx.stop.stopIndex) - } - - private fun compareSpan(span: Span, charIndex: Int, tokenStop: Int) { - val length = tokenStop - charIndex + 1 - softly.assertThat(span.charIndex).`as`("$span, index for path: $path").isEqualTo(charIndex) - softly.assertThat(span.length).`as`("$span, length for path: $path").isEqualTo(length) - } - - private fun compareDocComment(node: Node?, ctx: TerminalNode?) { - if (node == null) return - val charIndex = ctx!!.symbol.startIndex - // for some reason antlr's doc coments are off by one - val length = ctx.symbol.stopIndex - charIndex - val span = node.span() - softly.assertThat(span.charIndex).`as`("$span, index for path: $path").isEqualTo(charIndex) - softly.assertThat(span.length).`as`("$span, length for path: $path").isEqualTo(length) - } -}