mirror of
https://github.com/apple/pkl.git
synced 2026-01-11 22:30:54 +01:00
Remove ANTLR from the repo (#1114)
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,8 +20,4 @@ testgenerated/
|
||||
|
||||
.pkl-lsp/
|
||||
|
||||
# :pkl-core:makeIntelliJAntlrPluginHappy
|
||||
gen/
|
||||
PklLexer.tokens
|
||||
|
||||
.kotlin/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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") })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -39,7 +39,6 @@ dependencies {
|
||||
testImplementation(projects.pklCommonsTest)
|
||||
testImplementation(projects.pklParser)
|
||||
testImplementation(libs.junitEngine)
|
||||
testImplementation(libs.antlrRuntime)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
;
|
||||
@@ -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
@@ -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/.*"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user