Remove ANTLR from the repo (#1114)

This commit is contained in:
Islon Scherer
2025-07-08 09:43:35 +02:00
committed by GitHub
parent 0973774a5d
commit 3a35be6311
15 changed files with 0 additions and 2459 deletions

4
.gitignore vendored
View File

@@ -20,8 +20,4 @@ testgenerated/
.pkl-lsp/
# :pkl-core:makeIntelliJAntlrPluginHappy
gen/
PklLexer.tokens
.kotlin/

View File

@@ -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

View File

@@ -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<TaskTriggersConfig> {
afterSync(provider { project(":pkl-parser").tasks.named("makeIntelliJAntlrPluginHappy") })
}
}
}
}

View File

@@ -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) {

View File

@@ -39,7 +39,6 @@ dependencies {
testImplementation(projects.pklCommonsTest)
testImplementation(projects.pklParser)
testImplementation(libs.junitEngine)
testImplementation(libs.antlrRuntime)
}
tasks.test {

View File

@@ -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

View File

@@ -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" }

View File

@@ -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

View File

@@ -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") }
}

View File

@@ -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<StringInterpolationScope> 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?
;

View File

@@ -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
| <assoc=right> 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
| <assoc=right> 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'
;

File diff suppressed because it is too large Load Diff

View File

@@ -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<Path> {
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/.*"),
)
}
}

View File

@@ -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<Path>
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<String, String> = 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)
}
}
}

View File

@@ -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<StringPart.StringInterpolation>()
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)
}
}