mirror of
https://github.com/apple/pkl.git
synced 2026-03-25 02:21:11 +01:00
Initial commit
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
package org.pkl.core.generator
|
||||
|
||||
import com.oracle.truffle.api.dsl.GeneratedBy
|
||||
import com.squareup.javapoet.ClassName
|
||||
import javax.lang.model.SourceVersion
|
||||
import javax.annotation.processing.RoundEnvironment
|
||||
import com.squareup.javapoet.MethodSpec
|
||||
import com.squareup.javapoet.JavaFile
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
import javax.annotation.processing.AbstractProcessor
|
||||
import javax.lang.model.element.*
|
||||
import javax.lang.model.type.TypeMirror
|
||||
|
||||
/**
|
||||
* Generates a subclass of `org.pkl.core.stdlib.registry.ExternalMemberRegistry`
|
||||
* for each stdlib module and a factory to instantiate them.
|
||||
* Generated classes are written to `generated/truffle/org/pkl/core/stdlib/registry`.
|
||||
*
|
||||
* Inputs:
|
||||
* - Generated Truffle node classes for stdlib members.
|
||||
* These classes are located in subpackages of `org.pkl.core.stdlib`
|
||||
* and identified via their `@GeneratedBy` annotations.
|
||||
* - `@PklName` annotations on hand-written node classes from which Truffle node classes are generated.
|
||||
*/
|
||||
class MemberRegistryGenerator : AbstractProcessor() {
|
||||
private val truffleNodeClassSuffix = "NodeGen"
|
||||
private val truffleNodeFactorySuffix = "NodesFactory"
|
||||
private val stdLibPackageName: String = "org.pkl.core.stdlib"
|
||||
private val registryPackageName: String = "$stdLibPackageName.registry"
|
||||
private val modulePackageName: String = "org.pkl.core.module"
|
||||
|
||||
private val externalMemberRegistryClassName: ClassName =
|
||||
ClassName.get(registryPackageName, "ExternalMemberRegistry")
|
||||
private val emptyMemberRegistryClassName: ClassName =
|
||||
ClassName.get(registryPackageName, "EmptyMemberRegistry")
|
||||
private val memberRegistryFactoryClassName: ClassName =
|
||||
ClassName.get(registryPackageName, "MemberRegistryFactory")
|
||||
private val moduleKeyClassName: ClassName =
|
||||
ClassName.get(modulePackageName, "ModuleKey")
|
||||
private val moduleKeysClassName: ClassName =
|
||||
ClassName.get(modulePackageName, "ModuleKeys")
|
||||
|
||||
override fun getSupportedAnnotationTypes(): Set<String> = setOf(GeneratedBy::class.java.name)
|
||||
|
||||
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.RELEASE_11
|
||||
|
||||
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
||||
if (annotations.isEmpty()) return true
|
||||
|
||||
val nodeClassesByPackage = collectNodeClasses(roundEnv)
|
||||
generateRegistryClasses(nodeClassesByPackage)
|
||||
generateRegistryFactoryClass(nodeClassesByPackage.keys)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun collectNodeClasses(roundEnv: RoundEnvironment) = roundEnv
|
||||
.getElementsAnnotatedWith(GeneratedBy::class.java)
|
||||
.asSequence()
|
||||
.filterIsInstance<TypeElement>()
|
||||
.filter { it.qualifiedName.toString().startsWith(stdLibPackageName) }
|
||||
.filter { it.simpleName.toString().endsWith(truffleNodeClassSuffix) }
|
||||
.sortedWith(compareBy(
|
||||
{ if (it.enclosingElement.kind == ElementKind.PACKAGE) "" else it.enclosingElement.simpleName.toString() },
|
||||
{ it.simpleName.toString() }
|
||||
))
|
||||
.groupBy { processingEnv.elementUtils.getPackageOf(it) }
|
||||
|
||||
private fun generateRegistryClasses(nodeClassesByPackage: Map<PackageElement, List<TypeElement>>) {
|
||||
for ((pkg, nodeClasses) in nodeClassesByPackage) {
|
||||
generateRegistryClass(pkg, nodeClasses)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateRegistryClass(pkg: PackageElement, nodeClasses: List<TypeElement>) {
|
||||
val pklModuleName = getAnnotatedPklName(pkg) ?: pkg.simpleName.toString()
|
||||
val pklModuleNameCapitalized = pklModuleName.capitalize()
|
||||
val registryClassName = ClassName.get(registryPackageName, "${pklModuleNameCapitalized}MemberRegistry")
|
||||
|
||||
val registryClass = TypeSpec.classBuilder(registryClassName)
|
||||
.addJavadoc("Generated by {@link ${this::class.qualifiedName}}.")
|
||||
.addModifiers(Modifier.FINAL)
|
||||
.superclass(externalMemberRegistryClassName)
|
||||
val registryClassConstructor = MethodSpec.constructorBuilder()
|
||||
|
||||
for (nodeClass in nodeClasses) {
|
||||
val enclosingClass = nodeClass.enclosingElement
|
||||
val pklClassName = getAnnotatedPklName(enclosingClass)
|
||||
?: enclosingClass.simpleName.toString().removeSuffix(truffleNodeFactorySuffix)
|
||||
val pklMemberName = getAnnotatedPklName(nodeClass)
|
||||
?: nodeClass.simpleName.toString().removeSuffix(truffleNodeClassSuffix)
|
||||
val pklMemberNameQualified = when (pklClassName) {
|
||||
// By convention, the top-level class containing node classes
|
||||
// for *module* members is named `<SimpleModuleName>Nodes`.
|
||||
// Example: `BaseNodes` for pkl.base
|
||||
pklModuleNameCapitalized ->
|
||||
"pkl.$pklModuleName#$pklMemberName"
|
||||
else ->
|
||||
"pkl.$pklModuleName#$pklClassName.$pklMemberName"
|
||||
}
|
||||
registryClass.addOriginatingElement(nodeClass)
|
||||
registryClassConstructor
|
||||
.addStatement("register(\$S, \$T::create)", pklMemberNameQualified, nodeClass)
|
||||
}
|
||||
|
||||
registryClass.addMethod(registryClassConstructor.build())
|
||||
val javaFile = JavaFile.builder(registryPackageName, registryClass.build()).build()
|
||||
javaFile.writeTo(processingEnv.filer)
|
||||
}
|
||||
|
||||
private fun generateRegistryFactoryClass(packages: Collection<PackageElement>) {
|
||||
val registryFactoryClass = TypeSpec.classBuilder(memberRegistryFactoryClassName)
|
||||
.addJavadoc("Generated by {@link ${this::class.qualifiedName}}.")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
|
||||
val registryFactoryConstructor = MethodSpec.constructorBuilder()
|
||||
.addModifiers(Modifier.PRIVATE)
|
||||
registryFactoryClass.addMethod(registryFactoryConstructor.build())
|
||||
val registryFactoryGetMethod = MethodSpec.methodBuilder("get")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(moduleKeyClassName, "moduleKey")
|
||||
.returns(externalMemberRegistryClassName)
|
||||
.beginControlFlow("if (!\$T.isStdLibModule(moduleKey))", moduleKeysClassName)
|
||||
.addStatement("return \$T.INSTANCE", emptyMemberRegistryClassName)
|
||||
.endControlFlow()
|
||||
.beginControlFlow("switch (moduleKey.getUri().getSchemeSpecificPart())")
|
||||
|
||||
for (pkg in packages) {
|
||||
val pklModuleName = getAnnotatedPklName(pkg) ?: pkg.simpleName.toString()
|
||||
val pklModuleNameCapitalized = pklModuleName.capitalize()
|
||||
val registryClassName = ClassName.get(registryPackageName, "${pklModuleNameCapitalized}MemberRegistry")
|
||||
|
||||
// declare dependency on package-info.java (for `@PklName`)
|
||||
registryFactoryClass.addOriginatingElement(pkg)
|
||||
registryFactoryGetMethod
|
||||
.addCode("case \$S:\n", pklModuleName)
|
||||
.addStatement(" return new \$T()", registryClassName)
|
||||
}
|
||||
|
||||
registryFactoryGetMethod
|
||||
.addCode("default:\n")
|
||||
.addStatement(" return \$T.INSTANCE", emptyMemberRegistryClassName)
|
||||
.endControlFlow()
|
||||
registryFactoryClass.addMethod(registryFactoryGetMethod.build())
|
||||
val javaFile = JavaFile.builder(registryPackageName, registryFactoryClass.build()).build()
|
||||
javaFile.writeTo(processingEnv.filer)
|
||||
}
|
||||
|
||||
private fun getAnnotatedPklName(element: Element): String? {
|
||||
for (annotation in element.annotationMirrors) {
|
||||
when (annotation.annotationType.asElement().simpleName.toString()) {
|
||||
"PklName" ->
|
||||
return annotation.elementValues.values.iterator().next().value.toString()
|
||||
"GeneratedBy" -> {
|
||||
val annotationValue = annotation.elementValues.values.first().value as TypeMirror
|
||||
return getAnnotatedPklName(processingEnv.typeUtils.asElement(annotationValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun String.capitalize(): String = replaceFirstChar { it.titlecaseChar() }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# https://docs.gradle.org/current/userguide/java_plugin.html#aggregating_annotation_processors
|
||||
org.pkl.core.generator.MemberRegistryGenerator,aggregating
|
||||
@@ -0,0 +1 @@
|
||||
org.pkl.core.generator.MemberRegistryGenerator
|
||||
170
pkl-core/src/main/antlr/PclLexer.tokens
Normal file
170
pkl-core/src/main/antlr/PclLexer.tokens
Normal file
@@ -0,0 +1,170 @@
|
||||
ABSTRACT=1
|
||||
AMENDS=2
|
||||
AS=3
|
||||
CLASS=4
|
||||
ELSE=5
|
||||
EXTENDS=6
|
||||
EXTERNAL=7
|
||||
FALSE=8
|
||||
FINAL=9
|
||||
FOR=10
|
||||
FUNCTION=11
|
||||
HIDDEN_=12
|
||||
IF=13
|
||||
IMPORT=14
|
||||
IMPORT_GLOB=15
|
||||
IN=16
|
||||
IS=17
|
||||
LET=18
|
||||
LOCAL=19
|
||||
MODULE=20
|
||||
NEW=21
|
||||
NOTHING=22
|
||||
NULL=23
|
||||
OPEN=24
|
||||
OUT=25
|
||||
OUTER=26
|
||||
READ=27
|
||||
READ_GLOB=28
|
||||
READ_OR_NULL=29
|
||||
SUPER=30
|
||||
THIS=31
|
||||
THROW=32
|
||||
TRACE=33
|
||||
TRUE=34
|
||||
TYPE_ALIAS=35
|
||||
UNKNOWN=36
|
||||
WHEN=37
|
||||
LPAREN=38
|
||||
RPAREN=39
|
||||
LBRACE=40
|
||||
RBRACE=41
|
||||
LBRACK=42
|
||||
RBRACK=43
|
||||
LPRED=44
|
||||
COMMA=45
|
||||
DOT=46
|
||||
QDOT=47
|
||||
COALESCE=48
|
||||
NON_NULL=49
|
||||
AT=50
|
||||
ASSIGN=51
|
||||
GT=52
|
||||
LT=53
|
||||
NOT=54
|
||||
QUESTION=55
|
||||
COLON=56
|
||||
ARROW=57
|
||||
EQUAL=58
|
||||
NOT_EQUAL=59
|
||||
LTE=60
|
||||
GTE=61
|
||||
AND=62
|
||||
OR=63
|
||||
PLUS=64
|
||||
MINUS=65
|
||||
POW=66
|
||||
MUL=67
|
||||
DIV=68
|
||||
INT_DIV=69
|
||||
MOD=70
|
||||
UNION=71
|
||||
PIPE=72
|
||||
SPREAD=73
|
||||
QSPREAD=74
|
||||
SLQuote=75
|
||||
MLQuote=76
|
||||
IntLiteral=77
|
||||
FloatLiteral=78
|
||||
Identifier=79
|
||||
NewlineSemicolon=80
|
||||
Whitespace=81
|
||||
DocComment=82
|
||||
BlockComment=83
|
||||
LineComment=84
|
||||
ShebangComment=85
|
||||
SLEndQuote=86
|
||||
SLInterpolation=87
|
||||
SLUnicodeEscape=88
|
||||
SLCharacterEscape=89
|
||||
SLCharacters=90
|
||||
MLEndQuote=91
|
||||
MLInterpolation=92
|
||||
MLUnicodeEscape=93
|
||||
MLCharacterEscape=94
|
||||
MLNewline=95
|
||||
MLCharacters=96
|
||||
'abstract'=1
|
||||
'amends'=2
|
||||
'as'=3
|
||||
'class'=4
|
||||
'else'=5
|
||||
'extends'=6
|
||||
'external'=7
|
||||
'false'=8
|
||||
'final'=9
|
||||
'for'=10
|
||||
'function'=11
|
||||
'hidden'=12
|
||||
'if'=13
|
||||
'import'=14
|
||||
'import*'=15
|
||||
'in'=16
|
||||
'is'=17
|
||||
'let'=18
|
||||
'local'=19
|
||||
'module'=20
|
||||
'new'=21
|
||||
'nothing'=22
|
||||
'null'=23
|
||||
'open'=24
|
||||
'out'=25
|
||||
'outer'=26
|
||||
'read'=27
|
||||
'read*'=28
|
||||
'read?'=29
|
||||
'super'=30
|
||||
'this'=31
|
||||
'throw'=32
|
||||
'trace'=33
|
||||
'true'=34
|
||||
'typealias'=35
|
||||
'unknown'=36
|
||||
'when'=37
|
||||
'('=38
|
||||
')'=39
|
||||
'{'=40
|
||||
'}'=41
|
||||
'['=42
|
||||
']'=43
|
||||
'[['=44
|
||||
','=45
|
||||
'.'=46
|
||||
'?.'=47
|
||||
'??'=48
|
||||
'!!'=49
|
||||
'@'=50
|
||||
'='=51
|
||||
'>'=52
|
||||
'<'=53
|
||||
'!'=54
|
||||
'?'=55
|
||||
':'=56
|
||||
'->'=57
|
||||
'=='=58
|
||||
'!='=59
|
||||
'<='=60
|
||||
'>='=61
|
||||
'&&'=62
|
||||
'||'=63
|
||||
'+'=64
|
||||
'-'=65
|
||||
'**'=66
|
||||
'*'=67
|
||||
'/'=68
|
||||
'~/'=69
|
||||
'%'=70
|
||||
'|'=71
|
||||
'|>'=72
|
||||
'...'=73
|
||||
'...?'=74
|
||||
387
pkl-core/src/main/antlr/PklLexer.g4
Normal file
387
pkl-core/src/main/antlr/PklLexer.g4
Normal file
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.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?
|
||||
;
|
||||
255
pkl-core/src/main/antlr/PklParser.g4
Normal file
255
pkl-core/src/main/antlr/PklParser.g4
Normal file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.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'
|
||||
| 'const'
|
||||
;
|
||||
47
pkl-core/src/main/java/org/pkl/core/BufferedLogger.java
Normal file
47
pkl-core/src/main/java/org/pkl/core/BufferedLogger.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/** A logger that keeps messages locally and can return them. */
|
||||
public class BufferedLogger implements Logger {
|
||||
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
private final Logger logger;
|
||||
|
||||
public BufferedLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String message, StackFrame frame) {
|
||||
builder.append(message).append("\n");
|
||||
logger.trace(message, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message, StackFrame frame) {
|
||||
builder.append(message).append("\n");
|
||||
logger.warn(message, frame);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
builder.setLength(0);
|
||||
}
|
||||
|
||||
public String getLogs() {
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
50
pkl-core/src/main/java/org/pkl/core/Composite.java
Normal file
50
pkl-core/src/main/java/org/pkl/core/Composite.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.Map;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** A container of properties. */
|
||||
public interface Composite extends Value {
|
||||
/** Shorthand for {@code getProperties.containsKey(name)}. */
|
||||
default boolean hasProperty(String name) {
|
||||
return getProperties().containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the property with the given name, or throws {@link
|
||||
* NoSuchPropertyException} if no such property exists.
|
||||
*/
|
||||
Object getProperty(String name);
|
||||
|
||||
/** Shorthand for {@code getProperties().get(name)}; */
|
||||
default @Nullable Object getPropertyOrNull(String name) {
|
||||
return getProperties().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #getProperty} except that this method returns {@code null} instead of {@link
|
||||
* PNull} for Pkl value {@code null}.
|
||||
*/
|
||||
default @Nullable Object get(String name) {
|
||||
var result = getProperty(name);
|
||||
return result instanceof PNull ? null : result;
|
||||
}
|
||||
|
||||
/** Returns the properties of this composite. */
|
||||
Map<String, Object> getProperties();
|
||||
}
|
||||
258
pkl-core/src/main/java/org/pkl/core/DataSize.java
Normal file
258
pkl-core/src/main/java/org/pkl/core/DataSize.java
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import static org.pkl.core.DataSizeUnit.*;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.pkl.core.util.MathUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Java representation of a {@code pkl.base#DataSize} value. */
|
||||
public final strictfp class DataSize implements Value {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final double value;
|
||||
private final DataSizeUnit unit;
|
||||
|
||||
/** Constructs a new data size with the given value and unit. */
|
||||
public DataSize(double value, DataSizeUnit unit) {
|
||||
this.value = value;
|
||||
this.unit = Objects.requireNonNull(unit, "unit");
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#BYTES}. */
|
||||
public static DataSize ofBytes(double value) {
|
||||
return new DataSize(value, BYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#KILOBYTES}. */
|
||||
public static DataSize ofKilobytes(double value) {
|
||||
return new DataSize(value, KILOBYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#KIBIBYTES}. */
|
||||
public static DataSize ofKibibytes(double value) {
|
||||
return new DataSize(value, KIBIBYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#MEGABYTES}. */
|
||||
public static DataSize ofMegabytes(double value) {
|
||||
return new DataSize(value, MEGABYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#MEBIBYTES}. */
|
||||
public static DataSize ofMebibytes(double value) {
|
||||
return new DataSize(value, MEBIBYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#GIGABYTES}. */
|
||||
public static DataSize ofGigabytes(double value) {
|
||||
return new DataSize(value, GIGABYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#GIBIBYTES}. */
|
||||
public static DataSize ofGibibytes(double value) {
|
||||
return new DataSize(value, GIBIBYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#TERABYTES}. */
|
||||
public static DataSize ofTerabytes(double value) {
|
||||
return new DataSize(value, TERABYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#TEBIBYTES}. */
|
||||
public static DataSize ofTebibytes(double value) {
|
||||
return new DataSize(value, TEBIBYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#PETABYTES}. */
|
||||
public static DataSize ofPetabytes(double value) {
|
||||
return new DataSize(value, PETABYTES);
|
||||
}
|
||||
|
||||
/** Constructs a new data size with the given value and unit {@link DataSizeUnit#PEBIBYTES}. */
|
||||
public static DataSize ofPebibytes(double value) {
|
||||
return new DataSize(value, PEBIBYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size. The value is relative to the unit and may be negative. */
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns the unit of this data size. */
|
||||
public DataSizeUnit getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#BYTES}. */
|
||||
public double inBytes() {
|
||||
return convertValueTo(BYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#KILOBYTES}. */
|
||||
public double inKilobytes() {
|
||||
return convertValueTo(KILOBYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#KIBIBYTES}. */
|
||||
public double inKibibytes() {
|
||||
return convertValueTo(KIBIBYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#MEGABYTES}. */
|
||||
public double inMegabytes() {
|
||||
return convertValueTo(MEGABYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#MEBIBYTES}. */
|
||||
public double inMebibytes() {
|
||||
return convertValueTo(MEBIBYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#GIGABYTES}. */
|
||||
public double inGigabytes() {
|
||||
return convertValueTo(GIGABYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#GIBIBYTES}. */
|
||||
public double inGibibytes() {
|
||||
return convertValueTo(GIBIBYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#TERABYTES}. */
|
||||
public double inTerabytes() {
|
||||
return convertValueTo(TERABYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#TEBIBYTES}. */
|
||||
public double inTebibytes() {
|
||||
return convertValueTo(TEBIBYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#PETABYTES}. */
|
||||
public double inPetabytes() {
|
||||
return convertValueTo(PETABYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in {@link DataSizeUnit#PEBIBYTES}. */
|
||||
public double inPebibytes() {
|
||||
return convertValueTo(PEBIBYTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#BYTES}. */
|
||||
public long inWholeBytes() {
|
||||
return Math.round(inBytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#KILOBYTES}. */
|
||||
public long inWholeKilobytes() {
|
||||
return Math.round(inKilobytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#KIBIBYTES}. */
|
||||
public long inWholeKibibytes() {
|
||||
return Math.round(inKibibytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#MEGABYTES}. */
|
||||
public long inWholeMegabytes() {
|
||||
return Math.round(inMegabytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#MEBIBYTES}. */
|
||||
public long inWholeMebibytes() {
|
||||
return Math.round(inMebibytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#GIGABYTES}. */
|
||||
public long inWholeGigabytes() {
|
||||
return Math.round(inGigabytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#GIBIBYTES}. */
|
||||
public long inWholeGibibytes() {
|
||||
return Math.round(inGibibytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#TERABYTES}. */
|
||||
public long inWholeTerabytes() {
|
||||
return Math.round(inTerabytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#TEBIBYTES}. */
|
||||
public long inWholeTebibytes() {
|
||||
return Math.round(inTebibytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#PETABYTES}. */
|
||||
public long inWholePetabytes() {
|
||||
return Math.round(inPetabytes());
|
||||
}
|
||||
|
||||
/** Returns the value of this data size measured in whole {@link DataSizeUnit#PEBIBYTES}. */
|
||||
public long inWholePebibytes() {
|
||||
return Math.round(inPebibytes());
|
||||
}
|
||||
|
||||
/** Returns a new data size with the given unit and this value converted to the given unit. */
|
||||
public DataSize convertTo(DataSizeUnit other) {
|
||||
return new DataSize(convertValueTo(other), other);
|
||||
}
|
||||
|
||||
/** Returns the value of this data size converted to the given unit. */
|
||||
public double convertValueTo(DataSizeUnit other) {
|
||||
return value * unit.getBytes() / other.getBytes();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void accept(ValueVisitor visitor) {
|
||||
visitor.visitDataSize(this);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public <T> T accept(ValueConverter<T> converter) {
|
||||
return converter.convertDataSize(this);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public PClassInfo<?> getClassInfo() {
|
||||
return PClassInfo.DataSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof DataSize)) return false;
|
||||
|
||||
var other = (DataSize) obj;
|
||||
return convertValueTo(DataSizeUnit.BYTES) == other.convertValueTo(DataSizeUnit.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Double.hashCode(convertValueTo(DataSizeUnit.BYTES));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MathUtils.isMathematicalInteger(value) ? (long) value + "." + unit : value + "." + unit;
|
||||
}
|
||||
}
|
||||
92
pkl-core/src/main/java/org/pkl/core/DataSizeUnit.java
Normal file
92
pkl-core/src/main/java/org/pkl/core/DataSizeUnit.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/**
|
||||
* The unit of a {@link DataSize}. In Pkl, data size units are represented as String {@link
|
||||
* #getSymbol() symbols}.
|
||||
*/
|
||||
public strictfp enum DataSizeUnit {
|
||||
BYTES(1, "b"),
|
||||
KILOBYTES(1000, "kb"),
|
||||
KIBIBYTES(1024, "kib"),
|
||||
MEGABYTES(1000 * 1000, "mb"),
|
||||
MEBIBYTES(1024 * 1024, "mib"),
|
||||
GIGABYTES(1000 * 1000 * 1000, "gb"),
|
||||
GIBIBYTES(1024 * 1024 * 1024, "gib"),
|
||||
TERABYTES(1000L * 1000 * 1000 * 1000, "tb"),
|
||||
TEBIBYTES(1024L * 1024 * 1024 * 1024, "tib"),
|
||||
PETABYTES(1000L * 1000 * 1000 * 1000 * 1000, "pb"),
|
||||
PEBIBYTES(1024L * 1024 * 1024 * 1024 * 1024, "pib");
|
||||
|
||||
private final long bytes;
|
||||
|
||||
private final String symbol;
|
||||
|
||||
DataSizeUnit(long bytes, String symbol) {
|
||||
this.bytes = bytes;
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unit with the given symbol, or {@code null} if no unit with the given symbol
|
||||
* exists.
|
||||
*/
|
||||
public static @Nullable DataSizeUnit parse(String symbol) {
|
||||
switch (symbol) {
|
||||
case "b":
|
||||
return BYTES;
|
||||
case "kb":
|
||||
return KILOBYTES;
|
||||
case "kib":
|
||||
return KIBIBYTES;
|
||||
case "mb":
|
||||
return MEGABYTES;
|
||||
case "mib":
|
||||
return MEBIBYTES;
|
||||
case "gb":
|
||||
return GIGABYTES;
|
||||
case "gib":
|
||||
return GIBIBYTES;
|
||||
case "tb":
|
||||
return TERABYTES;
|
||||
case "tib":
|
||||
return TEBIBYTES;
|
||||
case "pb":
|
||||
return PETABYTES;
|
||||
case "pib":
|
||||
return PEBIBYTES;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the String symbol of this unit. */
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/** Returns the conversion factor from this unit to bytes. */
|
||||
public long getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
233
pkl-core/src/main/java/org/pkl/core/Duration.java
Normal file
233
pkl-core/src/main/java/org/pkl/core/Duration.java
Normal file
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import static org.pkl.core.DurationUnit.*;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.pkl.core.util.DurationUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Java representation of a {@code pkl.base#Duration} value. */
|
||||
public final strictfp class Duration implements Value {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final double value;
|
||||
private final DurationUnit unit;
|
||||
|
||||
/** Constructs a new duration with the given value and unit. */
|
||||
public Duration(double value, DurationUnit unit) {
|
||||
this.value = value;
|
||||
this.unit = Objects.requireNonNull(unit, "unit");
|
||||
}
|
||||
|
||||
/** Constructs a new duration with the given value and unit {@link DurationUnit#NANOS}. */
|
||||
public static Duration ofNanos(double value) {
|
||||
return new Duration(value, NANOS);
|
||||
}
|
||||
|
||||
/** Constructs a new duration with the given value and unit {@link DurationUnit#MICROS}. */
|
||||
public static Duration ofMicros(double value) {
|
||||
return new Duration(value, MICROS);
|
||||
}
|
||||
|
||||
/** Constructs a new duration with the given value and unit {@link DurationUnit#MILLIS}. */
|
||||
public static Duration ofMillis(double value) {
|
||||
return new Duration(value, MILLIS);
|
||||
}
|
||||
|
||||
/** Constructs a new duration with the given value and unit {@link DurationUnit#SECONDS}. */
|
||||
public static Duration ofSeconds(double value) {
|
||||
return new Duration(value, SECONDS);
|
||||
}
|
||||
|
||||
/** Constructs a new duration with the given value and unit {@link DurationUnit#MINUTES}. */
|
||||
public static Duration ofMinutes(double value) {
|
||||
return new Duration(value, MINUTES);
|
||||
}
|
||||
|
||||
/** Constructs a new duration with the given value and unit {@link DurationUnit#HOURS}. */
|
||||
public static Duration ofHours(double value) {
|
||||
return new Duration(value, HOURS);
|
||||
}
|
||||
|
||||
/** Constructs a new duration with the given value and unit {@link DurationUnit#DAYS}. */
|
||||
public static Duration ofDays(double value) {
|
||||
return new Duration(value, DAYS);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration. The value is relative to the unit and may be negative. */
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns an ISO 8601 representation of this duration. */
|
||||
public String toIsoString() {
|
||||
return DurationUtils.toIsoString(value, unit);
|
||||
}
|
||||
|
||||
/** Returns the unit of this duration. */
|
||||
public DurationUnit getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in {@link DurationUnit#NANOS}. */
|
||||
public double inNanos() {
|
||||
return convertValueTo(NANOS);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in {@link DurationUnit#MICROS}. */
|
||||
public double inMicros() {
|
||||
return convertValueTo(MICROS);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in {@link DurationUnit#MILLIS}. */
|
||||
public double inMillis() {
|
||||
return convertValueTo(MILLIS);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in {@link DurationUnit#SECONDS}. */
|
||||
public double inSeconds() {
|
||||
return convertValueTo(SECONDS);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in {@link DurationUnit#MINUTES}. */
|
||||
public double inMinutes() {
|
||||
return convertValueTo(MINUTES);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in {@link DurationUnit#HOURS}. */
|
||||
public double inHours() {
|
||||
return convertValueTo(HOURS);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in {@link DurationUnit#DAYS}. */
|
||||
public double inDays() {
|
||||
return convertValueTo(DAYS);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in whole {@link DurationUnit#NANOS}. */
|
||||
public long inWholeNanos() {
|
||||
return Math.round(inNanos());
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in whole {@link DurationUnit#MICROS}. */
|
||||
public long inWholeMicros() {
|
||||
return Math.round(inMicros());
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in whole {@link DurationUnit#MILLIS}. */
|
||||
public long inWholeMillis() {
|
||||
return Math.round(inMillis());
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in whole {@link DurationUnit#SECONDS}. */
|
||||
public long inWholeSeconds() {
|
||||
return Math.round(inSeconds());
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in whole {@link DurationUnit#MINUTES}. */
|
||||
public long inWholeMinutes() {
|
||||
return Math.round(inMinutes());
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in whole {@link DurationUnit#HOURS}. */
|
||||
public long inWholeHours() {
|
||||
return Math.round(inHours());
|
||||
}
|
||||
|
||||
/** Returns the value of this duration measured in whole {@link DurationUnit#DAYS}. */
|
||||
public long inWholeDays() {
|
||||
return Math.round(inDays());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this duration to a {@link java.time.Duration}. If {@link #getValue()} is NaN,
|
||||
* +/-Infinity or too large to fit into {@link java.time.Duration}, {@link ArithmeticException} is
|
||||
* thrown.
|
||||
*/
|
||||
public java.time.Duration toJavaDuration() {
|
||||
if (!Double.isFinite(value)) {
|
||||
throw new ArithmeticException(
|
||||
"Cannot convert Pkl duration `" + this + "` to `java.time.Duration`.");
|
||||
}
|
||||
|
||||
var l = (long) value;
|
||||
if (l == value) {
|
||||
// `value` is a mathematical integer that fits into a long.
|
||||
// Hence this duration is easy to convert without risk of rounding errors.
|
||||
// Throws ArithmeticException if this duration doesn't fit into java.time.Duration (e.g.,
|
||||
// Long.MAX_VALUE days).
|
||||
return java.time.Duration.of(l, unit.toChronoUnit());
|
||||
}
|
||||
|
||||
var seconds = convertValueTo(DurationUnit.SECONDS);
|
||||
var secondsPart = (long) seconds;
|
||||
var nanosPart = (long) ((seconds - secondsPart) * 1_000_000_000);
|
||||
// If `seconds` is infinite or too large to fit into java.time.Duration,
|
||||
// this throws ArithmeticException because one the following holds:
|
||||
// secondsPart == Long.MAX_VALUE && nanosPart >= 1_000_000_000
|
||||
// secondsPart == Long.MIN_VALUE && nanosPart <= -1_000_000_000.
|
||||
return java.time.Duration.ofSeconds(secondsPart, nanosPart);
|
||||
}
|
||||
|
||||
/** Returns a new duration with the given unit and this value converted to the given unit. */
|
||||
public Duration convertTo(DurationUnit other) {
|
||||
return new Duration(convertValueTo(other), other);
|
||||
}
|
||||
|
||||
/** Returns the value of this duration converted to the given unit. */
|
||||
public double convertValueTo(DurationUnit other) {
|
||||
return value * unit.getNanos() / other.getNanos();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void accept(ValueVisitor visitor) {
|
||||
visitor.visitDuration(this);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public <T> T accept(ValueConverter<T> converter) {
|
||||
return converter.convertDuration(this);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public PClassInfo<?> getClassInfo() {
|
||||
return PClassInfo.Duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Duration)) return false;
|
||||
|
||||
var other = (Duration) obj;
|
||||
return convertValueTo(DurationUnit.NANOS) == other.convertValueTo(DurationUnit.NANOS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Double.hashCode(convertValueTo(DurationUnit.NANOS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return DurationUtils.toPklString(value, unit);
|
||||
}
|
||||
}
|
||||
127
pkl-core/src/main/java/org/pkl/core/DurationUnit.java
Normal file
127
pkl-core/src/main/java/org/pkl/core/DurationUnit.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/**
|
||||
* The unit of a {@link Duration}. In Pkl, duration units are represented as String {@link
|
||||
* #getSymbol() symbols}.
|
||||
*/
|
||||
public enum DurationUnit {
|
||||
NANOS(1, "ns"),
|
||||
MICROS(1000, "us"),
|
||||
MILLIS(1000 * 1000, "ms"),
|
||||
SECONDS(1000 * 1000 * 1000, "s"),
|
||||
MINUTES(1000L * 1000 * 1000 * 60, "min"),
|
||||
HOURS(1000L * 1000 * 1000 * 60 * 60, "h"),
|
||||
DAYS(1000L * 1000 * 1000 * 60 * 60 * 24, "d");
|
||||
|
||||
private final long nanos;
|
||||
|
||||
private final String symbol;
|
||||
|
||||
DurationUnit(long nanos, String symbol) {
|
||||
this.nanos = nanos;
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unit with the given symbol, or {@code null} if no unit with the given symbol
|
||||
* exists.
|
||||
*/
|
||||
public static @Nullable DurationUnit parse(String symbol) {
|
||||
switch (symbol) {
|
||||
case "ns":
|
||||
return NANOS;
|
||||
case "us":
|
||||
return MICROS;
|
||||
case "ms":
|
||||
return MILLIS;
|
||||
case "s":
|
||||
return SECONDS;
|
||||
case "min":
|
||||
return MINUTES;
|
||||
case "h":
|
||||
return HOURS;
|
||||
case "d":
|
||||
return DAYS;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the string symbol of this unit. */
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/** Returns the conversion factor from this unit to nanoseconds. */
|
||||
public long getNanos() {
|
||||
return nanos;
|
||||
}
|
||||
|
||||
/** Converts this unit to a {@link java.time.temporal.ChronoUnit}. */
|
||||
public ChronoUnit toChronoUnit() {
|
||||
switch (this) {
|
||||
case NANOS:
|
||||
return ChronoUnit.NANOS;
|
||||
case MICROS:
|
||||
return ChronoUnit.MICROS;
|
||||
case MILLIS:
|
||||
return ChronoUnit.MILLIS;
|
||||
case SECONDS:
|
||||
return ChronoUnit.SECONDS;
|
||||
case MINUTES:
|
||||
return ChronoUnit.MINUTES;
|
||||
case HOURS:
|
||||
return ChronoUnit.HOURS;
|
||||
case DAYS:
|
||||
return ChronoUnit.DAYS;
|
||||
default:
|
||||
throw new AssertionError("Unknown duration unit: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts this unit to a {@link java.util.concurrent.TimeUnit}. */
|
||||
public TimeUnit toTimeUnit() {
|
||||
switch (this) {
|
||||
case NANOS:
|
||||
return TimeUnit.NANOSECONDS;
|
||||
case MICROS:
|
||||
return TimeUnit.MICROSECONDS;
|
||||
case MILLIS:
|
||||
return TimeUnit.MILLISECONDS;
|
||||
case SECONDS:
|
||||
return TimeUnit.SECONDS;
|
||||
case MINUTES:
|
||||
return TimeUnit.MINUTES;
|
||||
case HOURS:
|
||||
return TimeUnit.HOURS;
|
||||
case DAYS:
|
||||
return TimeUnit.DAYS;
|
||||
default:
|
||||
throw new AssertionError("Unknown duration unit: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
216
pkl-core/src/main/java/org/pkl/core/Evaluator.java
Normal file
216
pkl-core/src/main/java/org/pkl/core/Evaluator.java
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.Map;
|
||||
import org.pkl.core.runtime.TestResults;
|
||||
import org.pkl.core.runtime.VmEvalException;
|
||||
|
||||
/**
|
||||
* Evaluates a Pkl module through different modes of evaluation. Throws {@link VmEvalException} if
|
||||
* an error occurs during evaluation.
|
||||
*
|
||||
* <p>Evaluated modules, and modules imported by them, are cached based on their origin. This is
|
||||
* important to guarantee consistent evaluation results, for example when the same module is used by
|
||||
* multiple other modules. To reset the cache, {@link #close()} the current instance and create a
|
||||
* new one.
|
||||
*
|
||||
* <p>Construct an evaluator through {@link EvaluatorBuilder}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public interface Evaluator extends AutoCloseable {
|
||||
|
||||
/** Shorthand for {@code EvaluatorBuilder.preconfigured().build()}. */
|
||||
static Evaluator preconfigured() {
|
||||
return EvaluatorBuilder.preconfigured().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the module, returning the Java representation of the module object.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
PModule evaluate(ModuleSource moduleSource);
|
||||
|
||||
/**
|
||||
* Evaluates a module's {@code output.text} property.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
String evaluateOutputText(ModuleSource moduleSource);
|
||||
|
||||
/**
|
||||
* Evaluates a module's {@code output.value} property.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
Object evaluateOutputValue(ModuleSource moduleSource);
|
||||
|
||||
/**
|
||||
* Evaluates a module's {@code output.files} property.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
Map<String, FileOutput> evaluateOutputFiles(ModuleSource moduleSource);
|
||||
|
||||
/**
|
||||
* Evaluates the Pkl expression represented as {@code expression}, returning the Java
|
||||
* representation of the result.
|
||||
*
|
||||
* <p>The following table describes how Pkl types are represented in Java:
|
||||
*
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th>Pkl type</th>
|
||||
* <th>Java type</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <td>Null</td>
|
||||
* <td>{@link PNull}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>String</td>
|
||||
* <td>{@link String}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Boolean</td>
|
||||
* <td>{@link Boolean}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Int</td>
|
||||
* <td>{@link Long}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Float</td>
|
||||
* <td>{@link Double}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Typed, Dynamic</td>
|
||||
* <td>{@link PObject} ({@link PModule} if the object is a module)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Mapping, Map</td>
|
||||
* <td>{@link Map}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Listing, List</td>
|
||||
* <td>{@link java.util.List}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Set</td>
|
||||
* <td>{@link java.util.Set}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Pair</td>
|
||||
* <td>{@link Pair}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Regex</td>
|
||||
* <td>{@link java.util.regex.Pattern}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>DataSize</td>
|
||||
* <td>{@link DataSize}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Duration</td>
|
||||
* <td>{@link Duration}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Class</td>
|
||||
* <td>{@link PClass}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>TypeAlias</td>
|
||||
* <td>{@link TypeAlias}</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
* <p>The following Pkl types have no Java representation, and an error is thrown if an expression
|
||||
* computes to a value of these types:
|
||||
*
|
||||
* <ul>
|
||||
* <li>IntSeq
|
||||
* <li>Function
|
||||
* </ul>
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
Object evaluateExpression(ModuleSource moduleSource, String expression);
|
||||
|
||||
/**
|
||||
* Evaluates the Pkl expression, returning the stringified result.
|
||||
*
|
||||
* <p>This is equivalent to wrapping the expression with {@code .toString()}
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
String evaluateExpressionString(ModuleSource moduleSource, String expression);
|
||||
|
||||
/**
|
||||
* Evalautes the module's schema, which describes the properties, methods, and classes of a
|
||||
* module.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
ModuleSchema evaluateSchema(ModuleSource moduleSource);
|
||||
|
||||
/**
|
||||
* Evaluates the module's {@code output.value} property, and validates that its type matches the
|
||||
* provided class info.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
<T> T evaluateOutputValueAs(ModuleSource moduleSource, PClassInfo<T> classInfo);
|
||||
|
||||
/**
|
||||
* Runs tests within the module, and returns the test results.
|
||||
*
|
||||
* <p>This requires that the target module be a test module; it must either amend or extend module
|
||||
* {@code "pkl:test"}. Otherwise, a type mismatch error is thrown.
|
||||
*
|
||||
* <p>This method will write possibly {@code pcf-expected.pkl} and {@code pcf-actual.pcf} files as
|
||||
* a sibling of the test module. The {@code overwrite} parameter causes the evaluator to overwrite
|
||||
* {@code pcf-expected.pkl} files if they currently exist.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
TestResults evaluateTest(ModuleSource moduleSource, boolean overwrite);
|
||||
|
||||
/**
|
||||
* Releases all resources held by this evaluator. If an {@code evaluate} method is currently
|
||||
* executing, this method blocks until cancellation of that execution has completed.
|
||||
*
|
||||
* <p>Once an evaluator has been closed, it can no longer be used, and calling {@code evaluate}
|
||||
* methods will throw {@link IllegalStateException}. However, objects previously returned by
|
||||
* {@code evaluate} methods remain valid.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
482
pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java
Normal file
482
pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java
Normal file
@@ -0,0 +1,482 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import com.oracle.truffle.api.TruffleOptions;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.SecurityManagers.StandardBuilder;
|
||||
import org.pkl.core.module.ModuleKeyFactories;
|
||||
import org.pkl.core.module.ModuleKeyFactory;
|
||||
import org.pkl.core.module.ModulePathResolver;
|
||||
import org.pkl.core.project.DeclaredDependencies;
|
||||
import org.pkl.core.project.Project;
|
||||
import org.pkl.core.resource.ResourceReader;
|
||||
import org.pkl.core.resource.ResourceReaders;
|
||||
import org.pkl.core.runtime.LoggerImpl;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** A builder for an {@link Evaluator}. Can be reused to build multiple evaluators. */
|
||||
@SuppressWarnings({"UnusedReturnValue", "unused"})
|
||||
public final class EvaluatorBuilder {
|
||||
private final StandardBuilder securityManagerBuilder = SecurityManagers.standardBuilder();
|
||||
|
||||
private @Nullable SecurityManager securityManager;
|
||||
|
||||
private Logger logger = Loggers.noop();
|
||||
|
||||
private final List<ModuleKeyFactory> moduleKeyFactories = new ArrayList<>();
|
||||
|
||||
private final List<ResourceReader> resourceReaders = new ArrayList<>();
|
||||
|
||||
private final Map<String, String> environmentVariables = new HashMap<>();
|
||||
|
||||
private final Map<String, String> externalProperties = new HashMap<>();
|
||||
|
||||
private java.time.@Nullable Duration timeout;
|
||||
|
||||
private @Nullable Path moduleCacheDir = IoUtils.getDefaultModuleCacheDir();
|
||||
|
||||
private @Nullable String outputFormat;
|
||||
|
||||
private @Nullable StackFrameTransformer stackFrameTransformer;
|
||||
|
||||
private @Nullable DeclaredDependencies dependencies;
|
||||
|
||||
private EvaluatorBuilder() {}
|
||||
|
||||
/**
|
||||
* Creates a builder preconfigured with:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link SecurityManagers#defaultAllowedModules}
|
||||
* <li>{@link SecurityManagers#defaultAllowedResources}
|
||||
* <li>{@link Loggers#noop()}
|
||||
* <li>{@link ModuleKeyFactories#standardLibrary}
|
||||
* <li>{@link ModuleKeyFactories#classPath}
|
||||
* <li>{@link ModuleKeyFactories#fromServiceProviders}
|
||||
* <li>{@link ModuleKeyFactories#file}
|
||||
* <li>{@link ModuleKeyFactories#pkg}
|
||||
* <li>{@link ModuleKeyFactories#projectpackage}
|
||||
* <li>{@link ModuleKeyFactories#genericUrl}
|
||||
* <li>{@link ResourceReaders#environmentVariable}
|
||||
* <li>{@link ResourceReaders#externalProperty}
|
||||
* <li>{@link ResourceReaders#classPath}
|
||||
* <li>{@link ResourceReaders#file}
|
||||
* <li>{@link ResourceReaders#http}
|
||||
* <li>{@link ResourceReaders#https}
|
||||
* <li>{@link ResourceReaders#pkg}
|
||||
* <li>{@link ResourceReaders#projectpackage}
|
||||
* <li>{@link System#getProperties}
|
||||
* </ul>
|
||||
*/
|
||||
public static EvaluatorBuilder preconfigured() {
|
||||
EvaluatorBuilder builder = new EvaluatorBuilder();
|
||||
|
||||
builder
|
||||
.setStackFrameTransformer(StackFrameTransformers.defaultTransformer)
|
||||
.setAllowedModules(SecurityManagers.defaultAllowedModules)
|
||||
.setAllowedResources(SecurityManagers.defaultAllowedResources)
|
||||
.addResourceReader(ResourceReaders.environmentVariable())
|
||||
.addResourceReader(ResourceReaders.externalProperty())
|
||||
.addResourceReader(ResourceReaders.file())
|
||||
.addResourceReader(ResourceReaders.http())
|
||||
.addResourceReader(ResourceReaders.https())
|
||||
.addResourceReader(ResourceReaders.pkg())
|
||||
.addResourceReader(ResourceReaders.projectpackage())
|
||||
.addModuleKeyFactory(ModuleKeyFactories.standardLibrary);
|
||||
|
||||
if (!TruffleOptions.AOT) {
|
||||
// AOT does not support class loader API
|
||||
var classLoader = EvaluatorBuilder.class.getClassLoader();
|
||||
builder
|
||||
.addModuleKeyFactory(ModuleKeyFactories.classPath(classLoader))
|
||||
.addResourceReader(ResourceReaders.classPath(classLoader));
|
||||
|
||||
// only add system properties when running on JVM
|
||||
addSystemProperties(builder);
|
||||
}
|
||||
|
||||
builder
|
||||
.addModuleKeyFactories(ModuleKeyFactories.fromServiceProviders())
|
||||
.addModuleKeyFactory(ModuleKeyFactories.file)
|
||||
.addModuleKeyFactory(ModuleKeyFactories.pkg)
|
||||
.addModuleKeyFactory(ModuleKeyFactories.projectpackage)
|
||||
.addModuleKeyFactory(ModuleKeyFactories.genericUrl)
|
||||
.addEnvironmentVariables(System.getenv());
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static void addSystemProperties(EvaluatorBuilder builder) {
|
||||
builder.addExternalProperties((Map) System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder that is unconfigured. At a minimum, a security manager will need to be set
|
||||
* before building an instance.
|
||||
*/
|
||||
public static EvaluatorBuilder unconfigured() {
|
||||
return new EvaluatorBuilder();
|
||||
}
|
||||
|
||||
/** Sets the given stack frame transformer, replacing any previously set transformer. */
|
||||
public EvaluatorBuilder setStackFrameTransformer(StackFrameTransformer stackFrameTransformer) {
|
||||
this.stackFrameTransformer = stackFrameTransformer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the currently set stack frame transformer. */
|
||||
public @Nullable StackFrameTransformer getStackFrameTransformer() {
|
||||
return stackFrameTransformer;
|
||||
}
|
||||
|
||||
/** Sets the given security manager, replacing any previously set security manager. */
|
||||
public EvaluatorBuilder setSecurityManager(@Nullable SecurityManager manager) {
|
||||
this.securityManager = manager;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluatorBuilder unsetSecurityManager() {
|
||||
this.securityManager = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the currently set security manager. */
|
||||
public @Nullable SecurityManager getSecurityManager() {
|
||||
return securityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the set of URI patterns to be allowed when importing modules.
|
||||
*
|
||||
* @throws IllegalStateException if {@link #setSecurityManager(SecurityManager)} was also called.
|
||||
*/
|
||||
public EvaluatorBuilder setAllowedModules(Collection<Pattern> patterns) {
|
||||
if (securityManager != null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot call both `setSecurityManager` and `setAllowedModules`, because both define security manager settings.");
|
||||
}
|
||||
securityManagerBuilder.setAllowedModules(patterns);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the set of patterns to be allowed when importing modules. */
|
||||
public List<Pattern> getAllowedModules() {
|
||||
return securityManagerBuilder.getAllowedModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the set of URI patterns to be allowed when reading resources.
|
||||
*
|
||||
* @throws IllegalStateException if {@link #setSecurityManager(SecurityManager)} was also called.
|
||||
*/
|
||||
public EvaluatorBuilder setAllowedResources(Collection<Pattern> patterns) {
|
||||
if (securityManager != null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot call both `setSecurityManager` and `setAllowedResources`, because both define security manager settings.");
|
||||
}
|
||||
securityManagerBuilder.setAllowedResources(patterns);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the set of patterns to be allowed when reading resources. */
|
||||
public List<Pattern> getAllowedResources() {
|
||||
return securityManagerBuilder.getAllowedResources();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root directory, which restricts access to file-based modules and resources located
|
||||
* under this directory.
|
||||
*/
|
||||
public EvaluatorBuilder setRootDir(@Nullable Path rootDir) {
|
||||
securityManagerBuilder.setRootDir(rootDir);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the currently set root directory, if set. */
|
||||
public @Nullable Path getRootDir() {
|
||||
return securityManagerBuilder.getRootDir();
|
||||
}
|
||||
|
||||
/** Sets the given logger, replacing any previously set logger. */
|
||||
public EvaluatorBuilder setLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the currently set logger. */
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given module key factory. Factories will be asked to resolve module keys in the order
|
||||
* they have been added to this builder.
|
||||
*/
|
||||
public EvaluatorBuilder addModuleKeyFactory(ModuleKeyFactory factory) {
|
||||
moduleKeyFactories.add(factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given module key factories. Factories will be asked to resolve module keys in the
|
||||
* order they have been added to this builder.
|
||||
*/
|
||||
public EvaluatorBuilder addModuleKeyFactories(Collection<ModuleKeyFactory> factories) {
|
||||
moduleKeyFactories.addAll(factories);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes any existing module key factories, then adds the given factories. */
|
||||
public EvaluatorBuilder setModuleKeyFactories(Collection<ModuleKeyFactory> factories) {
|
||||
moduleKeyFactories.clear();
|
||||
return addModuleKeyFactories(factories);
|
||||
}
|
||||
|
||||
/** Returns the currently set module key factories. */
|
||||
public List<ModuleKeyFactory> getModuleKeyFactories() {
|
||||
return moduleKeyFactories;
|
||||
}
|
||||
|
||||
public EvaluatorBuilder addResourceReader(ResourceReader reader) {
|
||||
resourceReaders.add(reader);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluatorBuilder addResourceReaders(Collection<ResourceReader> readers) {
|
||||
resourceReaders.addAll(readers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluatorBuilder setResourceReaders(Collection<ResourceReader> readers) {
|
||||
resourceReaders.clear();
|
||||
return addResourceReaders(readers);
|
||||
}
|
||||
|
||||
/** Returns the currently set resource readers. */
|
||||
public List<ResourceReader> getResourceReaders() {
|
||||
return resourceReaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given environment variable, overriding any environment variable previously added under
|
||||
* the same name.
|
||||
*
|
||||
* <p>Pkl code can read environment variables with {@code read("env:<NAME>")}.
|
||||
*/
|
||||
public EvaluatorBuilder addEnvironmentVariable(String name, String value) {
|
||||
environmentVariables.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given environment variables, overriding any environment variables previously added
|
||||
* under the same name.
|
||||
*
|
||||
* <p>Pkl code can read environment variables with {@code read("env:<NAME>")}.
|
||||
*/
|
||||
public EvaluatorBuilder addEnvironmentVariables(Map<String, String> envVars) {
|
||||
environmentVariables.putAll(envVars);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes any existing environment variables, then adds the given environment variables. */
|
||||
public EvaluatorBuilder setEnvironmentVariables(Map<String, String> envVars) {
|
||||
environmentVariables.clear();
|
||||
return addEnvironmentVariables(envVars);
|
||||
}
|
||||
|
||||
/** Returns the currently set environment variables. */
|
||||
public Map<String, String> getEnvironmentVariables() {
|
||||
return environmentVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given external property, overriding any property previously set under the same name.
|
||||
*
|
||||
* <p>Pkl code can read external properties with {@code read("prop:<name>")}.
|
||||
*/
|
||||
public EvaluatorBuilder addExternalProperty(String name, String value) {
|
||||
externalProperties.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given external properties, overriding any properties previously set under the same
|
||||
* name.
|
||||
*
|
||||
* <p>Pkl code can read external properties with {@code read("prop:<name>")}.
|
||||
*/
|
||||
public EvaluatorBuilder addExternalProperties(Map<String, String> properties) {
|
||||
externalProperties.putAll(properties);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes any existing external properties, then adds the given properties. */
|
||||
public EvaluatorBuilder setExternalProperties(Map<String, String> properties) {
|
||||
externalProperties.clear();
|
||||
return addExternalProperties(properties);
|
||||
}
|
||||
|
||||
/** Returns the currently set external properties. */
|
||||
public Map<String, String> getExternalProperties() {
|
||||
return externalProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an evaluation timeout to be enforced by the {@link Evaluator}'s {@code evaluate} methods.
|
||||
*/
|
||||
public EvaluatorBuilder setTimeout(java.time.@Nullable Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the currently set evaluation timeout. */
|
||||
public java.time.@Nullable Duration getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the directory where `package:` modules are cached.
|
||||
*
|
||||
* <p>If {@code null}, the module cache is disabled.
|
||||
*/
|
||||
public EvaluatorBuilder setModuleCacheDir(@Nullable Path moduleCacheDir) {
|
||||
this.moduleCacheDir = moduleCacheDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory where `package:` modules are cached. If {@code null}, the module cache is
|
||||
* disabled.
|
||||
*/
|
||||
public @Nullable Path getModuleCacheDir() {
|
||||
return moduleCacheDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired output format, if any.
|
||||
*
|
||||
* <p>By default, modules support the formats described by {@link OutputFormat}. and fall back to
|
||||
* {@link OutputFormat#PCF} if no format is specified.
|
||||
*
|
||||
* <p>Modules that override {@code output.renderer} in their source code may ignore this option or
|
||||
* may support formats other than those described by {@link OutputFormat}. In particular, most
|
||||
* templates ignore this option and always render the same format.
|
||||
*/
|
||||
public EvaluatorBuilder setOutputFormat(@Nullable String outputFormat) {
|
||||
this.outputFormat = outputFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired output format, if any.
|
||||
*
|
||||
* <p>By default, modules support the formats described by {@link OutputFormat}. and fall back to
|
||||
* {@link OutputFormat#PCF} if no format is specified.
|
||||
*
|
||||
* <p>Modules that override {@code output.renderer} in their source code may ignore this option or
|
||||
* may support formats other than those described by {@link OutputFormat}. In particular, most
|
||||
* templates ignore this option and always render the same format.
|
||||
*/
|
||||
public EvaluatorBuilder setOutputFormat(@Nullable OutputFormat outputFormat) {
|
||||
this.outputFormat = outputFormat == null ? null : outputFormat.toString();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the currently set output format, if any. */
|
||||
public @Nullable String getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
/** Sets the project dependencies for the evaluator. */
|
||||
public EvaluatorBuilder setProjectDependencies(DeclaredDependencies dependencies) {
|
||||
this.dependencies = dependencies;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a project, sets its dependencies, and also applies any evaluator settings if set.
|
||||
*
|
||||
* @throws IllegalStateException if {@link #setSecurityManager(SecurityManager)} was also called.
|
||||
*/
|
||||
public EvaluatorBuilder applyFromProject(Project project) {
|
||||
this.dependencies = project.getDependencies();
|
||||
var settings = project.getSettings();
|
||||
if (securityManager != null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot call both `setSecurityManager` and `setProject`, because both define security manager settings. Call `setProjectOnly` if the security manager is desired.");
|
||||
}
|
||||
if (settings.getAllowedModules() != null) {
|
||||
setAllowedModules(settings.getAllowedModules());
|
||||
}
|
||||
if (settings.getAllowedResources() != null) {
|
||||
setAllowedResources(settings.getAllowedResources());
|
||||
}
|
||||
if (settings.getExternalProperties() != null) {
|
||||
setExternalProperties(settings.getExternalProperties());
|
||||
}
|
||||
if (settings.getEnv() != null) {
|
||||
setEnvironmentVariables(settings.getEnv());
|
||||
}
|
||||
if (settings.getTimeout() != null) {
|
||||
setTimeout(settings.getTimeout().toJavaDuration());
|
||||
}
|
||||
if (settings.getModulePath() != null) {
|
||||
// indirectly closed by `ModuleKeyFactories.closeQuietly(builder.moduleKeyFactories)`
|
||||
var modulePathResolver = new ModulePathResolver(settings.getModulePath());
|
||||
addResourceReader(ResourceReaders.modulePath(modulePathResolver));
|
||||
addModuleKeyFactory(ModuleKeyFactories.modulePath(modulePathResolver));
|
||||
}
|
||||
if (settings.getRootDir() != null) {
|
||||
setRootDir(settings.getRootDir());
|
||||
}
|
||||
if (Boolean.TRUE.equals(settings.isNoCache())) {
|
||||
setModuleCacheDir(null);
|
||||
} else if (settings.getModuleCacheDir() != null) {
|
||||
setModuleCacheDir(settings.getModuleCacheDir());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Evaluator build() {
|
||||
if (securityManager == null) {
|
||||
securityManager = securityManagerBuilder.build();
|
||||
}
|
||||
|
||||
if (stackFrameTransformer == null) {
|
||||
throw new IllegalStateException("No stack frame transformer set.");
|
||||
}
|
||||
|
||||
return new EvaluatorImpl(
|
||||
stackFrameTransformer,
|
||||
securityManager,
|
||||
new LoggerImpl(logger, stackFrameTransformer),
|
||||
// copy to shield against subsequent modification through builder
|
||||
new ArrayList<>(moduleKeyFactories),
|
||||
new ArrayList<>(resourceReaders),
|
||||
new HashMap<>(environmentVariables),
|
||||
new HashMap<>(externalProperties),
|
||||
timeout,
|
||||
moduleCacheDir,
|
||||
dependencies,
|
||||
outputFormat);
|
||||
}
|
||||
}
|
||||
439
pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java
Normal file
439
pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java
Normal file
@@ -0,0 +1,439 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import com.oracle.truffle.api.TruffleStackTrace;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.pkl.core.ast.ConstantValueNode;
|
||||
import org.pkl.core.ast.internal.ToStringNodeGen;
|
||||
import org.pkl.core.module.ModuleKeyFactory;
|
||||
import org.pkl.core.module.ProjectDependenciesManager;
|
||||
import org.pkl.core.packages.PackageResolver;
|
||||
import org.pkl.core.project.DeclaredDependencies;
|
||||
import org.pkl.core.resource.ResourceReader;
|
||||
import org.pkl.core.runtime.BaseModule;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
import org.pkl.core.runtime.ModuleResolver;
|
||||
import org.pkl.core.runtime.ResourceManager;
|
||||
import org.pkl.core.runtime.TestResults;
|
||||
import org.pkl.core.runtime.TestRunner;
|
||||
import org.pkl.core.runtime.VmContext;
|
||||
import org.pkl.core.runtime.VmException;
|
||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||
import org.pkl.core.runtime.VmLanguage;
|
||||
import org.pkl.core.runtime.VmMapping;
|
||||
import org.pkl.core.runtime.VmNull;
|
||||
import org.pkl.core.runtime.VmStackOverflowException;
|
||||
import org.pkl.core.runtime.VmTyped;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.runtime.VmValue;
|
||||
import org.pkl.core.runtime.VmValueRenderer;
|
||||
import org.pkl.core.util.ErrorMessages;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public class EvaluatorImpl implements Evaluator {
|
||||
protected final StackFrameTransformer frameTransformer;
|
||||
protected final ModuleResolver moduleResolver;
|
||||
protected final Context polyglotContext;
|
||||
protected final @Nullable Duration timeout;
|
||||
protected final @Nullable ScheduledExecutorService timeoutExecutor;
|
||||
protected final SecurityManager securityManager;
|
||||
protected final BufferedLogger logger;
|
||||
protected final PackageResolver packageResolver;
|
||||
private final VmValueRenderer vmValueRenderer = VmValueRenderer.singleLine(1000);
|
||||
|
||||
public EvaluatorImpl(
|
||||
StackFrameTransformer transformer,
|
||||
SecurityManager manager,
|
||||
Logger logger,
|
||||
Collection<ModuleKeyFactory> factories,
|
||||
Collection<ResourceReader> readers,
|
||||
Map<String, String> environmentVariables,
|
||||
Map<String, String> externalProperties,
|
||||
@Nullable Duration timeout,
|
||||
@Nullable Path moduleCacheDir,
|
||||
@Nullable DeclaredDependencies projectDependencies,
|
||||
@Nullable String outputFormat) {
|
||||
|
||||
securityManager = manager;
|
||||
frameTransformer = transformer;
|
||||
moduleResolver = new ModuleResolver(factories);
|
||||
this.logger = new BufferedLogger(logger);
|
||||
packageResolver = PackageResolver.getInstance(securityManager, moduleCacheDir);
|
||||
polyglotContext =
|
||||
VmUtils.createContext(
|
||||
() -> {
|
||||
VmContext vmContext = VmContext.get(null);
|
||||
vmContext.initialize(
|
||||
new VmContext.Holder(
|
||||
transformer,
|
||||
manager,
|
||||
moduleResolver,
|
||||
new ResourceManager(manager, readers),
|
||||
this.logger,
|
||||
environmentVariables,
|
||||
externalProperties,
|
||||
moduleCacheDir,
|
||||
outputFormat,
|
||||
packageResolver,
|
||||
projectDependencies == null
|
||||
? null
|
||||
: new ProjectDependenciesManager(projectDependencies)));
|
||||
});
|
||||
this.timeout = timeout;
|
||||
// NOTE: would probably make sense to share executor between evaluators
|
||||
// (blocked on https://github.com/oracle/graal/issues/1230)
|
||||
timeoutExecutor =
|
||||
timeout == null
|
||||
? null
|
||||
: Executors.newSingleThreadScheduledExecutor(
|
||||
runnable -> {
|
||||
Thread t = new Thread(runnable, "Pkl Timeout Scheduler");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public PModule evaluate(ModuleSource moduleSource) {
|
||||
return doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
module.force(false);
|
||||
return (PModule) module.export();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String evaluateOutputText(ModuleSource moduleSource) {
|
||||
return doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
var output = (VmTyped) VmUtils.readMember(module, Identifier.OUTPUT);
|
||||
return VmUtils.readTextProperty(output);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluateOutputValue(ModuleSource moduleSource) {
|
||||
return doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
var output = (VmTyped) VmUtils.readMember(module, Identifier.OUTPUT);
|
||||
var value = VmUtils.readMember(output, Identifier.VALUE);
|
||||
if (value instanceof VmValue) {
|
||||
var vmValue = (VmValue) value;
|
||||
vmValue.force(false);
|
||||
return vmValue.export();
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, FileOutput> evaluateOutputFiles(ModuleSource moduleSource) {
|
||||
return doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
var output = (VmTyped) VmUtils.readMember(module, Identifier.OUTPUT);
|
||||
var filesOrNull = VmUtils.readMember(output, Identifier.FILES);
|
||||
if (filesOrNull instanceof VmNull) {
|
||||
return Map.of();
|
||||
}
|
||||
var files = (VmMapping) filesOrNull;
|
||||
var result = new LinkedHashMap<String, FileOutput>();
|
||||
files.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
assert member.isEntry();
|
||||
result.put((String) key, new FileOutputImpl(this, (VmTyped) value));
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluateExpression(ModuleSource moduleSource, String expression) {
|
||||
// optimization: if the expression is `output.text` or `output.value` (the common cases), read
|
||||
// members directly instead of creating new truffle nodes.
|
||||
if (expression.equals("output.text")) {
|
||||
return evaluateOutputText(moduleSource);
|
||||
}
|
||||
if (expression.equals("output.value")) {
|
||||
return evaluateOutputValue(moduleSource);
|
||||
}
|
||||
return doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
var expressionResult =
|
||||
VmUtils.evaluateExpression(module, expression, securityManager, moduleResolver);
|
||||
if (expressionResult instanceof VmValue) {
|
||||
var value = (VmValue) expressionResult;
|
||||
value.force(false);
|
||||
return value.export();
|
||||
}
|
||||
return expressionResult;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String evaluateExpressionString(ModuleSource moduleSource, String expression) {
|
||||
// optimization: if the expression is `output.text` (the common case), read members
|
||||
// directly
|
||||
// instead of creating new truffle nodes.
|
||||
if (expression.equals("output.text")) {
|
||||
return evaluateOutputText(moduleSource);
|
||||
}
|
||||
return doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
var expressionResult =
|
||||
VmUtils.evaluateExpression(module, expression, securityManager, moduleResolver);
|
||||
var toStringNode =
|
||||
ToStringNodeGen.create(
|
||||
VmUtils.unavailableSourceSection(), new ConstantValueNode(expressionResult));
|
||||
var stringified = toStringNode.executeGeneric(VmUtils.createEmptyMaterializedFrame());
|
||||
return (String) stringified;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleSchema evaluateSchema(ModuleSource moduleSource) {
|
||||
return doEvaluate(moduleSource, (module) -> module.getModuleInfo().getModuleSchema(module));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestResults evaluateTest(ModuleSource moduleSource, boolean overwrite) {
|
||||
return doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
var testRunner = new TestRunner(logger, frameTransformer, overwrite);
|
||||
return testRunner.run(module);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T evaluateOutputValueAs(ModuleSource moduleSource, PClassInfo<T> classInfo) {
|
||||
return doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
var output = (VmTyped) VmUtils.readMember(module, Identifier.OUTPUT);
|
||||
var value = VmUtils.readMember(output, Identifier.VALUE);
|
||||
var valueClassInfo = VmUtils.getClass(value).getPClassInfo();
|
||||
if (valueClassInfo.equals(classInfo)) {
|
||||
if (value instanceof VmValue) {
|
||||
var vmValue = (VmValue) value;
|
||||
vmValue.force(false);
|
||||
//noinspection unchecked
|
||||
return (T) vmValue.export();
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (T) value;
|
||||
}
|
||||
throw moduleOutputValueTypeMismatch(module, classInfo, value, output);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// if currently executing, blocks until cancellation has completed (see
|
||||
// https://github.com/oracle/graal/issues/1230)
|
||||
polyglotContext.close(true);
|
||||
try {
|
||||
packageResolver.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
if (timeoutExecutor != null) {
|
||||
timeoutExecutor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
String evaluateOutputText(VmTyped fileOutput) {
|
||||
return doEvaluate(() -> VmUtils.readTextProperty(fileOutput));
|
||||
}
|
||||
|
||||
private <T> T doEvaluate(Supplier<T> supplier) {
|
||||
@Nullable TimeoutTask timeoutTask = null;
|
||||
logger.clear();
|
||||
if (timeout != null) {
|
||||
assert timeoutExecutor != null;
|
||||
timeoutTask = new TimeoutTask();
|
||||
timeoutExecutor.schedule(timeoutTask, timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
polyglotContext.enter();
|
||||
T evalResult;
|
||||
// There is a chance that a timeout is triggered just when evaluation completes on its own.
|
||||
// In this case, if evaluation completed normally or with an expected exception (VmException),
|
||||
// nevertheless report the timeout. (For technical reasons, timeout implies that evaluator is
|
||||
// being closed,
|
||||
// which needs to be communicated to the client.)
|
||||
// If evaluation completed with an unexpected exception (translated to PklBugException) or
|
||||
// error,
|
||||
// report that instead of the timeout so as not to swallow a fundamental problem.
|
||||
try {
|
||||
evalResult = supplier.get();
|
||||
} catch (VmStackOverflowException e) {
|
||||
if (isPklBug(e)) {
|
||||
throw new VmExceptionBuilder()
|
||||
.bug("Stack overflow")
|
||||
.withCause(e.getCause())
|
||||
.build()
|
||||
.toPklException(frameTransformer);
|
||||
}
|
||||
handleTimeout(timeoutTask);
|
||||
throw e.toPklException(frameTransformer);
|
||||
} catch (VmException e) {
|
||||
handleTimeout(timeoutTask);
|
||||
throw e.toPklException(frameTransformer);
|
||||
} catch (Exception e) {
|
||||
throw new PklBugException(e);
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
if (!(e.getCause() instanceof VmException)) {
|
||||
throw new PklBugException(e);
|
||||
}
|
||||
var pklException = ((VmException) e.getCause()).toPklException(frameTransformer);
|
||||
var error = new ExceptionInInitializerError(pklException);
|
||||
error.setStackTrace(e.getStackTrace());
|
||||
throw new PklBugException(error);
|
||||
} catch (ThreadDeath e) {
|
||||
if (e.getClass()
|
||||
.getName()
|
||||
.equals("com.oracle.truffle.polyglot.PolyglotEngineImpl$CancelExecution")) {
|
||||
// Truffle cancelled evaluation in response to polyglotContext.close(true) triggered by
|
||||
// TimeoutTask
|
||||
handleTimeout(timeoutTask);
|
||||
throw PklBugException.unreachableCode();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
polyglotContext.leave();
|
||||
} catch (IllegalStateException ignored) {
|
||||
// happens if evaluation has already been cancelled with polyglotContext.close(true)
|
||||
}
|
||||
}
|
||||
|
||||
handleTimeout(timeoutTask);
|
||||
return evalResult;
|
||||
}
|
||||
|
||||
protected <T> T doEvaluate(ModuleSource moduleSource, Function<VmTyped, T> doEvaluate) {
|
||||
return doEvaluate(
|
||||
() -> {
|
||||
var moduleKey = moduleResolver.resolve(moduleSource);
|
||||
var module = VmLanguage.get(null).loadModule(moduleKey);
|
||||
return doEvaluate.apply(module);
|
||||
});
|
||||
}
|
||||
|
||||
private void handleTimeout(@Nullable TimeoutTask timeoutTask) {
|
||||
if (timeoutTask == null || timeoutTask.cancel()) return;
|
||||
|
||||
assert timeout != null;
|
||||
// TODO: use a different exception type so that clients can tell apart timeouts from other
|
||||
// errors
|
||||
throw new PklException(
|
||||
ErrorMessages.create(
|
||||
"evaluationTimedOut", (timeout.getSeconds() + timeout.getNano() / 1_000_000_000d)));
|
||||
}
|
||||
|
||||
private VmException moduleOutputValueTypeMismatch(
|
||||
VmTyped module, PClassInfo<?> expectedClassInfo, Object value, VmTyped output) {
|
||||
var moduleUri = module.getModuleInfo().getModuleKey().getUri();
|
||||
var builder =
|
||||
new VmExceptionBuilder()
|
||||
.evalError(
|
||||
"invalidModuleOutputValue",
|
||||
expectedClassInfo.getDisplayName(),
|
||||
VmUtils.getClass(value).getPClassInfo().getDisplayName(),
|
||||
moduleUri);
|
||||
var outputValueMember = output.getMember(Identifier.VALUE);
|
||||
assert outputValueMember != null;
|
||||
var uriOfValueMember = outputValueMember.getSourceSection().getSource().getURI();
|
||||
// If `value` was explicitly re-assigned, show that in the stack trace.
|
||||
// Otherwise, show the module header.
|
||||
if (!uriOfValueMember.equals(PClassInfo.pklBaseUri)) {
|
||||
return builder
|
||||
.withSourceSection(outputValueMember.getBodySection())
|
||||
.withMemberName("value")
|
||||
.build();
|
||||
} else {
|
||||
// if the module does not extend or amend anything, suggest amending the module URI
|
||||
if (module.getParent() != null
|
||||
&& module.getParent().getVmClass().equals(BaseModule.getModuleClass())
|
||||
&& expectedClassInfo.isModuleClass()) {
|
||||
builder.withHint(
|
||||
String.format(
|
||||
"Try adding `amends %s` to the module header.",
|
||||
vmValueRenderer.render(expectedClassInfo.getModuleUri().toString())));
|
||||
}
|
||||
return builder
|
||||
.withSourceSection(module.getModuleInfo().getHeaderSection())
|
||||
.withMemberName(module.getModuleInfo().getModuleName())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPklBug(VmStackOverflowException e) {
|
||||
// There's no good way to tell if a StackOverflowError came from Pkl, or from our
|
||||
// implementation.
|
||||
// This is a simple heuristic; it's pretty likely that any stack overflow error that occurs
|
||||
// if there's less than 100 truffle frames is due to our own doing.
|
||||
var truffleStackTraceElements = TruffleStackTrace.getStackTrace(e);
|
||||
return truffleStackTraceElements != null && truffleStackTraceElements.size() < 100;
|
||||
}
|
||||
|
||||
// ScheduledFuture.cancel() is problematic, so let's handle cancellation on our own
|
||||
private final class TimeoutTask implements Runnable {
|
||||
// both fields guarded by synchronizing on `this`
|
||||
private boolean started = false;
|
||||
private boolean cancelled = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (this) {
|
||||
if (cancelled) return;
|
||||
|
||||
started = true;
|
||||
}
|
||||
|
||||
// may take a while
|
||||
close();
|
||||
}
|
||||
|
||||
/** Returns `true` if this task was successfully cancelled before it had started. */
|
||||
public synchronized boolean cancel() {
|
||||
if (started) return false;
|
||||
|
||||
cancelled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
pkl-core/src/main/java/org/pkl/core/FileOutput.java
Normal file
26
pkl-core/src/main/java/org/pkl/core/FileOutput.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/** Java representation of {@code pkl.base#FileOutput}. */
|
||||
public interface FileOutput {
|
||||
/**
|
||||
* Returns the text content of this file.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
*/
|
||||
String getText();
|
||||
}
|
||||
47
pkl-core/src/main/java/org/pkl/core/FileOutputImpl.java
Normal file
47
pkl-core/src/main/java/org/pkl/core/FileOutputImpl.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import org.graalvm.polyglot.PolyglotException;
|
||||
import org.pkl.core.runtime.VmTyped;
|
||||
|
||||
final class FileOutputImpl implements FileOutput {
|
||||
private final VmTyped fileOutput;
|
||||
private final EvaluatorImpl evaluator;
|
||||
|
||||
FileOutputImpl(EvaluatorImpl evaluator, VmTyped fileOutput) {
|
||||
this.evaluator = evaluator;
|
||||
this.fileOutput = fileOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the text output of this file.
|
||||
*
|
||||
* <p>Will throw {@link PklException} if a normal evaluator error occurs.
|
||||
*
|
||||
* <p>If the evaluator that produced this {@link FileOutput} is closed, an error will be thrown.
|
||||
*/
|
||||
public String getText() {
|
||||
try {
|
||||
return evaluator.evaluateOutputText(fileOutput);
|
||||
} catch (PolyglotException e) {
|
||||
if (e.isCancelled()) {
|
||||
throw new PklException("The evaluator is no longer available", e);
|
||||
}
|
||||
throw new PklBugException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
212
pkl-core/src/main/java/org/pkl/core/JsonRenderer.java
Normal file
212
pkl-core/src/main/java/org/pkl/core/JsonRenderer.java
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.util.json.JsonWriter;
|
||||
|
||||
final class JsonRenderer implements ValueRenderer {
|
||||
private final JsonWriter writer;
|
||||
private final boolean omitNullProperties;
|
||||
|
||||
public JsonRenderer(Writer writer, String indent, boolean omitNullProperties) {
|
||||
this.writer = new JsonWriter(writer);
|
||||
this.writer.setIndent(indent);
|
||||
this.omitNullProperties = omitNullProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderDocument(Object value) {
|
||||
// JSON document can have any top-level value
|
||||
// https://stackoverflow.com/a/3833312
|
||||
// http://www.ietf.org/rfc/rfc7159.txt
|
||||
renderValue(value);
|
||||
try {
|
||||
writer.newline();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderValue(Object value) {
|
||||
new Visitor().visit(value);
|
||||
}
|
||||
|
||||
private class Visitor implements ValueVisitor {
|
||||
@Override
|
||||
public void visitString(String value) {
|
||||
try {
|
||||
writer.value(value);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInt(Long value) {
|
||||
try {
|
||||
writer.value(value);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFloat(Double value) {
|
||||
try {
|
||||
writer.value(value);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitBoolean(Boolean value) {
|
||||
try {
|
||||
writer.value(value);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDuration(Duration value) {
|
||||
throw new RendererException(
|
||||
String.format("Values of type `Duration` cannot be rendered as JSON. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDataSize(DataSize value) {
|
||||
throw new RendererException(
|
||||
String.format("Values of type `DataSize` cannot be rendered as JSON. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPair(Pair<?, ?> value) {
|
||||
try {
|
||||
writer.beginArray();
|
||||
visit(value.getFirst());
|
||||
visit(value.getSecond());
|
||||
writer.endArray();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitList(List<?> value) {
|
||||
doVisitCollection(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSet(Set<?> value) {
|
||||
doVisitCollection(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMap(Map<?, ?> value) {
|
||||
for (var key : value.keySet()) {
|
||||
if (!(key instanceof String)) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Maps containing non-String keys cannot be rendered as JSON. Key: %s", key));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var mapValue = (Map<String, ?>) value;
|
||||
doVisitProperties(mapValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitObject(PObject value) {
|
||||
doVisitProperties(value.getProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitModule(PModule value) {
|
||||
doVisitProperties(value.getProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNull() {
|
||||
try {
|
||||
writer.nullValue();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClass(PClass value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Class` cannot be rendered as JSON. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTypeAlias(TypeAlias value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `TypeAlias` cannot be rendered as JSON. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegex(Pattern value) {
|
||||
throw new RendererException(
|
||||
String.format("Values of type `Regex` cannot be rendered as JSON. Value: %s", value));
|
||||
}
|
||||
|
||||
private void doVisitCollection(Collection<?> collection) {
|
||||
try {
|
||||
writer.beginArray();
|
||||
for (var elem : collection) visit(elem);
|
||||
writer.endArray();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void doVisitProperties(Map<String, ?> properties) {
|
||||
try {
|
||||
writer.beginObject();
|
||||
|
||||
for (var entry : properties.entrySet()) {
|
||||
var value = entry.getValue();
|
||||
|
||||
if (omitNullProperties && value instanceof PNull) continue;
|
||||
|
||||
writer.name(entry.getKey());
|
||||
visit(value);
|
||||
}
|
||||
|
||||
writer.endObject();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
pkl-core/src/main/java/org/pkl/core/Logger.java
Normal file
29
pkl-core/src/main/java/org/pkl/core/Logger.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/**
|
||||
* SPI for log messages emitted by the Pkl evaluator. Use {@link EvaluatorBuilder#setLogger} to set
|
||||
* a logger. See {@link Loggers} for predefined loggers.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public interface Logger {
|
||||
/** Logs the given message on level TRACE. */
|
||||
default void trace(String message, StackFrame frame) {}
|
||||
|
||||
/** Logs the given message on level WARN. */
|
||||
default void warn(String message, StackFrame frame) {}
|
||||
}
|
||||
83
pkl-core/src/main/java/org/pkl/core/Loggers.java
Normal file
83
pkl-core/src/main/java/org/pkl/core/Loggers.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/** Predefined {@link Logger}s. */
|
||||
@SuppressWarnings("unused")
|
||||
public final class Loggers {
|
||||
/** Returns a logger that discards log messages. */
|
||||
public static Logger noop() {
|
||||
return new Logger() {
|
||||
@Override
|
||||
public void trace(String message, StackFrame frame) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message, StackFrame frame) {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns a logger that sends log messages to standard error. */
|
||||
public static Logger stdErr() {
|
||||
return stream(System.err);
|
||||
}
|
||||
|
||||
/** Returns a logger that sends log messages to the given stream. */
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public static Logger stream(PrintStream stream) {
|
||||
return new Logger() {
|
||||
@Override
|
||||
public void trace(String message, StackFrame frame) {
|
||||
stream.println(formatMessage("TRACE", message, frame));
|
||||
stream.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message, StackFrame frame) {
|
||||
stream.println(formatMessage("WARN", message, frame));
|
||||
stream.flush();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns a logger that sends log messages to the given writer. */
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public static Logger writer(PrintWriter writer) {
|
||||
return new Logger() {
|
||||
@Override
|
||||
public void trace(String message, StackFrame frame) {
|
||||
writer.println(formatMessage("TRACE", message, frame));
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message, StackFrame frame) {
|
||||
writer.println(formatMessage("WARN", message, frame));
|
||||
writer.flush();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String formatMessage(String level, String message, StackFrame frame) {
|
||||
return "pkl: " + level + ": " + message + " (" + frame.getModuleUri() + ')';
|
||||
}
|
||||
}
|
||||
121
pkl-core/src/main/java/org/pkl/core/Member.java
Normal file
121
pkl-core/src/main/java/org/pkl/core/Member.java
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Common base class for TypeAlias, PClass, PClass.Property, and PClass.Method. */
|
||||
public abstract class Member implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final @Nullable String docComment;
|
||||
private final SourceLocation sourceLocation;
|
||||
private final Set<Modifier> modifiers;
|
||||
private final List<PObject> annotations;
|
||||
private final String simpleName;
|
||||
|
||||
public Member(
|
||||
@Nullable String docComment,
|
||||
SourceLocation sourceLocation,
|
||||
Set<Modifier> modifiers,
|
||||
List<PObject> annotations,
|
||||
String simpleName) {
|
||||
this.docComment = docComment;
|
||||
this.sourceLocation = Objects.requireNonNull(sourceLocation, "sourceLocation");
|
||||
this.modifiers = Objects.requireNonNull(modifiers, "modifiers");
|
||||
this.annotations = Objects.requireNonNull(annotations, "annotations");
|
||||
this.simpleName = Objects.requireNonNull(simpleName, "simpleName");
|
||||
}
|
||||
|
||||
/** Returns the name of the module that this member is declared in. */
|
||||
public abstract String getModuleName();
|
||||
|
||||
/** Returns the documentation comment of this member. */
|
||||
public @Nullable String getDocComment() {
|
||||
return docComment;
|
||||
}
|
||||
|
||||
/** Returns the source location, such as start and end line, of this member. */
|
||||
public SourceLocation getSourceLocation() {
|
||||
return sourceLocation;
|
||||
}
|
||||
|
||||
/** Returns the modifiers of this member. */
|
||||
public Set<Modifier> getModifiers() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
/** Returns the annotations of this member. */
|
||||
public List<PObject> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
/** Tells if this member has an {@code external} modifier. */
|
||||
public boolean isExternal() {
|
||||
return modifiers.contains(Modifier.EXTERNAL);
|
||||
}
|
||||
|
||||
/** Tells if this member has an {@code abstract} modifier. */
|
||||
public boolean isAbstract() {
|
||||
return modifiers.contains(Modifier.ABSTRACT);
|
||||
}
|
||||
|
||||
/** Tells if this member has a {@code hidden} modifier. */
|
||||
public boolean isHidden() {
|
||||
return modifiers.contains(Modifier.HIDDEN);
|
||||
}
|
||||
|
||||
/** Tells if this member has an {@code open} modifier. */
|
||||
public boolean isOpen() {
|
||||
return modifiers.contains(Modifier.OPEN);
|
||||
}
|
||||
|
||||
/** Tells if this member is defined in Pkl's standard library. */
|
||||
public final boolean isStandardLibraryMember() {
|
||||
return getModuleName().startsWith("pkl.");
|
||||
}
|
||||
|
||||
/** Returns the unqualified name of this member. */
|
||||
public String getSimpleName() {
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
public static class SourceLocation implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final int startLine;
|
||||
private final int endLine;
|
||||
|
||||
public SourceLocation(int startLine, int endLine) {
|
||||
this.startLine = startLine;
|
||||
this.endLine = endLine;
|
||||
}
|
||||
|
||||
public int getStartLine() {
|
||||
return startLine;
|
||||
}
|
||||
|
||||
public int getEndLine() {
|
||||
return endLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
pkl-core/src/main/java/org/pkl/core/Modifier.java
Normal file
34
pkl-core/src/main/java/org/pkl/core/Modifier.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
public enum Modifier {
|
||||
ABSTRACT("abstract"),
|
||||
OPEN("open"),
|
||||
HIDDEN("hidden"),
|
||||
EXTERNAL("external");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
Modifier(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
182
pkl-core/src/main/java/org/pkl/core/ModuleSchema.java
Normal file
182
pkl-core/src/main/java/org/pkl/core/ModuleSchema.java
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import org.pkl.core.util.LateInit;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Describes the property, method and class members of a module. */
|
||||
public final class ModuleSchema {
|
||||
private final URI moduleUri;
|
||||
private final String moduleName;
|
||||
private final boolean isAmend;
|
||||
private final @Nullable ModuleSchema supermodule;
|
||||
private final PClass moduleClass;
|
||||
private final @Nullable String docComment;
|
||||
private final List<PObject> annotations;
|
||||
private final Map<String, PClass> classes;
|
||||
private final Map<String, TypeAlias> typeAliases;
|
||||
private final Map<String, URI> imports;
|
||||
|
||||
@LateInit private Map<String, PClass> __allClasses;
|
||||
@LateInit private Map<String, TypeAlias> __allTypeAliases;
|
||||
|
||||
/** Constructs a {@code ModuleSchema} instance. */
|
||||
public ModuleSchema(
|
||||
URI moduleUri,
|
||||
String moduleName,
|
||||
boolean isAmend,
|
||||
@Nullable ModuleSchema supermodule,
|
||||
PClass moduleClass,
|
||||
@Nullable String docComment,
|
||||
List<PObject> annotations,
|
||||
Map<String, PClass> classes,
|
||||
Map<String, TypeAlias> typeAliases,
|
||||
Map<String, URI> imports) {
|
||||
this.moduleUri = moduleUri;
|
||||
this.moduleName = moduleName;
|
||||
this.isAmend = isAmend;
|
||||
this.supermodule = supermodule;
|
||||
this.moduleClass = moduleClass;
|
||||
this.docComment = docComment;
|
||||
this.annotations = annotations;
|
||||
this.classes = classes;
|
||||
this.typeAliases = typeAliases;
|
||||
this.imports = imports;
|
||||
}
|
||||
|
||||
/** Returns the absolute URI from which this module was first loaded. */
|
||||
public URI getModuleUri() {
|
||||
return moduleUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this module.
|
||||
*
|
||||
* <p>Note that module names are not guaranteed to be unique, especially if they are not declared
|
||||
* but inferred from the module URI.
|
||||
*/
|
||||
public String getModuleName() {
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last name part of a dot-separated {@link #getModuleName}, or the entire {@link
|
||||
* #getModuleName} if it is not dot-separated.
|
||||
*/
|
||||
public String getShortModuleName() {
|
||||
var index = moduleName.lastIndexOf('.');
|
||||
return moduleName.substring(index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this module's supermodule, or {@code null} if this module does not amend or extend
|
||||
* another module.
|
||||
*/
|
||||
public @Nullable ModuleSchema getSupermodule() {
|
||||
return supermodule;
|
||||
}
|
||||
|
||||
/** Tells if this module amends a module (namely {@link #getSupermodule()}). */
|
||||
public boolean isAmend() {
|
||||
return isAmend;
|
||||
}
|
||||
|
||||
/** Tells if this module extends a module (namely {@link #getSupermodule()}). */
|
||||
public boolean isExtend() {
|
||||
return supermodule != null && !isAmend;
|
||||
}
|
||||
|
||||
/** Returns the doc comment of this module (if any). */
|
||||
public @Nullable String getDocComment() {
|
||||
return docComment;
|
||||
}
|
||||
|
||||
/** Returns the annotations of this module. */
|
||||
public List<PObject> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class of this module, which describes the properties and methods defined in this
|
||||
* module.
|
||||
*/
|
||||
public PClass getModuleClass() {
|
||||
return moduleClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the imports declared in this module.
|
||||
*
|
||||
* <p>Map keys are the identifiers by which imports are accessed within this module. Map values
|
||||
* are the URIs of the imported modules.
|
||||
*
|
||||
* <p>Does not cover import expressions.
|
||||
*/
|
||||
public Map<String, URI> getImports() {
|
||||
return imports;
|
||||
}
|
||||
|
||||
/** Returns the classes defined in this module in declaration order. */
|
||||
public Map<String, PClass> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all classes defined in this module and its supermodules in declaration order.
|
||||
* Supermodule classes are ordered before submodule classes.
|
||||
*/
|
||||
public Map<String, PClass> getAllClasses() {
|
||||
if (__allClasses == null) {
|
||||
if (supermodule == null) {
|
||||
__allClasses = classes;
|
||||
} else if (classes.isEmpty()) {
|
||||
__allClasses = supermodule.getAllClasses();
|
||||
} else {
|
||||
__allClasses = new LinkedHashMap<>();
|
||||
__allClasses.putAll(supermodule.getAllClasses());
|
||||
__allClasses.putAll(classes);
|
||||
}
|
||||
}
|
||||
return __allClasses;
|
||||
}
|
||||
|
||||
/** Returns the type aliases defined in this module in declaration order. */
|
||||
public Map<String, TypeAlias> getTypeAliases() {
|
||||
return typeAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all type aliases defined in this module and its supermodules in declaration order.
|
||||
* Supermodule type aliases are ordered before submodule type aliases.
|
||||
*/
|
||||
public Map<String, TypeAlias> getAllTypeAliases() {
|
||||
if (__allTypeAliases == null) {
|
||||
if (supermodule == null) {
|
||||
__allTypeAliases = typeAliases;
|
||||
} else if (typeAliases.isEmpty()) {
|
||||
__allTypeAliases = supermodule.getAllTypeAliases();
|
||||
} else {
|
||||
__allTypeAliases = new LinkedHashMap<>();
|
||||
__allTypeAliases.putAll(supermodule.getAllTypeAliases());
|
||||
__allTypeAliases.putAll(typeAliases);
|
||||
}
|
||||
}
|
||||
return __allTypeAliases;
|
||||
}
|
||||
}
|
||||
91
pkl-core/src/main/java/org/pkl/core/ModuleSource.java
Normal file
91
pkl-core/src/main/java/org/pkl/core/ModuleSource.java
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/**
|
||||
* A representation for a Pkl module's source URI, and optionally its source text.
|
||||
*
|
||||
* <p>Create a new module source via {@link #create(URI, String)}, or one of the various helper
|
||||
* factory methods.
|
||||
*/
|
||||
public class ModuleSource {
|
||||
public static ModuleSource create(URI uri, @Nullable String text) {
|
||||
return new ModuleSource(uri, text);
|
||||
}
|
||||
|
||||
public static ModuleSource path(Path path) {
|
||||
return new ModuleSource(path.toUri(), null);
|
||||
}
|
||||
|
||||
public static ModuleSource path(String path) {
|
||||
return path(Path.of(path));
|
||||
}
|
||||
|
||||
public static ModuleSource text(String text) {
|
||||
return new ModuleSource(VmUtils.REPL_TEXT_URI, text);
|
||||
}
|
||||
|
||||
public static ModuleSource file(String file) {
|
||||
return file(new File(file));
|
||||
}
|
||||
|
||||
public static ModuleSource file(File file) {
|
||||
// File.toPath.toUri() gives more complaint file URIs
|
||||
// than File.toUri() (file:/// vs. file:/ for local files)
|
||||
return new ModuleSource(file.toPath().toUri(), null);
|
||||
}
|
||||
|
||||
public static ModuleSource uri(String uri) {
|
||||
return uri(URI.create(uri));
|
||||
}
|
||||
|
||||
public static ModuleSource uri(URI uri) {
|
||||
return new ModuleSource(uri, null);
|
||||
}
|
||||
|
||||
public static ModuleSource modulePath(String path) {
|
||||
URI uri;
|
||||
if (path.charAt(0) == '/') {
|
||||
uri = URI.create("modulepath:" + path);
|
||||
} else {
|
||||
uri = URI.create("modulepath:/" + path);
|
||||
}
|
||||
return uri(uri);
|
||||
}
|
||||
|
||||
private final URI uri;
|
||||
|
||||
@Nullable private final String contents;
|
||||
|
||||
private ModuleSource(URI uri, @Nullable String contents) {
|
||||
this.uri = uri;
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public @Nullable String getContents() {
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/**
|
||||
* Indicates that a non-existent property was requested for a {@link Composite}. To check if a
|
||||
* property exists, use {@link Composite#hasProperty(String)}.
|
||||
*/
|
||||
public final class NoSuchPropertyException extends RuntimeException {
|
||||
private final String propertyName;
|
||||
|
||||
public NoSuchPropertyException(String message, String propertyName) {
|
||||
super(message);
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
}
|
||||
40
pkl-core/src/main/java/org/pkl/core/OutputFormat.java
Normal file
40
pkl-core/src/main/java/org/pkl/core/OutputFormat.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/** The output formats that Pkl supports out-of-the-box. */
|
||||
public enum OutputFormat {
|
||||
JSON("json"),
|
||||
JSONNET("jsonnet"),
|
||||
PCF("pcf"),
|
||||
PROPERTIES("properties"),
|
||||
PLIST("plist"),
|
||||
TEXTPROTO("textproto"),
|
||||
XML("xml"),
|
||||
YAML("yaml");
|
||||
|
||||
private final String cliArgument;
|
||||
|
||||
OutputFormat(String cliArgument) {
|
||||
this.cliArgument = cliArgument;
|
||||
}
|
||||
|
||||
/** The argument for CLI option <code>--format</code> that selects this output format. */
|
||||
@Override
|
||||
public String toString() {
|
||||
return cliArgument;
|
||||
}
|
||||
}
|
||||
282
pkl-core/src/main/java/org/pkl/core/PClass.java
Normal file
282
pkl-core/src/main/java/org/pkl/core/PClass.java
Normal file
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.*;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Java representation of a {@code pkl.base#Class} value. */
|
||||
public final class PClass extends Member implements Value {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final PClassInfo<?> classInfo;
|
||||
private final List<TypeParameter> typeParameters;
|
||||
private final Map<String, Property> properties;
|
||||
private final Map<String, Method> methods;
|
||||
|
||||
private @Nullable PType supertype;
|
||||
private @Nullable PClass superclass;
|
||||
|
||||
private @Nullable Map<String, Property> allProperties;
|
||||
private @Nullable Map<String, Method> allMethods;
|
||||
|
||||
public PClass(
|
||||
@Nullable String docComment,
|
||||
SourceLocation sourceLocation,
|
||||
Set<Modifier> modifiers,
|
||||
List<PObject> annotations,
|
||||
PClassInfo<?> classInfo,
|
||||
List<TypeParameter> typeParameters,
|
||||
Map<String, Property> properties,
|
||||
Map<String, Method> methods) {
|
||||
super(docComment, sourceLocation, modifiers, annotations, classInfo.getSimpleName());
|
||||
this.classInfo = classInfo;
|
||||
this.typeParameters = typeParameters;
|
||||
this.properties = properties;
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public void initSupertype(PType supertype, PClass superclass) {
|
||||
this.supertype = supertype;
|
||||
this.superclass = superclass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the module that this class is declared in. Note that a module name is not
|
||||
* guaranteed to be unique, especially if it not declared but inferred from the module URI.
|
||||
*/
|
||||
public String getModuleName() {
|
||||
return classInfo.getModuleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the qualified name of this class, `moduleName#className`. Note that a qualified class
|
||||
* name is not guaranteed to be unique, especially if the module name is not declared but inferred
|
||||
* from the module URI.
|
||||
*/
|
||||
public String getQualifiedName() {
|
||||
return classInfo.getQualifiedName();
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return classInfo.getDisplayName();
|
||||
}
|
||||
|
||||
public PClassInfo<?> getInfo() {
|
||||
return classInfo;
|
||||
}
|
||||
|
||||
/** Tells if this class is the class of a module. */
|
||||
public boolean isModuleClass() {
|
||||
return getInfo().isModuleClass();
|
||||
}
|
||||
|
||||
public List<TypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
public @Nullable PType getSupertype() {
|
||||
return supertype;
|
||||
}
|
||||
|
||||
public @Nullable PClass getSuperclass() {
|
||||
return superclass;
|
||||
}
|
||||
|
||||
public Map<String, Property> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public Map<String, Method> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public Map<String, Property> getAllProperties() {
|
||||
if (allProperties == null) {
|
||||
allProperties = collectAllProperties(this, new LinkedHashMap<>());
|
||||
}
|
||||
return allProperties;
|
||||
}
|
||||
|
||||
public Map<String, Method> getAllMethods() {
|
||||
if (allMethods == null) {
|
||||
allMethods = collectAllMethods(this, new LinkedHashMap<>());
|
||||
}
|
||||
return allMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ValueVisitor visitor) {
|
||||
visitor.visitClass(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(ValueConverter<T> converter) {
|
||||
return converter.convertClass(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PClassInfo<?> getClassInfo() {
|
||||
return PClassInfo.Class;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
public abstract static class ClassMember extends Member {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final PClass owner;
|
||||
|
||||
public ClassMember(
|
||||
@Nullable String docComment,
|
||||
SourceLocation sourceLocation,
|
||||
Set<Modifier> modifiers,
|
||||
List<PObject> annotations,
|
||||
String simpleName,
|
||||
PClass owner) {
|
||||
super(docComment, sourceLocation, modifiers, annotations, simpleName);
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return owner.getInfo().getModuleName();
|
||||
}
|
||||
|
||||
/** Returns the class declaring this member. */
|
||||
public PClass getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the documentation comment of this member. If this member does not have a
|
||||
* documentation comment, returns the documentation comment of the nearest documented ancestor,
|
||||
* if any.
|
||||
*/
|
||||
public abstract @Nullable String getInheritedDocComment();
|
||||
}
|
||||
|
||||
public static final class Property extends ClassMember {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final PType type;
|
||||
|
||||
public Property(
|
||||
PClass owner,
|
||||
@Nullable String docComment,
|
||||
SourceLocation sourceLocation,
|
||||
Set<Modifier> modifiers,
|
||||
List<PObject> annotations,
|
||||
String simpleName,
|
||||
PType type) {
|
||||
super(docComment, sourceLocation, modifiers, annotations, simpleName, owner);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public PType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getInheritedDocComment() {
|
||||
if (getDocComment() != null) return getDocComment();
|
||||
|
||||
for (var clazz = getOwner().getSuperclass(); clazz != null; clazz = clazz.getSuperclass()) {
|
||||
var property = clazz.getProperties().get(getSimpleName());
|
||||
if (property != null && property.getDocComment() != null) {
|
||||
return property.getDocComment();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Method extends ClassMember {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final List<TypeParameter> typeParameters;
|
||||
private final Map<String, PType> parameters;
|
||||
private final PType returnType;
|
||||
|
||||
public Method(
|
||||
PClass owner,
|
||||
@Nullable String docComment,
|
||||
SourceLocation sourceLocation,
|
||||
Set<Modifier> modifiers,
|
||||
List<PObject> annotations,
|
||||
String simpleName,
|
||||
List<TypeParameter> typeParameters,
|
||||
Map<String, PType> parameters,
|
||||
PType returnType) {
|
||||
super(docComment, sourceLocation, modifiers, annotations, simpleName, owner);
|
||||
this.typeParameters = typeParameters;
|
||||
this.parameters = parameters;
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public List<TypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
public Map<String, PType> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public PType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getInheritedDocComment() {
|
||||
if (getDocComment() != null) return getDocComment();
|
||||
|
||||
for (var clazz = getOwner().getSuperclass(); clazz != null; clazz = clazz.getSuperclass()) {
|
||||
var method = clazz.getMethods().get(getSimpleName());
|
||||
if (method != null && method.getDocComment() != null) {
|
||||
return method.getDocComment();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Property> collectAllProperties(
|
||||
PClass clazz, Map<String, Property> collector) {
|
||||
if (clazz.superclass != null) {
|
||||
collectAllProperties(clazz.superclass, collector);
|
||||
}
|
||||
collector.putAll(clazz.properties);
|
||||
return collector;
|
||||
}
|
||||
|
||||
private Map<String, Method> collectAllMethods(PClass clazz, Map<String, Method> collector) {
|
||||
if (clazz.superclass != null) {
|
||||
collectAllMethods(clazz.superclass, collector);
|
||||
}
|
||||
collector.putAll(clazz.methods);
|
||||
return collector;
|
||||
}
|
||||
}
|
||||
248
pkl-core/src/main/java/org/pkl/core/PClassInfo.java
Normal file
248
pkl-core/src/main/java/org/pkl/core/PClassInfo.java
Normal file
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import static java.util.Map.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Information about a Pkl class and its Java representation. */
|
||||
@SuppressWarnings("rawtypes")
|
||||
public final class PClassInfo<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
// Simple name of a module's class.
|
||||
// User-facing via `module.getClass()` and error messages.
|
||||
// "Module" would result in a name clash between
|
||||
// the module class of `pkl.base` and class `Module` defined in `pkl.base`.
|
||||
public static final String MODULE_CLASS_NAME = "ModuleClass";
|
||||
|
||||
public static final URI pklBaseUri = URI.create("pkl:base");
|
||||
public static final URI pklSemverUri = URI.create("pkl:semver");
|
||||
public static final URI pklProjectUri = URI.create("pkl:Project");
|
||||
|
||||
public static final PClassInfo<Void> Any = pklBaseClassInfo("Any", Void.class);
|
||||
public static final PClassInfo<PNull> Null = pklBaseClassInfo("Null", PNull.class);
|
||||
public static final PClassInfo<String> String = pklBaseClassInfo("String", String.class);
|
||||
public static final PClassInfo<Boolean> Boolean = pklBaseClassInfo("Boolean", boolean.class);
|
||||
public static final PClassInfo<Void> Number = pklBaseClassInfo("Number", Void.class);
|
||||
public static final PClassInfo<Long> Int = pklBaseClassInfo("Int", long.class);
|
||||
public static final PClassInfo<Double> Float = pklBaseClassInfo("Float", double.class);
|
||||
public static final PClassInfo<Duration> Duration = pklBaseClassInfo("Duration", Duration.class);
|
||||
public static final PClassInfo<DataSize> DataSize = pklBaseClassInfo("DataSize", DataSize.class);
|
||||
public static final PClassInfo<Pair> Pair = pklBaseClassInfo("Pair", Pair.class);
|
||||
public static final PClassInfo<Void> Collection = pklBaseClassInfo("Collection", Void.class);
|
||||
public static final PClassInfo<ArrayList> List = pklBaseClassInfo("List", ArrayList.class);
|
||||
public static final PClassInfo<LinkedHashSet> Set = pklBaseClassInfo("Set", LinkedHashSet.class);
|
||||
public static final PClassInfo<LinkedHashMap> Map = pklBaseClassInfo("Map", LinkedHashMap.class);
|
||||
public static final PClassInfo<PObject> Object = pklBaseClassInfo("Object", PObject.class);
|
||||
public static final PClassInfo<PObject> Dynamic = pklBaseClassInfo("Dynamic", PObject.class);
|
||||
public static final PClassInfo<PObject> Typed = pklBaseClassInfo("Typed", PObject.class);
|
||||
public static final PClassInfo<ArrayList> Listing = pklBaseClassInfo("Listing", ArrayList.class);
|
||||
public static final PClassInfo<LinkedHashMap> Mapping =
|
||||
pklBaseClassInfo("Mapping", LinkedHashMap.class);
|
||||
public static final PClassInfo<PModule> Module = pklBaseClassInfo("Module", PModule.class);
|
||||
public static final PClassInfo<PClass> Class = pklBaseClassInfo("Class", PClass.class);
|
||||
public static final PClassInfo<TypeAlias> TypeAlias =
|
||||
pklBaseClassInfo("TypeAlias", TypeAlias.class);
|
||||
public static final PClassInfo<Pattern> Regex = pklBaseClassInfo("Regex", Pattern.class);
|
||||
public static final PClassInfo<PObject> Deprecated =
|
||||
pklBaseClassInfo("Deprecated", PObject.class);
|
||||
public static final PClassInfo<PObject> AlsoKnownAs =
|
||||
pklBaseClassInfo("AlsoKnownAs", PObject.class);
|
||||
public static final PClassInfo<PObject> Unlisted = pklBaseClassInfo("Unlisted", PObject.class);
|
||||
public static final PClassInfo<PObject> DocExample =
|
||||
pklBaseClassInfo("DocExample", PObject.class);
|
||||
public static final PClassInfo<PObject> PcfRenderDirective =
|
||||
pklBaseClassInfo("PcfRenderDirective", PObject.class);
|
||||
public static final PClassInfo<PObject> ModuleInfo =
|
||||
pklBaseClassInfo("ModuleInfo", PObject.class);
|
||||
public static final PClassInfo<PObject> Version =
|
||||
new PClassInfo<>("pkl.semver", "Version", PObject.class, pklSemverUri);
|
||||
public static final PClassInfo<PObject> Project =
|
||||
new PClassInfo<>("pkl.Project", "ModuleClass", PObject.class, pklProjectUri);
|
||||
|
||||
public static final PClassInfo<Object> Unavailable =
|
||||
new PClassInfo<>("unavailable", "unavailable", Object.class, URI.create("pkl:unavailable"));
|
||||
|
||||
/** Returns the class info for the class with the given module and class name. */
|
||||
public static PClassInfo<?> get(String moduleName, String className, URI moduleUri) {
|
||||
if (moduleName.equals("pkl.base")) {
|
||||
var classInfo = pooledPklBaseClassInfos.get(className);
|
||||
if (classInfo != null) return classInfo;
|
||||
}
|
||||
return new PClassInfo<>(moduleName, className, PObject.class, moduleUri);
|
||||
}
|
||||
|
||||
/** Returns the class info for the module class with the given module name. */
|
||||
public static PClassInfo<?> forModuleClass(String moduleName, URI moduleUri) {
|
||||
return get(moduleName, MODULE_CLASS_NAME, moduleUri);
|
||||
}
|
||||
|
||||
/** Returns the class info for the given value's class. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> PClassInfo<T> forValue(T value) {
|
||||
if (value instanceof Value) return (PClassInfo<T>) ((Value) value).getClassInfo();
|
||||
|
||||
if (value instanceof String) return (PClassInfo<T>) String;
|
||||
if (value instanceof Boolean) return (PClassInfo<T>) Boolean;
|
||||
if (value instanceof Long) return (PClassInfo<T>) Int;
|
||||
if (value instanceof Double) return (PClassInfo<T>) Float;
|
||||
if (value instanceof List) return (PClassInfo<T>) List;
|
||||
if (value instanceof Set) return (PClassInfo<T>) Set;
|
||||
if (value instanceof Map) return (PClassInfo<T>) Map;
|
||||
if (value instanceof Pattern) return (PClassInfo<T>) Regex;
|
||||
|
||||
throw new IllegalArgumentException("Not a Pkl value: " + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the module that this Pkl class is declared in. Note that a module name is
|
||||
* not guaranteed to be unique, especially if it not declared but inferred.
|
||||
*/
|
||||
public String getModuleName() {
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
/** Returns the simple name of this Pkl class. */
|
||||
public String getSimpleName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the qualified name of this Pkl class, `moduleName/className`. Note that a qualified
|
||||
* class name is not guaranteed to be unique, especially if the module name is not declared but
|
||||
* inferred.
|
||||
*/
|
||||
public String getQualifiedName() {
|
||||
return qualifiedName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
// display `String` rather than `pkl.base#String`, etc.
|
||||
return moduleName.equals("pkl.base") ? className : isModuleClass() ? moduleName : qualifiedName;
|
||||
}
|
||||
|
||||
public boolean isModuleClass() {
|
||||
// should have a better way but this is what we got
|
||||
return className.equals(MODULE_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the concrete Java class used to represent values of this Pkl class in Java. Returns
|
||||
* {@code Void.class} for abstract Pkl classes.
|
||||
*/
|
||||
public Class<T> getJavaClass() {
|
||||
return javaClass;
|
||||
}
|
||||
|
||||
/** Tells if this Pkl class is external (built-in). */
|
||||
public boolean isExternalClass() {
|
||||
return javaClass != PObject.class;
|
||||
}
|
||||
|
||||
/** Tells if this class is defined in Pkl's standard library. */
|
||||
public boolean isStandardLibraryClass() {
|
||||
return moduleName.startsWith("pkl.");
|
||||
}
|
||||
|
||||
public boolean isConcreteCollectionClass() {
|
||||
return this == PClassInfo.List || this == PClassInfo.Set;
|
||||
}
|
||||
|
||||
public boolean isExactClassOf(Object value) {
|
||||
var clazz = value.getClass();
|
||||
if (clazz != javaClass) return false;
|
||||
if (clazz != PObject.class) return true;
|
||||
|
||||
var pObject = (PObject) value;
|
||||
return pObject.getClassInfo().equals(this);
|
||||
}
|
||||
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof PClassInfo)) return false;
|
||||
|
||||
var other = (PClassInfo<?>) obj;
|
||||
return qualifiedName.equals(other.qualifiedName);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return qualifiedName.hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
private static final Map<String, PClassInfo<?>> pooledPklBaseClassInfos =
|
||||
java.util.Map.ofEntries(
|
||||
entry(Any.className, Any),
|
||||
entry(Null.className, Null),
|
||||
entry(Boolean.className, Boolean),
|
||||
entry(String.className, String),
|
||||
entry(Number.className, Number),
|
||||
entry(Int.className, Int),
|
||||
entry(Float.className, Float),
|
||||
entry(Duration.className, Duration),
|
||||
entry(DataSize.className, DataSize),
|
||||
entry(Pair.className, Pair),
|
||||
entry(Collection.className, Collection),
|
||||
entry(List.className, List),
|
||||
entry(Set.className, Set),
|
||||
entry(Map.className, Map),
|
||||
entry(Object.className, Object),
|
||||
entry(Dynamic.className, Dynamic),
|
||||
entry(Typed.className, Typed),
|
||||
entry(Listing.className, Listing),
|
||||
entry(Mapping.className, Mapping),
|
||||
entry(Module.className, Module),
|
||||
entry(Class.className, Class),
|
||||
entry(TypeAlias.className, TypeAlias),
|
||||
entry(Regex.className, Regex),
|
||||
entry(Deprecated.className, Deprecated),
|
||||
entry(AlsoKnownAs.className, AlsoKnownAs),
|
||||
entry(Unlisted.className, Unlisted),
|
||||
entry(DocExample.className, DocExample),
|
||||
entry(PcfRenderDirective.className, PcfRenderDirective));
|
||||
|
||||
private final String moduleName;
|
||||
private final String className;
|
||||
private final URI moduleUri;
|
||||
private final String qualifiedName;
|
||||
private final Class<T> javaClass;
|
||||
|
||||
private PClassInfo(String moduleName, String className, Class<T> javaClass, URI moduleUri) {
|
||||
this.moduleName = moduleName;
|
||||
this.className = className;
|
||||
this.moduleUri = moduleUri;
|
||||
this.qualifiedName = moduleName + "#" + className;
|
||||
this.javaClass = javaClass;
|
||||
}
|
||||
|
||||
private static <T> PClassInfo<T> pklBaseClassInfo(String className, Class<T> javaType) {
|
||||
return new PClassInfo<>("pkl.base", className, javaType, pklBaseUri);
|
||||
}
|
||||
|
||||
public URI getModuleUri() {
|
||||
return moduleUri;
|
||||
}
|
||||
}
|
||||
297
pkl-core/src/main/java/org/pkl/core/PListRenderer.java
Normal file
297
pkl-core/src/main/java/org/pkl/core/PListRenderer.java
Normal file
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.util.ArrayCharEscaper;
|
||||
|
||||
// To instantiate this class, use ValueRenderers.plist().
|
||||
final class PListRenderer implements ValueRenderer {
|
||||
private static final String LINE_BREAK = "\n";
|
||||
|
||||
// it's safe (though not required) to escape all of the following characters in XML text nodes
|
||||
private static final ArrayCharEscaper charEscaper =
|
||||
ArrayCharEscaper.builder()
|
||||
.withEscape('"', """)
|
||||
.withEscape('\'', "'")
|
||||
.withEscape('<', "<")
|
||||
.withEscape('>', ">")
|
||||
.withEscape('&', "&")
|
||||
.build();
|
||||
|
||||
private final Writer writer;
|
||||
private final String indent;
|
||||
|
||||
public PListRenderer(Writer writer, String indent) {
|
||||
this.writer = writer;
|
||||
this.indent = indent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderDocument(Object value) {
|
||||
if (!(value instanceof Collection || value instanceof Map || value instanceof Composite)) {
|
||||
// as far as I can tell, only arrays and dicts are allowed as top-level values
|
||||
// see:
|
||||
// https://github.com/apple/swift/blob/2bf6b88585ba0bc756c8a50c95081fc08f47fbf0/stdlib/public/SDK/Foundation/PlistEncoder.swift#L79
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"The top-level value of an XML property list must have type `Collection`, `Map`, or `Composite`, but got type `%s`.",
|
||||
value.getClass().getTypeName()));
|
||||
}
|
||||
|
||||
write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
write(LINE_BREAK);
|
||||
write(
|
||||
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
|
||||
write(LINE_BREAK);
|
||||
write("<plist version=\"1.0\">");
|
||||
write(LINE_BREAK);
|
||||
|
||||
new Visitor().visit(value);
|
||||
|
||||
write(LINE_BREAK);
|
||||
write("</plist>");
|
||||
write(LINE_BREAK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderValue(Object value) {
|
||||
new Visitor().visit(value);
|
||||
}
|
||||
|
||||
// keep in sync with org.pkl.core.stdlib.PListRendererNodes.PListRenderer
|
||||
private class Visitor implements ValueVisitor {
|
||||
private String currIndent = "";
|
||||
|
||||
@Override
|
||||
public void visitString(String value) {
|
||||
write("<string>");
|
||||
write(charEscaper.escape(value));
|
||||
write("</string>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInt(Long value) {
|
||||
write("<integer>");
|
||||
write(value.toString());
|
||||
write("</integer>");
|
||||
}
|
||||
|
||||
// according to:
|
||||
// https://www.apple.com/DTDs/PropertyList-1.0.dtd
|
||||
// http://www.atomicbird.com/blog/json-vs-plists (nan, infinity)
|
||||
@Override
|
||||
@SuppressWarnings("Duplicates")
|
||||
public void visitFloat(Double value) {
|
||||
write("<real>");
|
||||
|
||||
if (value.isNaN()) {
|
||||
write("nan");
|
||||
} else if (value == Double.POSITIVE_INFINITY) {
|
||||
write("+infinity");
|
||||
} else if (value == Double.NEGATIVE_INFINITY) {
|
||||
write("-infinity");
|
||||
} else {
|
||||
write(value.toString());
|
||||
}
|
||||
|
||||
write("</real>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitBoolean(Boolean value) {
|
||||
write(value ? "<true/>" : "<false/>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDuration(Duration value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Duration` cannot be rendered as XML property list. Value: %s",
|
||||
value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDataSize(DataSize value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `DataSize` cannot be rendered as XML property list. Value: %s",
|
||||
value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPair(Pair<?, ?> value) {
|
||||
doVisitIterable(value, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitList(List<?> value) {
|
||||
doVisitIterable(value, value.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSet(Set<?> value) {
|
||||
doVisitIterable(value, value.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMap(Map<?, ?> map) {
|
||||
var renderedAtLeastOneEntry = false;
|
||||
|
||||
for (var entry : map.entrySet()) {
|
||||
var key = entry.getKey();
|
||||
if (!(key instanceof String)) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Maps with non-String keys cannot be rendered as XML property list. Key: %s",
|
||||
key));
|
||||
}
|
||||
|
||||
var value = entry.getValue();
|
||||
if (value instanceof PNull) continue;
|
||||
|
||||
if (!renderedAtLeastOneEntry) {
|
||||
write("<dict>");
|
||||
write(LINE_BREAK);
|
||||
currIndent += indent;
|
||||
renderedAtLeastOneEntry = true;
|
||||
}
|
||||
|
||||
write(currIndent);
|
||||
write("<key>");
|
||||
write(charEscaper.escape((String) key));
|
||||
write("</key>");
|
||||
write(LINE_BREAK);
|
||||
|
||||
write(currIndent);
|
||||
visit(value);
|
||||
write(LINE_BREAK);
|
||||
}
|
||||
|
||||
if (renderedAtLeastOneEntry) {
|
||||
currIndent = currIndent.substring(0, currIndent.length() - indent.length());
|
||||
write(currIndent);
|
||||
write("</dict>");
|
||||
} else {
|
||||
write("<dict/>");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitObject(PObject value) {
|
||||
doVisitComposite(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitModule(PModule value) {
|
||||
doVisitComposite(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNull() {
|
||||
throw new RendererException("`null` values cannot be rendered as XML property list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClass(PClass value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Class` cannot be rendered as XML property list. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTypeAlias(TypeAlias value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `TypeAlias` cannot be rendered as XML property list. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegex(Pattern value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Regex` cannot be rendered as XML property list. Value: %s", value));
|
||||
}
|
||||
|
||||
private void doVisitIterable(Iterable<?> iterable, boolean isEmpty) {
|
||||
if (isEmpty) {
|
||||
write("<array/>");
|
||||
return;
|
||||
}
|
||||
|
||||
write("<array>");
|
||||
write(LINE_BREAK);
|
||||
currIndent += indent;
|
||||
|
||||
for (var elem : iterable) {
|
||||
write(currIndent);
|
||||
visit(elem);
|
||||
write(LINE_BREAK);
|
||||
}
|
||||
|
||||
currIndent = currIndent.substring(0, currIndent.length() - indent.length());
|
||||
write(currIndent);
|
||||
write("</array>");
|
||||
}
|
||||
|
||||
private void doVisitComposite(Composite composite) {
|
||||
var renderedAtLeastOneProperty = false;
|
||||
|
||||
for (var entry : composite.getProperties().entrySet()) {
|
||||
var value = entry.getValue();
|
||||
if (value instanceof PNull) continue;
|
||||
|
||||
if (!renderedAtLeastOneProperty) {
|
||||
write("<dict>");
|
||||
write(LINE_BREAK);
|
||||
currIndent += indent;
|
||||
renderedAtLeastOneProperty = true;
|
||||
}
|
||||
|
||||
write(currIndent);
|
||||
write("<key>");
|
||||
write(charEscaper.escape(entry.getKey()));
|
||||
write("</key>");
|
||||
write(LINE_BREAK);
|
||||
|
||||
write(currIndent);
|
||||
visit(value);
|
||||
write(LINE_BREAK);
|
||||
}
|
||||
|
||||
if (renderedAtLeastOneProperty) {
|
||||
currIndent = currIndent.substring(0, currIndent.length() - indent.length());
|
||||
write(currIndent);
|
||||
write("</dict>");
|
||||
} else {
|
||||
write("<dict/>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void write(String str) {
|
||||
try {
|
||||
writer.write(str);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
pkl-core/src/main/java/org/pkl/core/PModule.java
Normal file
84
pkl-core/src/main/java/org/pkl/core/PModule.java
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Java representation of a Pkl module. */
|
||||
public final class PModule extends PObject {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final URI moduleUri;
|
||||
private final String moduleName;
|
||||
|
||||
public PModule(
|
||||
URI moduleUri, String moduleName, PClassInfo<?> classInfo, Map<String, Object> properties) {
|
||||
super(classInfo, properties);
|
||||
this.moduleUri = Objects.requireNonNull(moduleUri, "moduleUri");
|
||||
this.moduleName = Objects.requireNonNull(moduleName, "moduleName");
|
||||
}
|
||||
|
||||
public URI getModuleUri() {
|
||||
return moduleUri;
|
||||
}
|
||||
|
||||
public String getModuleName() {
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
var result = properties.get(name);
|
||||
if (result != null) return result;
|
||||
|
||||
throw new NoSuchPropertyException(
|
||||
String.format(
|
||||
"Module `%s` does not have a property named `%s`. Available properties: %s",
|
||||
moduleName, name, properties.keySet()),
|
||||
name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ValueVisitor visitor) {
|
||||
visitor.visitModule(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof PModule)) return false;
|
||||
|
||||
var other = (PModule) obj;
|
||||
|
||||
return moduleUri.equals(other.moduleUri)
|
||||
&& moduleName.equals(other.moduleName)
|
||||
&& classInfo.equals(other.classInfo)
|
||||
&& properties.equals(other.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(moduleUri, moduleName, classInfo, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return render(moduleName);
|
||||
}
|
||||
}
|
||||
47
pkl-core/src/main/java/org/pkl/core/PNull.java
Normal file
47
pkl-core/src/main/java/org/pkl/core/PNull.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/** Java representation of a {@code pkl.base#Null} value. */
|
||||
public final class PNull implements Value {
|
||||
private static final long serialVersionUID = 0L;
|
||||
private static final PNull INSTANCE = new PNull();
|
||||
|
||||
/** Returns the sole instance of this class. */
|
||||
public static PNull getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ValueVisitor visitor) {
|
||||
visitor.visitNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(ValueConverter<T> converter) {
|
||||
return converter.convertNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PClassInfo<?> getClassInfo() {
|
||||
return PClassInfo.Null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
109
pkl-core/src/main/java/org/pkl/core/PObject.java
Normal file
109
pkl-core/src/main/java/org/pkl/core/PObject.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Java representation of a Pkl object. */
|
||||
public class PObject implements Composite {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
protected final PClassInfo<?> classInfo;
|
||||
protected final Map<String, Object> properties;
|
||||
|
||||
public PObject(PClassInfo<?> classInfo, Map<String, Object> properties) {
|
||||
this.classInfo = Objects.requireNonNull(classInfo, "classInfo");
|
||||
this.properties = Objects.requireNonNull(properties, "properties");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PClassInfo<?> getClassInfo() {
|
||||
return classInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
var result = properties.get(name);
|
||||
if (result != null) return result;
|
||||
|
||||
throw new NoSuchPropertyException(
|
||||
String.format(
|
||||
"Object of type `%s` does not have a property named `%s`. Available properties: %s",
|
||||
classInfo.getQualifiedName(), name, properties.keySet()),
|
||||
name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ValueVisitor visitor) {
|
||||
visitor.visitObject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(ValueConverter<T> converter) {
|
||||
return converter.convertObject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
|
||||
var other = (PObject) obj;
|
||||
return classInfo.equals(other.classInfo) && properties.equals(other.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(classInfo, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return render(getClassInfo().getDisplayName());
|
||||
}
|
||||
|
||||
protected String render(@Nullable String prefix) {
|
||||
var builder = new StringBuilder();
|
||||
|
||||
if (prefix != null) {
|
||||
builder.append(prefix);
|
||||
builder.append(" { ");
|
||||
} else {
|
||||
builder.append("{ ");
|
||||
}
|
||||
|
||||
var first = true;
|
||||
for (var property : properties.entrySet()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
builder.append("; ");
|
||||
}
|
||||
builder.append(property.getKey()).append(" = ").append(property.getValue());
|
||||
}
|
||||
|
||||
builder.append(" }");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
225
pkl-core/src/main/java/org/pkl/core/PType.java
Normal file
225
pkl-core/src/main/java/org/pkl/core/PType.java
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/** A Pkl type as used in type annotations. */
|
||||
public abstract class PType implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
/** The `unknown` type. Omitting a type annotation is equivalent to stating this type. */
|
||||
public static final PType UNKNOWN =
|
||||
new PType() {
|
||||
private static final long serialVersionUID = 0L;
|
||||
};
|
||||
|
||||
/** The bottom type. */
|
||||
public static final PType NOTHING =
|
||||
new PType() {
|
||||
private static final long serialVersionUID = 0L;
|
||||
};
|
||||
|
||||
/** The type of the enclosing module. */
|
||||
public static final PType MODULE =
|
||||
new PType() {
|
||||
private static final long serialVersionUID = 0L;
|
||||
};
|
||||
|
||||
private PType() {}
|
||||
|
||||
public List<PType> getTypeArguments() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
public static final class StringLiteral extends PType {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final String literal;
|
||||
|
||||
public StringLiteral(String literal) {
|
||||
this.literal = literal;
|
||||
}
|
||||
|
||||
public String getLiteral() {
|
||||
return literal;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Class extends PType {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final PClass pClass;
|
||||
private final List<PType> typeArguments;
|
||||
|
||||
public Class(PClass pClass, List<PType> typeArguments) {
|
||||
this.pClass = pClass;
|
||||
this.typeArguments = typeArguments;
|
||||
}
|
||||
|
||||
public Class(PClass pClass) {
|
||||
this(pClass, List.of());
|
||||
}
|
||||
|
||||
public Class(PClass pClass, PType typeArgument1) {
|
||||
this(pClass, List.of(typeArgument1));
|
||||
}
|
||||
|
||||
public Class(PClass pClass, PType typeArgument1, PType typeArgument2) {
|
||||
this(pClass, List.of(typeArgument1, typeArgument2));
|
||||
}
|
||||
|
||||
public PClass getPClass() {
|
||||
return pClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PType> getTypeArguments() {
|
||||
return typeArguments;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Nullable extends PType {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final PType baseType;
|
||||
|
||||
public Nullable(PType baseType) {
|
||||
this.baseType = baseType;
|
||||
}
|
||||
|
||||
public PType getBaseType() {
|
||||
return baseType;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Constrained extends PType {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final PType baseType;
|
||||
private final List<String> constraints;
|
||||
|
||||
public Constrained(PType baseType, List<String> constraints) {
|
||||
this.baseType = baseType;
|
||||
this.constraints = constraints;
|
||||
}
|
||||
|
||||
public PType getBaseType() {
|
||||
return baseType;
|
||||
}
|
||||
|
||||
public List<String> getConstraints() {
|
||||
return constraints;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Alias extends PType {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final TypeAlias typeAlias;
|
||||
private final List<PType> typeArguments;
|
||||
private final PType aliasedType;
|
||||
|
||||
public Alias(TypeAlias typeAlias) {
|
||||
this(typeAlias, List.of(), typeAlias.getAliasedType());
|
||||
}
|
||||
|
||||
public Alias(TypeAlias typeAlias, List<PType> typeArguments, PType aliasedType) {
|
||||
this.typeAlias = typeAlias;
|
||||
this.typeArguments = typeArguments;
|
||||
this.aliasedType = aliasedType;
|
||||
}
|
||||
|
||||
public TypeAlias getTypeAlias() {
|
||||
return typeAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PType> getTypeArguments() {
|
||||
return typeArguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aliased type, namely {@code getTypeAlias().getAliasedType()} with type arguments
|
||||
* substituted for type variables.
|
||||
*/
|
||||
public PType getAliasedType() {
|
||||
return aliasedType;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Function extends PType {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final List<PType> parameterTypes;
|
||||
private final PType returnType;
|
||||
|
||||
public Function(List<PType> parameterTypes, PType returnType) {
|
||||
this.parameterTypes = parameterTypes;
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public List<PType> getParameterTypes() {
|
||||
return parameterTypes;
|
||||
}
|
||||
|
||||
public PType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Union extends PType {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final List<PType> elementTypes;
|
||||
|
||||
public Union(List<PType> elementTypes) {
|
||||
this.elementTypes = elementTypes;
|
||||
}
|
||||
|
||||
public List<PType> getElementTypes() {
|
||||
return elementTypes;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TypeVariable extends PType {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final TypeParameter typeParameter;
|
||||
|
||||
public TypeVariable(TypeParameter typeParameter) {
|
||||
this.typeParameter = typeParameter;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return typeParameter.getName();
|
||||
}
|
||||
|
||||
public TypeParameter getTypeParameter() {
|
||||
return typeParameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
102
pkl-core/src/main/java/org/pkl/core/Pair.java
Normal file
102
pkl-core/src/main/java/org/pkl/core/Pair.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Java representation of a {@code pkl.base#Pair} value. */
|
||||
public final class Pair<F, S> implements Value, Iterable<Object> {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final F first;
|
||||
private final S second;
|
||||
|
||||
/** Constructs a pair with the given elements. */
|
||||
public Pair(F first, S second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
/** Returns the first element of this pair. */
|
||||
public F getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
/** Returns the second element of this pair. */
|
||||
public S getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
return new Iterator<>() {
|
||||
int pos = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return pos < 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() {
|
||||
switch (pos++) {
|
||||
case 0:
|
||||
return first;
|
||||
case 1:
|
||||
return second;
|
||||
default:
|
||||
throw new NoSuchElementException("Pair only has two elements.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ValueVisitor visitor) {
|
||||
visitor.visitPair(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(ValueConverter<T> converter) {
|
||||
return converter.convertPair(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PClassInfo<?> getClassInfo() {
|
||||
return PClassInfo.Pair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Pair)) return false;
|
||||
|
||||
var other = (Pair<?, ?>) obj;
|
||||
return first.equals(other.first) && second.equals(other.second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return first.hashCode() * 31 + second.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Pair(" + first + ", " + second + ")";
|
||||
}
|
||||
}
|
||||
255
pkl-core/src/main/java/org/pkl/core/PcfRenderer.java
Normal file
255
pkl-core/src/main/java/org/pkl/core/PcfRenderer.java
Normal file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.parser.Lexer;
|
||||
|
||||
// To instantiate this class, use ValueRenderers.pcf().
|
||||
final class PcfRenderer implements ValueRenderer {
|
||||
private static final String LINE_SEPARATOR = "\n";
|
||||
|
||||
private final Writer writer;
|
||||
private final String indent;
|
||||
private final boolean omitNullProperties;
|
||||
private final ValueFormatter valueFormatter;
|
||||
|
||||
private String currIndent = "";
|
||||
|
||||
public PcfRenderer(
|
||||
Writer writer, String indent, boolean omitNullProperties, boolean useCustomStringDelimiters) {
|
||||
this.writer = writer;
|
||||
this.indent = indent;
|
||||
this.omitNullProperties = omitNullProperties;
|
||||
this.valueFormatter = new ValueFormatter(true, useCustomStringDelimiters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderDocument(Object value) {
|
||||
if (!(value instanceof Composite)) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"The top-level value of a Pcf document must have type `Composite`, but got type `%s`.",
|
||||
value.getClass().getTypeName()));
|
||||
}
|
||||
|
||||
new Visitor().doVisitProperties(((Composite) value).getProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderValue(Object value) {
|
||||
new Visitor().visit(value);
|
||||
}
|
||||
|
||||
private class Visitor implements ValueVisitor {
|
||||
@Override
|
||||
public void visitString(String value) {
|
||||
try {
|
||||
valueFormatter.formatStringValue(value, currIndent + indent, writer);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInt(Long value) {
|
||||
write(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFloat(Double value) {
|
||||
write(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitBoolean(Boolean value) {
|
||||
write(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDuration(Duration value) {
|
||||
write(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDataSize(DataSize value) {
|
||||
write(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPair(Pair<?, ?> value) {
|
||||
doVisitIterable(value, "Pair(");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitList(List<?> value) {
|
||||
doVisitIterable(value, "List(");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSet(Set<?> value) {
|
||||
doVisitIterable(value, "Set(");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMap(Map<?, ?> value) {
|
||||
var first = true;
|
||||
write("Map(");
|
||||
for (var entry : value.entrySet()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
write(", ");
|
||||
}
|
||||
if (entry.getKey() instanceof Composite) {
|
||||
write("new ");
|
||||
}
|
||||
visit(entry.getKey());
|
||||
write(", ");
|
||||
if (entry.getValue() instanceof Composite) {
|
||||
write("new ");
|
||||
}
|
||||
visit(entry.getValue());
|
||||
}
|
||||
write(')');
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitObject(PObject value) {
|
||||
doVisitComposite(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitModule(PModule value) {
|
||||
doVisitComposite(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClass(PClass value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Class` cannot be rendered as Pcf. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTypeAlias(TypeAlias value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `TypeAlias` cannot be rendered as Pcf. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNull() {
|
||||
write("null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegex(Pattern value) {
|
||||
write("Regex(");
|
||||
visitString(value.pattern());
|
||||
write(')');
|
||||
}
|
||||
|
||||
private void doVisitIterable(Iterable<?> iterable, String prefix) {
|
||||
var first = true;
|
||||
write(prefix);
|
||||
for (var elem : iterable) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
write(", ");
|
||||
}
|
||||
if (elem == null) { // unevaluated property
|
||||
write("?");
|
||||
continue;
|
||||
}
|
||||
if (elem instanceof Composite) {
|
||||
write("new ");
|
||||
}
|
||||
visit(elem);
|
||||
}
|
||||
write(')');
|
||||
}
|
||||
|
||||
private void doVisitComposite(Composite composite) {
|
||||
if (composite.getProperties().isEmpty()) {
|
||||
write("{}");
|
||||
return;
|
||||
}
|
||||
|
||||
write('{');
|
||||
write(LINE_SEPARATOR);
|
||||
currIndent += indent;
|
||||
doVisitProperties(composite.getProperties());
|
||||
currIndent = currIndent.substring(0, currIndent.length() - indent.length());
|
||||
write(currIndent);
|
||||
write('}');
|
||||
}
|
||||
|
||||
private void doVisitProperties(Map<String, Object> properties) {
|
||||
properties.forEach(
|
||||
(name, value) -> {
|
||||
if (omitNullProperties && value instanceof PNull) return;
|
||||
|
||||
write(currIndent);
|
||||
writeIdentifier(name);
|
||||
|
||||
if (value == null) { // unevaluated property
|
||||
write(" = ?");
|
||||
} else if (value instanceof Composite) {
|
||||
write(' ');
|
||||
visit(value);
|
||||
} else {
|
||||
write(" = ");
|
||||
visit(value);
|
||||
}
|
||||
|
||||
write(LINE_SEPARATOR);
|
||||
});
|
||||
}
|
||||
|
||||
private void write(char ch) {
|
||||
try {
|
||||
writer.write(ch);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void write(String str) {
|
||||
try {
|
||||
writer.write(str);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeIdentifier(String identifier) {
|
||||
if (Lexer.isRegularIdentifier(identifier)) {
|
||||
write(identifier);
|
||||
} else {
|
||||
write('`');
|
||||
write(identifier);
|
||||
write('`');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
pkl-core/src/main/java/org/pkl/core/PklBugException.java
Normal file
34
pkl-core/src/main/java/org/pkl/core/PklBugException.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
public final class PklBugException extends PklException {
|
||||
public static PklBugException unreachableCode() {
|
||||
return new PklBugException("Unreachable code.");
|
||||
}
|
||||
|
||||
public PklBugException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PklBugException(Throwable cause) {
|
||||
super("An unexpected error has occurred. Would you mind filing a bug report?", cause);
|
||||
}
|
||||
|
||||
public PklBugException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
26
pkl-core/src/main/java/org/pkl/core/PklException.java
Normal file
26
pkl-core/src/main/java/org/pkl/core/PklException.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
public class PklException extends RuntimeException {
|
||||
public PklException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PklException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
63
pkl-core/src/main/java/org/pkl/core/PklInfo.java
Normal file
63
pkl-core/src/main/java/org/pkl/core/PklInfo.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/** Information about the Pkl package index. */
|
||||
public final class PklInfo {
|
||||
private static final String PACKAGE_INDEX_HOMEPAGE = "https://pkl-lang.org/package-docs";
|
||||
|
||||
private static final PklInfo CURRENT;
|
||||
private final PackageIndex packageIndex;
|
||||
|
||||
static {
|
||||
CURRENT = new PklInfo(new PackageIndex(PACKAGE_INDEX_HOMEPAGE));
|
||||
}
|
||||
|
||||
/** The current {@link PklInfo}. */
|
||||
public static PklInfo current() {
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
/** Constructs a {@link PklInfo}. */
|
||||
PklInfo(PackageIndex packageIndex) {
|
||||
this.packageIndex = packageIndex;
|
||||
}
|
||||
|
||||
/** The Pkl package index. */
|
||||
public PackageIndex getPackageIndex() {
|
||||
return packageIndex;
|
||||
}
|
||||
|
||||
/** A Pkl package index. */
|
||||
public static final class PackageIndex {
|
||||
private final String homepage;
|
||||
|
||||
/** Constructs a {@link PackageIndex}. */
|
||||
public PackageIndex(String homepage) {
|
||||
this.homepage = homepage;
|
||||
}
|
||||
|
||||
/** The homepage of this package index. */
|
||||
public String homepage() {
|
||||
return homepage;
|
||||
}
|
||||
|
||||
/** Returns the homepage of the given package. */
|
||||
public String getPackagePage(String packageName, String packageVersion) {
|
||||
return homepage + packageName + "/" + packageVersion + "/";
|
||||
}
|
||||
}
|
||||
}
|
||||
284
pkl-core/src/main/java/org/pkl/core/Platform.java
Normal file
284
pkl-core/src/main/java/org/pkl/core/Platform.java
Normal file
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import com.oracle.truffle.api.Truffle;
|
||||
import com.oracle.truffle.api.TruffleOptions;
|
||||
import java.util.Objects;
|
||||
import org.graalvm.home.Version;
|
||||
|
||||
/**
|
||||
* Information about the Pkl release that the current program runs on. This class is the Java
|
||||
* equivalent of standard library module {@code pkl.platform}.
|
||||
*/
|
||||
public final class Platform {
|
||||
private static final Platform CURRENT;
|
||||
|
||||
static {
|
||||
var pklVersion = Release.current().version().toString();
|
||||
var osName = System.getProperty("os.name");
|
||||
if (osName.equals("Mac OS X")) osName = "macOS";
|
||||
var osVersion = System.getProperty("os.version");
|
||||
var architecture = System.getProperty("os.arch");
|
||||
|
||||
var runtimeName = Truffle.getRuntime().getName();
|
||||
var runtimeVersion = Version.getCurrent().toString();
|
||||
var vmName = TruffleOptions.AOT ? runtimeName : System.getProperty("java.vm.name");
|
||||
var vmVersion = TruffleOptions.AOT ? runtimeVersion : System.getProperty("java.vm.version");
|
||||
|
||||
CURRENT =
|
||||
new Platform(
|
||||
new Language(pklVersion),
|
||||
new Runtime(runtimeName, runtimeVersion),
|
||||
new VirtualMachine(vmName, vmVersion),
|
||||
new OperatingSystem(osName, osVersion),
|
||||
new Processor(architecture));
|
||||
}
|
||||
|
||||
private final Language language;
|
||||
private final Runtime runtime;
|
||||
private final VirtualMachine virtualMachine;
|
||||
private final OperatingSystem operatingSystem;
|
||||
private final Processor processor;
|
||||
|
||||
/** Constructs a platform. */
|
||||
public Platform(
|
||||
Language language,
|
||||
Runtime runtime,
|
||||
VirtualMachine virtualMachine,
|
||||
OperatingSystem operatingSystem,
|
||||
Processor processor) {
|
||||
this.language = language;
|
||||
this.runtime = runtime;
|
||||
this.virtualMachine = virtualMachine;
|
||||
this.operatingSystem = operatingSystem;
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
/** The Pkl release that the current program runs on. */
|
||||
public static Platform current() {
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
/** The language implementation of this platform. */
|
||||
public Language language() {
|
||||
return language;
|
||||
}
|
||||
|
||||
/** The language runtime of this platform. */
|
||||
public Runtime runtime() {
|
||||
return runtime;
|
||||
}
|
||||
|
||||
/** The virtual machine of this platform. */
|
||||
public VirtualMachine virtualMachine() {
|
||||
return virtualMachine;
|
||||
}
|
||||
|
||||
/** The operating system of this platform. */
|
||||
public OperatingSystem operatingSystem() {
|
||||
return operatingSystem;
|
||||
}
|
||||
|
||||
/** The processor of this platform. */
|
||||
public Processor processor() {
|
||||
return processor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Platform)) return false;
|
||||
|
||||
var other = (Platform) obj;
|
||||
return language.equals(other.language)
|
||||
&& runtime.equals(other.runtime)
|
||||
&& virtualMachine.equals(other.virtualMachine)
|
||||
&& operatingSystem.equals(other.operatingSystem)
|
||||
&& processor.equals(other.processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(language, runtime, virtualMachine, operatingSystem, processor);
|
||||
}
|
||||
|
||||
/** The language implementation of a platform. */
|
||||
public static final class Language {
|
||||
private final String version;
|
||||
|
||||
/** Constructs a {@link Language}. */
|
||||
public Language(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/** The version of this language implementation. */
|
||||
public String version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Language)) return false;
|
||||
|
||||
var other = (Language) obj;
|
||||
return version.equals(other.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return version.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/** The language runtime of a platform. */
|
||||
public static final class Runtime {
|
||||
private final String name;
|
||||
private final String version;
|
||||
|
||||
/** Constructs a {@link Runtime}. */
|
||||
public Runtime(String name, String version) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/** The name of this language runtime. */
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** The version of this language runtime. */
|
||||
public String version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Runtime)) return false;
|
||||
|
||||
var other = (Runtime) obj;
|
||||
return name.equals(other.name) && version.equals(other.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, version);
|
||||
}
|
||||
}
|
||||
|
||||
/** The virtual machine of a platform. */
|
||||
public static final class VirtualMachine {
|
||||
private final String name;
|
||||
private final String version;
|
||||
|
||||
/** Constructs a {@link VirtualMachine}. */
|
||||
public VirtualMachine(String name, String version) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/** The name of this virtual machine. */
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** The version of this virtual machine. */
|
||||
public String version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof VirtualMachine)) return false;
|
||||
|
||||
var other = (VirtualMachine) obj;
|
||||
return name.equals(other.name) && version.equals(other.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, version);
|
||||
}
|
||||
}
|
||||
|
||||
/** The operating system of a platform. */
|
||||
public static final class OperatingSystem {
|
||||
private final String name;
|
||||
private final String version;
|
||||
|
||||
/** Constructs an {@link OperatingSystem}. */
|
||||
public OperatingSystem(String name, String version) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/** The name of this operating system. */
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** The version of this operating system. */
|
||||
public String version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof OperatingSystem)) return false;
|
||||
|
||||
var other = (OperatingSystem) obj;
|
||||
return name.equals(other.name) && version.equals(other.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, version);
|
||||
}
|
||||
}
|
||||
|
||||
/** The processor of a platform. */
|
||||
public static final class Processor {
|
||||
private final String architecture;
|
||||
|
||||
/** Constructs a {@link Processor}. */
|
||||
public Processor(String architecture) {
|
||||
this.architecture = architecture;
|
||||
}
|
||||
|
||||
/** The instruction set architecture of this processor. */
|
||||
public String architecture() {
|
||||
return architecture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Processor)) return false;
|
||||
|
||||
var other = (Processor) obj;
|
||||
return architecture.equals(other.architecture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return architecture.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
223
pkl-core/src/main/java/org/pkl/core/PropertiesRenderer.java
Normal file
223
pkl-core/src/main/java/org/pkl/core/PropertiesRenderer.java
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.util.Nullable;
|
||||
import org.pkl.core.util.properties.PropertiesUtils;
|
||||
|
||||
// To instantiate this class, use ValueRenderers.properties().
|
||||
final class PropertiesRenderer implements ValueRenderer {
|
||||
private final Writer writer;
|
||||
private final boolean omitNullProperties;
|
||||
private final boolean restrictCharset;
|
||||
|
||||
public PropertiesRenderer(Writer writer, boolean omitNullProperties, boolean restrictCharset) {
|
||||
this.writer = writer;
|
||||
this.omitNullProperties = omitNullProperties;
|
||||
this.restrictCharset = restrictCharset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderDocument(Object value) {
|
||||
new Visitor().renderDocument(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderValue(Object value) {
|
||||
new Visitor().renderValue(value);
|
||||
}
|
||||
|
||||
private class Visitor implements ValueConverter<String> {
|
||||
|
||||
public void renderDocument(Object value) {
|
||||
if (value instanceof Composite) {
|
||||
doVisitMap(null, ((Composite) value).getProperties());
|
||||
} else if (value instanceof Map) {
|
||||
doVisitMap(null, (Map<?, ?>) value);
|
||||
} else if (value instanceof Pair) {
|
||||
Pair<?, ?> pair = (Pair<?, ?>) value;
|
||||
doVisitKeyAndValue(null, pair.getFirst(), pair.getSecond());
|
||||
} else {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"The top-level value of a Java properties file must have type `Composite`, `Map`, or `Pair`, but got type `%s`.",
|
||||
value.getClass().getTypeName()));
|
||||
}
|
||||
}
|
||||
|
||||
public void renderValue(Object value) {
|
||||
write(convert(value), false, restrictCharset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertNull() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertString(String value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertInt(Long value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertFloat(Double value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertBoolean(Boolean value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertDuration(Duration value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Duration` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertDataSize(DataSize value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `DataSize` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertPair(Pair<?, ?> value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Pair` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertList(List<?> value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `List` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertSet(Set<?> value) {
|
||||
throw new RendererException(
|
||||
String.format("Values of type `Set` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertMap(Map<?, ?> value) {
|
||||
throw new RendererException(
|
||||
String.format("Values of type `Map` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertObject(PObject value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Object` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertModule(PModule value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Module` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertClass(PClass value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Class` cannot be rendered as Properties. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertTypeAlias(TypeAlias value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `TypeAlias` cannot be rendered as Properties. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertRegex(Pattern value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Regex` cannot be rendered as Properties. Value: %s", value));
|
||||
}
|
||||
|
||||
private void doVisitMap(@Nullable String keyPrefix, Map<?, ?> map) {
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
doVisitKeyAndValue(keyPrefix, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void doVisitKeyAndValue(@Nullable String keyPrefix, Object key, Object value) {
|
||||
if (omitNullProperties && value instanceof PNull) return;
|
||||
|
||||
var keyString = keyPrefix == null ? convert(key) : keyPrefix + "." + convert(key);
|
||||
|
||||
if (value instanceof Composite) {
|
||||
doVisitMap(keyString, ((Composite) value).getProperties());
|
||||
} else if (value instanceof Map) {
|
||||
doVisitMap(keyString, (Map<?, ?>) value);
|
||||
} else {
|
||||
write(keyString, true, restrictCharset);
|
||||
writeSeparator();
|
||||
write(convert(value), false, restrictCharset);
|
||||
writeLineBreak();
|
||||
}
|
||||
}
|
||||
|
||||
private void write(String value, boolean escapeSpace, boolean restrictCharset) {
|
||||
try {
|
||||
writer.write(
|
||||
PropertiesUtils.renderPropertiesKeyOrValue(value, escapeSpace, restrictCharset));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSeparator() {
|
||||
try {
|
||||
writer.write(' ');
|
||||
writer.write('=');
|
||||
writer.write(' ');
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLineBreak() {
|
||||
try {
|
||||
writer.write('\n');
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
276
pkl-core/src/main/java/org/pkl/core/Release.java
Normal file
276
pkl-core/src/main/java/org/pkl/core/Release.java
Normal file
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import com.oracle.truffle.api.TruffleOptions;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Information about the Pkl release that the current program runs on. This class is the Java
|
||||
* equivalent of standard library module {@code pkl.release}.
|
||||
*/
|
||||
public class Release {
|
||||
private static final String SOURCE_CODE_HOMEPAGE = "https://github.com/apple/pkl/";
|
||||
private static final String DOCUMENTATION_HOMEPAGE = "https://pkl-lang.org/main/";
|
||||
|
||||
private static final Release CURRENT;
|
||||
|
||||
static {
|
||||
var properties = new Properties();
|
||||
|
||||
try (var stream = Release.class.getResourceAsStream("Release.properties")) {
|
||||
if (stream == null) {
|
||||
throw new AssertionError("Failed to locate `Release.properties`.");
|
||||
}
|
||||
properties.load(stream);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Failed to load `Release.properties`.", e);
|
||||
}
|
||||
|
||||
var version = Version.parse(properties.getProperty("version"));
|
||||
var commitId = properties.getProperty("commitId");
|
||||
var osName = System.getProperty("os.name");
|
||||
if (osName.equals("Mac OS X")) osName = "macOS";
|
||||
var osVersion = System.getProperty("os.version");
|
||||
var os = osName + " " + osVersion;
|
||||
var flavor = TruffleOptions.AOT ? "native" : "Java " + System.getProperty("java.version");
|
||||
var versionInfo = "Pkl " + version + " (" + os + ", " + flavor + ")";
|
||||
var commitish = version.isNormal() ? version.toString() : commitId;
|
||||
var docsVersion = version.isNormal() ? version.toString() : "latest";
|
||||
var docsHomepage = DOCUMENTATION_HOMEPAGE + docsVersion + "/";
|
||||
var stdlibModules =
|
||||
new LinkedHashSet<>(List.of(properties.getProperty("stdlibModules").split(",")));
|
||||
|
||||
CURRENT =
|
||||
new Release(
|
||||
version,
|
||||
os,
|
||||
flavor,
|
||||
versionInfo,
|
||||
commitId,
|
||||
new SourceCode(SOURCE_CODE_HOMEPAGE, commitish),
|
||||
new Documentation(docsHomepage),
|
||||
new StandardLibrary(stdlibModules));
|
||||
}
|
||||
|
||||
private final Version version;
|
||||
private final String os;
|
||||
private final String flavor;
|
||||
private final String versionInfo;
|
||||
private final String commitId;
|
||||
private final SourceCode sourceCode;
|
||||
private final Documentation documentation;
|
||||
private final StandardLibrary standardLibrary;
|
||||
|
||||
/** Constructs a release. */
|
||||
public Release(
|
||||
Version version,
|
||||
String os,
|
||||
String flavor,
|
||||
String versionInfo,
|
||||
String commitId,
|
||||
SourceCode sourceCode,
|
||||
Documentation documentation,
|
||||
StandardLibrary standardLibrary) {
|
||||
this.version = version;
|
||||
this.os = os;
|
||||
this.flavor = flavor;
|
||||
this.versionInfo = versionInfo;
|
||||
this.commitId = commitId;
|
||||
this.sourceCode = sourceCode;
|
||||
this.documentation = documentation;
|
||||
this.standardLibrary = standardLibrary;
|
||||
}
|
||||
|
||||
/** The Pkl release that the current program runs on. */
|
||||
public static Release current() {
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
/** The version of this release. */
|
||||
public Version version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/** The operating system (name and version) this release is running on. */
|
||||
public String os() {
|
||||
return os;
|
||||
}
|
||||
|
||||
/** The flavor of this release (native, or Java and JVM version). */
|
||||
public String flavor() {
|
||||
return flavor;
|
||||
}
|
||||
|
||||
/** The output of {@code pkl --version} for this release. */
|
||||
public String versionInfo() {
|
||||
return versionInfo;
|
||||
}
|
||||
|
||||
/** The Git commit ID of this release. */
|
||||
public String commitId() {
|
||||
return commitId;
|
||||
}
|
||||
|
||||
/** The source code of this release. */
|
||||
public SourceCode sourceCode() {
|
||||
return sourceCode;
|
||||
}
|
||||
|
||||
/** The documentation of this release. */
|
||||
public Documentation documentation() {
|
||||
return documentation;
|
||||
}
|
||||
|
||||
/** The standard library of this release. */
|
||||
public StandardLibrary standardLibrary() {
|
||||
return standardLibrary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Release)) return false;
|
||||
|
||||
var other = (Release) obj;
|
||||
return version.equals(other.version)
|
||||
&& versionInfo.equals(other.versionInfo)
|
||||
&& commitId.equals(other.commitId)
|
||||
&& sourceCode.equals(other.sourceCode)
|
||||
&& documentation.equals(other.documentation)
|
||||
&& standardLibrary.equals(other.standardLibrary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(version, versionInfo, commitId, sourceCode, documentation, standardLibrary);
|
||||
}
|
||||
|
||||
/** The source code of a Pkl release. */
|
||||
public static final class SourceCode {
|
||||
private final String homepage;
|
||||
private final String version;
|
||||
|
||||
/** Constructs a {@link SourceCode}. */
|
||||
public SourceCode(String homepage, String version) {
|
||||
this.homepage = homepage;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/** The homepage of this source code. */
|
||||
public String homepage() {
|
||||
return homepage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source code page of the file with the given path. <b>Note:</b> Files may be moved
|
||||
* or deleted anytime.
|
||||
*/
|
||||
public String getFilePage(String path) {
|
||||
return homepage + "blob/" + version + "/" + path;
|
||||
}
|
||||
|
||||
/** The source code scheme for the stdlib module. */
|
||||
public String getSourceCodeUrlScheme() {
|
||||
return homepage + "blob/" + version + "/stdlib%{path}#L%{line}-L%{endLine}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof SourceCode)) return false;
|
||||
|
||||
var other = (SourceCode) obj;
|
||||
return homepage.equals(other.homepage) && version.equals(other.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(homepage, version);
|
||||
}
|
||||
}
|
||||
|
||||
/** The documentation of a Pkl release. */
|
||||
public static final class Documentation {
|
||||
private final String homepage;
|
||||
|
||||
/** Constructs a {@link Documentation}. */
|
||||
public Documentation(String homepage) {
|
||||
this.homepage = homepage;
|
||||
}
|
||||
|
||||
/** The homepage of this documentation. */
|
||||
public String homepage() {
|
||||
return homepage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Documentation)) return false;
|
||||
|
||||
var other = (Documentation) obj;
|
||||
return homepage.equals(other.homepage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return homepage.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard library of a Pkl release.
|
||||
*
|
||||
* @since 0.21.0
|
||||
*/
|
||||
public static final class StandardLibrary {
|
||||
private final Set<String> modules;
|
||||
|
||||
/** Constructs a {@link StandardLibrary}. */
|
||||
public StandardLibrary(Set<String> modules) {
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
/** The modules of this standard library. */
|
||||
public Set<String> modules() {
|
||||
return modules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof StandardLibrary)) return false;
|
||||
|
||||
var other = (StandardLibrary) obj;
|
||||
return modules.equals(other.modules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return modules.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
pkl-core/src/main/java/org/pkl/core/RendererException.java
Normal file
22
pkl-core/src/main/java/org/pkl/core/RendererException.java
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
public class RendererException extends RuntimeException {
|
||||
public RendererException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
43
pkl-core/src/main/java/org/pkl/core/SecurityManager.java
Normal file
43
pkl-core/src/main/java/org/pkl/core/SecurityManager.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Enforces a security model during {@link Evaluator evaluation}.
|
||||
*
|
||||
* <p>Use {@link SecurityManagers} to obtain or construct an instance of this type.
|
||||
*/
|
||||
public interface SecurityManager {
|
||||
/**
|
||||
* Checks if the given module may be resolved. This check is required before any attempt is made
|
||||
* to access the given URI.
|
||||
*/
|
||||
void checkResolveModule(URI uri) throws SecurityManagerException;
|
||||
|
||||
/** Checks if the given importing module may import the given imported module. */
|
||||
void checkImportModule(URI importingModule, URI importedModule) throws SecurityManagerException;
|
||||
|
||||
/** Checks if the given resource may be read. */
|
||||
void checkReadResource(URI resource) throws SecurityManagerException;
|
||||
|
||||
/**
|
||||
* Checks if the given resource may be resolved. This check is required before any attempt is made
|
||||
* to access the given URI.
|
||||
*/
|
||||
void checkResolveResource(URI resource) throws SecurityManagerException;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/**
|
||||
* Parent interface to builder classes for configuring a {@link SecurityManager}.
|
||||
*
|
||||
* @param <B> concrete type of the builder class to maintain covariance in inherited methods
|
||||
*/
|
||||
public interface SecurityManagerBuilder<B extends SecurityManagerBuilder<B>> {
|
||||
B addAllowedModule(Pattern pattern);
|
||||
|
||||
B addAllowedModules(Collection<Pattern> patterns);
|
||||
|
||||
B setAllowedModules(Collection<Pattern> patterns);
|
||||
|
||||
List<Pattern> getAllowedModules();
|
||||
|
||||
B addAllowedResource(Pattern pattern);
|
||||
|
||||
B addAllowedResources(Collection<Pattern> patterns);
|
||||
|
||||
B setAllowedResources(Collection<Pattern> patterns);
|
||||
|
||||
List<Pattern> getAllowedResources();
|
||||
|
||||
B setRootDir(@Nullable Path rootDir);
|
||||
|
||||
@Nullable
|
||||
Path getRootDir();
|
||||
|
||||
SecurityManager build();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/**
|
||||
* A SecurityManagerException declares that a violation occured during an external i/o operation.
|
||||
*
|
||||
* <p>{@link SecurityManagerException#getMessage()} is passed to users when errors arise.
|
||||
*/
|
||||
public class SecurityManagerException extends Exception {
|
||||
public SecurityManagerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
304
pkl-core/src/main/java/org/pkl/core/SecurityManagers.java
Normal file
304
pkl-core/src/main/java/org/pkl/core/SecurityManagers.java
Normal file
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.util.ErrorMessages;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** A provider for {@link SecurityManager}s. */
|
||||
public final class SecurityManagers {
|
||||
private SecurityManagers() {}
|
||||
|
||||
/**
|
||||
* Returns the list of module patterns that the {@link #defaultManager default security manager}
|
||||
* will use to determine if a module import may be resolved.
|
||||
*/
|
||||
public static final List<Pattern> defaultAllowedModules =
|
||||
List.of(
|
||||
Pattern.compile("repl:"),
|
||||
Pattern.compile("file:"),
|
||||
// for evaluating URLs returned by `Class(Loader).getResource()`
|
||||
Pattern.compile("jar:file:"),
|
||||
Pattern.compile("modulepath:"),
|
||||
Pattern.compile("https:"),
|
||||
Pattern.compile("pkl:"),
|
||||
Pattern.compile("package:"),
|
||||
Pattern.compile("projectpackage:"));
|
||||
|
||||
/**
|
||||
* Returns the list of resource patterns that the {@link #defaultManager default security manager}
|
||||
* will use to determine if an external resource may be read.
|
||||
*/
|
||||
public static final List<Pattern> defaultAllowedResources =
|
||||
List.of(
|
||||
Pattern.compile("prop:"),
|
||||
Pattern.compile("env:"),
|
||||
Pattern.compile("file:"),
|
||||
Pattern.compile("modulepath:"),
|
||||
Pattern.compile("package:"),
|
||||
Pattern.compile("projectpackage:"),
|
||||
Pattern.compile("https:"));
|
||||
|
||||
/**
|
||||
* Returns the mapping from module URIs to trust levels used by the {@link #defaultManager default
|
||||
* security manager}.
|
||||
*
|
||||
* <p>Trust levels are used to determine whether a module may import another module. Only modules
|
||||
* with the same or a lower trust level may be imported.
|
||||
*
|
||||
* <p>This mapping supports a fixed set of module URI schemes. Local modules are given a higher
|
||||
* trust level than remote modules. For example, a local file may import a remote file, but not
|
||||
* the other way around.
|
||||
*/
|
||||
public static final Function<URI, Integer> defaultTrustLevels =
|
||||
SecurityManagers::getDefaultTrustLevel;
|
||||
|
||||
private static int getDefaultTrustLevel(URI uri) {
|
||||
switch (uri.getScheme()) {
|
||||
case "repl":
|
||||
return 40;
|
||||
case "file":
|
||||
return uri.getHost() == null ? 30 : 10;
|
||||
case "jar":
|
||||
// use trust level of embedded URL
|
||||
return getDefaultTrustLevel(URI.create(uri.toString().substring(4)));
|
||||
case "modulepath":
|
||||
return 20;
|
||||
case "pkl":
|
||||
return 0;
|
||||
default:
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link #standard standard security manager} with {@link #defaultAllowedModules
|
||||
* default allowed modules}, {@link #defaultAllowedResources default allowed resources}, {@link
|
||||
* #defaultTrustLevels default trust levels}, and no root directory for modules and resources.
|
||||
*/
|
||||
public static final SecurityManager defaultManager =
|
||||
new Standard(defaultAllowedModules, defaultAllowedResources, defaultTrustLevels, null);
|
||||
|
||||
/**
|
||||
* Creates a {@link SecurityManager} that determines whether a module can be resolved based on the
|
||||
* given list of module URI patterns, whether an external resources can be read based on the given
|
||||
* list of resource URI patterns, and whether a module can import another module based on the
|
||||
* given module trust levels. A module can only import modules with the same or a lower trust
|
||||
* level.
|
||||
*
|
||||
* <p>If {@code rootDir} is non-null, access to file-based modules and resources is restricted to
|
||||
* those located under {@code rootDir}. Any symlinks are resolved before this check is performed.
|
||||
*/
|
||||
public static SecurityManager standard(
|
||||
List<Pattern> allowedModules,
|
||||
List<Pattern> allowedResources,
|
||||
Function<URI, Integer> trustLevels,
|
||||
@Nullable Path rootDir) {
|
||||
return new Standard(allowedModules, allowedResources, trustLevels, rootDir);
|
||||
}
|
||||
|
||||
/** Creates an unconfigured builder for setting up a standard {@link SecurityManager}. */
|
||||
public static StandardBuilder standardBuilder() {
|
||||
return new StandardBuilder();
|
||||
}
|
||||
|
||||
private static class Standard implements SecurityManager {
|
||||
private final List<Pattern> allowedModules;
|
||||
private final List<Pattern> allowedResources;
|
||||
private final Function<URI, Integer> trustLevels;
|
||||
private final @Nullable Path rootDir;
|
||||
|
||||
Standard(
|
||||
List<Pattern> allowedModules,
|
||||
List<Pattern> allowedResources,
|
||||
Function<URI, Integer> trustLevels,
|
||||
@Nullable Path rootDir) {
|
||||
this.allowedModules = allowedModules;
|
||||
this.allowedResources = allowedResources;
|
||||
this.trustLevels = trustLevels;
|
||||
this.rootDir = normalizePath(rootDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkResolveModule(URI uri) throws SecurityManagerException {
|
||||
checkRead(uri, allowedModules, "moduleNotInAllowList");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkResolveResource(URI resource) throws SecurityManagerException {
|
||||
checkRead(resource, allowedResources, "resourceNotInAllowList");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkReadResource(URI uri) throws SecurityManagerException {
|
||||
checkRead(uri, allowedResources, "resourceNotInAllowList");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkImportModule(URI importingModule, URI importedModule)
|
||||
throws SecurityManagerException {
|
||||
var importingTrustLevel = trustLevels.apply(importingModule);
|
||||
var importedTrustLevel = trustLevels.apply(importedModule);
|
||||
|
||||
if (importingTrustLevel < importedTrustLevel) {
|
||||
var message =
|
||||
ErrorMessages.create("insufficientModuleTrustLevel", importedModule, importingModule);
|
||||
throw new SecurityManagerException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Path normalizePath(@Nullable Path path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (Files.exists(path)) {
|
||||
return path.toRealPath();
|
||||
}
|
||||
return path.toAbsolutePath();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRead(URI uri, List<Pattern> allowedPatterns, String errorMessageKey)
|
||||
throws SecurityManagerException {
|
||||
for (var pattern : allowedPatterns) {
|
||||
if (pattern.matcher(uri.toString()).lookingAt()) {
|
||||
checkIsUnderRootDir(uri, errorMessageKey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var message = ErrorMessages.create(errorMessageKey, uri);
|
||||
throw new SecurityManagerException(message);
|
||||
}
|
||||
|
||||
private void checkIsUnderRootDir(URI uri, String errorMessageKey)
|
||||
throws SecurityManagerException {
|
||||
if (!uri.isAbsolute()) {
|
||||
throw new AssertionError("Expected absolute URI but got: " + uri);
|
||||
}
|
||||
|
||||
if (rootDir == null || !uri.getScheme().equals("file")) return;
|
||||
|
||||
var path = Path.of(uri);
|
||||
if (Files.exists(path)) {
|
||||
try {
|
||||
path = path.toRealPath();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
} else {
|
||||
// perform check even if file doesn't exist
|
||||
// to avoid leaking information on whether files outside root dir exist
|
||||
path = path.normalize();
|
||||
}
|
||||
|
||||
if (!path.startsWith(rootDir)) {
|
||||
var message = ErrorMessages.create(errorMessageKey, uri);
|
||||
throw new SecurityManagerException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class StandardBuilder implements SecurityManagerBuilder<StandardBuilder> {
|
||||
|
||||
private final List<Pattern> allowedModules = new ArrayList<>();
|
||||
|
||||
private final List<Pattern> allowedResources = new ArrayList<>();
|
||||
|
||||
private Path rootDir;
|
||||
|
||||
private StandardBuilder() {}
|
||||
|
||||
@Override
|
||||
public StandardBuilder addAllowedModule(Pattern pattern) {
|
||||
allowedModules.add(pattern);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandardBuilder addAllowedModules(Collection<Pattern> patterns) {
|
||||
allowedModules.addAll(patterns);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandardBuilder setAllowedModules(Collection<Pattern> patterns) {
|
||||
allowedModules.clear();
|
||||
allowedModules.addAll(patterns);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pattern> getAllowedModules() {
|
||||
return allowedModules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandardBuilder addAllowedResource(Pattern pattern) {
|
||||
allowedResources.add(pattern);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandardBuilder addAllowedResources(Collection<Pattern> patterns) {
|
||||
allowedResources.addAll(patterns);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandardBuilder setAllowedResources(Collection<Pattern> patterns) {
|
||||
allowedResources.clear();
|
||||
allowedResources.addAll(patterns);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pattern> getAllowedResources() {
|
||||
return allowedResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandardBuilder setRootDir(@Nullable Path rootDir) {
|
||||
this.rootDir = rootDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Path getRootDir() {
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecurityManager build() {
|
||||
if (allowedResources.isEmpty() && allowedModules.isEmpty()) {
|
||||
throw new IllegalStateException("No security manager set.");
|
||||
}
|
||||
|
||||
return new Standard(allowedModules, allowedResources, defaultTrustLevels, rootDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
pkl-core/src/main/java/org/pkl/core/StackFrame.java
Normal file
132
pkl-core/src/main/java/org/pkl/core/StackFrame.java
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** An element of a Pkl stack trace. */
|
||||
// better name would be `StackTraceElement`
|
||||
public final class StackFrame {
|
||||
private final String moduleUri;
|
||||
|
||||
// TODO: can we make this non-null?
|
||||
private final @Nullable String memberName;
|
||||
|
||||
private final List<String> sourceLines;
|
||||
private final int startLine;
|
||||
private final int startColumn;
|
||||
private final int endLine;
|
||||
private final int endColumn;
|
||||
|
||||
public StackFrame(
|
||||
String moduleUri,
|
||||
@Nullable String memberName,
|
||||
List<String> sourceLines,
|
||||
int startLine,
|
||||
int startColumn,
|
||||
int endLine,
|
||||
int endColumn) {
|
||||
|
||||
assert startLine >= 1;
|
||||
assert startColumn >= 1;
|
||||
assert endLine >= 1;
|
||||
assert endColumn >= 1;
|
||||
assert startLine <= endLine;
|
||||
assert startLine < endLine || startColumn <= endColumn;
|
||||
|
||||
this.moduleUri = moduleUri;
|
||||
this.memberName = memberName;
|
||||
this.sourceLines = sourceLines;
|
||||
this.startLine = startLine;
|
||||
this.startColumn = startColumn;
|
||||
this.endLine = endLine;
|
||||
this.endColumn = endColumn;
|
||||
}
|
||||
|
||||
/** Returns the module URI to display for this frame. May not be a syntactically valid URI. */
|
||||
public String getModuleUri() {
|
||||
return moduleUri;
|
||||
}
|
||||
|
||||
/** Returns a copy of this frame with the given module URI. */
|
||||
public StackFrame withModuleUri(String moduleUri) {
|
||||
return new StackFrame(
|
||||
moduleUri, memberName, sourceLines, startLine, startColumn, endLine, endColumn);
|
||||
}
|
||||
|
||||
/** Returns the qualified name of the property or function corresponding to this frame, if any. */
|
||||
public @Nullable String getMemberName() {
|
||||
return memberName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lines of source code corresponding to this frame. The first line has line number
|
||||
* {@link #getStartLine}. The last line has line number {@link #getEndLine()}.
|
||||
*/
|
||||
public List<String> getSourceLines() {
|
||||
return sourceLines;
|
||||
}
|
||||
|
||||
/** Returns the start line number (1-based) corresponding to this frame. */
|
||||
public int getStartLine() {
|
||||
return startLine;
|
||||
}
|
||||
|
||||
/** Returns the start column number (1-based) corresponding to this frame. */
|
||||
public int getStartColumn() {
|
||||
return startColumn;
|
||||
}
|
||||
|
||||
/** Returns the end line number (1-based) corresponding to this frame. */
|
||||
public int getEndLine() {
|
||||
return endLine;
|
||||
}
|
||||
|
||||
/** Returns the end column number (1-based) corresponding to this frame. */
|
||||
public int getEndColumn() {
|
||||
return endColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof StackFrame)) return false;
|
||||
|
||||
var other = (StackFrame) obj;
|
||||
|
||||
if (startLine != other.startLine) return false;
|
||||
if (startColumn != other.startColumn) return false;
|
||||
if (endLine != other.endLine) return false;
|
||||
if (endColumn != other.endColumn) return false;
|
||||
if (!moduleUri.equals(other.moduleUri)) return false;
|
||||
if (!Objects.equals(memberName, other.memberName)) return false;
|
||||
return sourceLines.equals(other.sourceLines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
var result = moduleUri.hashCode();
|
||||
result = 31 * result + Objects.hashCode(memberName);
|
||||
result = 31 * result + sourceLines.hashCode();
|
||||
result = 31 * result + startLine;
|
||||
result = 31 * result + startColumn;
|
||||
result = 31 * result + endLine;
|
||||
result = 31 * result + endColumn;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/** Transforms stack frames to make Pkl stack trace output more useful. */
|
||||
@FunctionalInterface
|
||||
public interface StackFrameTransformer extends Function<StackFrame, StackFrame> {
|
||||
default StackFrameTransformer andThen(StackFrameTransformer transformer) {
|
||||
return stackTrace -> transformer.apply(apply(stackTrace));
|
||||
}
|
||||
}
|
||||
120
pkl-core/src/main/java/org/pkl/core/StackFrameTransformers.java
Normal file
120
pkl-core/src/main/java/org/pkl/core/StackFrameTransformers.java
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.StreamSupport;
|
||||
import org.pkl.core.packages.PackageAssetUri;
|
||||
import org.pkl.core.runtime.VmContext;
|
||||
import org.pkl.core.settings.PklSettings;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
|
||||
/** Static factory for stack frame transformers. */
|
||||
public final class StackFrameTransformers {
|
||||
|
||||
private StackFrameTransformers() {}
|
||||
|
||||
public static final StackFrameTransformer empty = s -> s;
|
||||
|
||||
public static final StackFrameTransformer convertStdLibUrlToExternalUrl =
|
||||
frame -> {
|
||||
var uri = frame.getModuleUri();
|
||||
if (uri.startsWith("pkl:")) {
|
||||
var moduleName = uri.substring(4);
|
||||
return frame.withModuleUri(
|
||||
Release.current()
|
||||
.sourceCode()
|
||||
.getFilePage("stdlib/" + moduleName + ".pkl#L" + frame.getStartLine()));
|
||||
}
|
||||
return frame;
|
||||
};
|
||||
|
||||
public static StackFrameTransformer replacePackageUriWithSourceCodeUrl =
|
||||
frame -> {
|
||||
var uri = URI.create(frame.getModuleUri());
|
||||
if (!uri.getScheme().equalsIgnoreCase("package")) {
|
||||
return frame;
|
||||
}
|
||||
try {
|
||||
var assetUri = new PackageAssetUri(uri);
|
||||
var packageResolver = VmContext.get(null).getPackageResolver();
|
||||
assert packageResolver != null;
|
||||
var pkg = packageResolver.getDependencyMetadata(assetUri.getPackageUri(), null);
|
||||
var sourceCode = pkg.getSourceCodeUrlScheme();
|
||||
if (sourceCode == null) {
|
||||
return frame;
|
||||
}
|
||||
return transformUri(frame, uri.getFragment(), sourceCode);
|
||||
} catch (IOException | URISyntaxException | SecurityManagerException e) {
|
||||
// should never get here. by this point, we should have already performed all validation
|
||||
// and downloaded all assets.
|
||||
throw PklBugException.unreachableCode();
|
||||
}
|
||||
};
|
||||
|
||||
public static final StackFrameTransformer fromServiceProviders = loadFromServiceProviders();
|
||||
|
||||
public static final StackFrameTransformer defaultTransformer =
|
||||
fromServiceProviders
|
||||
.andThen(convertStdLibUrlToExternalUrl)
|
||||
.andThen(replacePackageUriWithSourceCodeUrl);
|
||||
|
||||
private static StackFrame transformUri(StackFrame frame, String path, String format) {
|
||||
var uri = frame.getModuleUri();
|
||||
var newUri =
|
||||
format
|
||||
.replace("%{path}", path)
|
||||
.replace("%{url}", uri)
|
||||
.replace("%{line}", String.valueOf(frame.getStartLine()))
|
||||
.replace("%{endLine}", String.valueOf(frame.getEndLine()))
|
||||
.replace("%{column}", String.valueOf(frame.getStartColumn()))
|
||||
.replace("%{endColumn}", String.valueOf(frame.getEndColumn()));
|
||||
return frame.withModuleUri(newUri);
|
||||
}
|
||||
|
||||
public static StackFrameTransformer convertFilePathToUriScheme(String scheme) {
|
||||
return frame -> {
|
||||
var uri = frame.getModuleUri();
|
||||
if (!uri.startsWith("file:")) return frame;
|
||||
|
||||
return transformUri(frame, Path.of(URI.create(uri)).toString(), scheme);
|
||||
};
|
||||
}
|
||||
|
||||
public static StackFrameTransformer relativizeModuleUri(URI baseUri) {
|
||||
return frame -> {
|
||||
var uri = URI.create(frame.getModuleUri());
|
||||
var relativized = baseUri.relativize(uri);
|
||||
return frame.withModuleUri(relativized.toString());
|
||||
};
|
||||
}
|
||||
|
||||
public static StackFrameTransformer createDefault(PklSettings settings) {
|
||||
return defaultTransformer
|
||||
// order is relevant
|
||||
.andThen(convertFilePathToUriScheme(settings.getEditor().getUrlScheme()));
|
||||
}
|
||||
|
||||
private static StackFrameTransformer loadFromServiceProviders() {
|
||||
var loader = IoUtils.createServiceLoader(StackFrameTransformer.class);
|
||||
return StreamSupport.stream(loader.spliterator(), false)
|
||||
.reduce((t1, t2) -> t1.andThen(t2))
|
||||
.orElse(t -> t); // use no-op transformer if no service providers found
|
||||
}
|
||||
}
|
||||
104
pkl-core/src/main/java/org/pkl/core/TypeAlias.java
Normal file
104
pkl-core/src/main/java/org/pkl/core/TypeAlias.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.pkl.core.util.LateInit;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Java representation of a {@code pkl.base#TypeAlias} value. */
|
||||
public final class TypeAlias extends Member implements Value {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final String moduleName;
|
||||
private final String qualifiedName;
|
||||
private final List<TypeParameter> typeParameters;
|
||||
|
||||
@LateInit private PType aliasedType;
|
||||
|
||||
public TypeAlias(
|
||||
@Nullable String docComment,
|
||||
SourceLocation sourceLocation,
|
||||
Set<Modifier> modifiers,
|
||||
List<PObject> annotations,
|
||||
String simpleName,
|
||||
String moduleName,
|
||||
String qualifiedName,
|
||||
List<TypeParameter> typeParameters) {
|
||||
super(docComment, sourceLocation, modifiers, annotations, simpleName);
|
||||
this.moduleName = moduleName;
|
||||
this.qualifiedName = qualifiedName;
|
||||
this.typeParameters = typeParameters;
|
||||
}
|
||||
|
||||
public void initAliasedType(PType type) {
|
||||
assert aliasedType == null;
|
||||
aliasedType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the module that this type alias is declared in. Note that a module name is
|
||||
* not guaranteed to be unique, especially if it not declared but inferred from the module URI.
|
||||
*/
|
||||
public String getModuleName() {
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the qualified name of this type alias, `moduleName#typeAliasName`. Note that a
|
||||
* qualified type alias name is not guaranteed to be unique, especially if the module name is not
|
||||
* declared but inferred from the module URI.
|
||||
*/
|
||||
public String getQualifiedName() {
|
||||
return qualifiedName;
|
||||
}
|
||||
|
||||
/** Returns the name of this type alias for use in user-facing messages. */
|
||||
public String getDisplayName() {
|
||||
// display `String` rather than `pkl.base#String`, etc.
|
||||
return moduleName.equals("pkl.base") ? getSimpleName() : qualifiedName;
|
||||
}
|
||||
|
||||
public List<TypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
/** Returns the type that this type alias stands for. */
|
||||
public PType getAliasedType() {
|
||||
assert aliasedType != null;
|
||||
return aliasedType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ValueVisitor visitor) {
|
||||
visitor.visitTypeAlias(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(ValueConverter<T> converter) {
|
||||
return converter.convertTypeAlias(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PClassInfo<?> getClassInfo() {
|
||||
return PClassInfo.TypeAlias;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getDisplayName();
|
||||
}
|
||||
}
|
||||
80
pkl-core/src/main/java/org/pkl/core/TypeParameter.java
Normal file
80
pkl-core/src/main/java/org/pkl/core/TypeParameter.java
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.pkl.core.util.LateInit;
|
||||
|
||||
/** A type parameter of a generic class, type alias, or method. */
|
||||
public final class TypeParameter implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final Variance variance;
|
||||
private final String name;
|
||||
private final int index;
|
||||
|
||||
@LateInit private volatile Member owner;
|
||||
|
||||
public TypeParameter(Variance variance, String name, int index) {
|
||||
this.variance = variance;
|
||||
this.name = name;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the generic class, type alias, or method that this type parameter belongs to. This
|
||||
* method must be called as part of initializing this object. It is kept separate from the
|
||||
* constructor to help clients avoid circular evaluation.
|
||||
*/
|
||||
public void initOwner(Member owner) {
|
||||
assert this.owner == null;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/** Returns the generic class, type alias, or method that this type parameter belongs to. */
|
||||
public Member getOwner() {
|
||||
assert owner != null;
|
||||
return owner;
|
||||
}
|
||||
|
||||
/** Returns the variance of this type parameter. */
|
||||
public Variance getVariance() {
|
||||
return variance;
|
||||
}
|
||||
|
||||
/** Returns the name of this type parameter. */
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of this type parameter in its owner's type parameter list, starting from
|
||||
* zero.
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/** The variance of a type parameter. */
|
||||
public enum Variance {
|
||||
INVARIANT,
|
||||
/** An `out` parameter. */
|
||||
COVARIANT,
|
||||
/** An `in` parameter. */
|
||||
CONTRAVARIANT
|
||||
}
|
||||
}
|
||||
48
pkl-core/src/main/java/org/pkl/core/Value.java
Normal file
48
pkl-core/src/main/java/org/pkl/core/Value.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Java representation of a Pkl value.
|
||||
*
|
||||
* <p>The following Pkl values aren't represented as {@code Value} but as instances of Java standard
|
||||
* library classes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code pkl.base#String}: {@link java.lang.String}
|
||||
* <li>{@code pkl.base#Boolean}: {@link java.lang.Boolean}
|
||||
* <li>{@code pkl.base#Int}: {@link java.lang.Long}
|
||||
* <li>{@code pkl.base#Float}: {@link java.lang.Double}
|
||||
* <li>{@code pkl.base#List}: {@link java.util.List}
|
||||
* <li>{@code pkl.base#Set}: {@link java.util.Set}
|
||||
* <li>{@code pkl.base#Map}: {@link java.util.Map}
|
||||
* <li>{@code pkl.base#Listing}: {@link java.util.List}
|
||||
* <li>{@code pkl.base#Mapping}: {@link java.util.Map}
|
||||
* <li>{@code pkl.base#Regex}: {@link java.util.regex.Pattern}
|
||||
* </ul>
|
||||
*/
|
||||
public interface Value extends Serializable {
|
||||
/** Invokes the given visitor's visit method for this {@code Value}. */
|
||||
void accept(ValueVisitor visitor);
|
||||
|
||||
/** Invokes the given converters's convert method for this {@code Value}. */
|
||||
<T> T accept(ValueConverter<T> converter);
|
||||
|
||||
/** Returns information about the Pkl class associated with this {@code Value}. */
|
||||
PClassInfo<?> getClassInfo();
|
||||
}
|
||||
81
pkl-core/src/main/java/org/pkl/core/ValueConverter.java
Normal file
81
pkl-core/src/main/java/org/pkl/core/ValueConverter.java
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** Converter for data models generated by [Evaluator]. */
|
||||
@SuppressWarnings("unused")
|
||||
public interface ValueConverter<T> {
|
||||
T convertNull();
|
||||
|
||||
T convertString(String value);
|
||||
|
||||
T convertBoolean(Boolean value);
|
||||
|
||||
T convertInt(Long value);
|
||||
|
||||
T convertFloat(Double value);
|
||||
|
||||
T convertDuration(Duration value);
|
||||
|
||||
T convertDataSize(DataSize value);
|
||||
|
||||
T convertPair(Pair<?, ?> value);
|
||||
|
||||
T convertList(List<?> value);
|
||||
|
||||
T convertSet(Set<?> value);
|
||||
|
||||
T convertMap(Map<?, ?> value);
|
||||
|
||||
T convertObject(PObject value);
|
||||
|
||||
T convertModule(PModule value);
|
||||
|
||||
T convertClass(PClass value);
|
||||
|
||||
T convertTypeAlias(TypeAlias value);
|
||||
|
||||
T convertRegex(Pattern value);
|
||||
|
||||
default T convert(Object value) {
|
||||
if (value instanceof Value) {
|
||||
return ((Value) value).accept(this);
|
||||
} else if (value instanceof String) {
|
||||
return convertString((String) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
return convertBoolean((Boolean) value);
|
||||
} else if (value instanceof Long) {
|
||||
return convertInt((Long) value);
|
||||
} else if (value instanceof Double) {
|
||||
return convertFloat((Double) value);
|
||||
} else if (value instanceof List) {
|
||||
return convertList((List<?>) value);
|
||||
} else if (value instanceof Set) {
|
||||
return convertSet((Set<?>) value);
|
||||
} else if (value instanceof Map) {
|
||||
return convertMap((Map<?, ?>) value);
|
||||
} else if (value instanceof Pattern) {
|
||||
return convertRegex((Pattern) value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot convert value with unexpected type: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
336
pkl-core/src/main/java/org/pkl/core/ValueFormatter.java
Normal file
336
pkl-core/src/main/java/org/pkl/core/ValueFormatter.java
Normal file
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.pkl.core.util.ArrayCharEscaper;
|
||||
|
||||
public final class ValueFormatter {
|
||||
private static final ArrayCharEscaper charEscaper =
|
||||
ArrayCharEscaper.builder()
|
||||
.withEscape('\n', "\\n")
|
||||
.withEscape('\r', "\\r")
|
||||
.withEscape('\t', "\\t")
|
||||
.withEscape('"', "\\\"")
|
||||
.withEscape('\\', "\\\\")
|
||||
.build();
|
||||
|
||||
private static final ValueFormatter BASIC = new ValueFormatter(false, false);
|
||||
|
||||
private static final ValueFormatter WITH_CUSTOM_DELIMITERS = new ValueFormatter(false, true);
|
||||
|
||||
private final boolean useMultilineStrings;
|
||||
private final boolean useCustomStringDelimiters;
|
||||
|
||||
/** Equivalent to {@code new ValueFormatter(false, false)}. */
|
||||
public static ValueFormatter basic() {
|
||||
return BASIC;
|
||||
}
|
||||
|
||||
/** Equivalent to {@code new ValueFormatter(false, true)}. */
|
||||
public static ValueFormatter withCustomStringDelimiters() {
|
||||
return WITH_CUSTOM_DELIMITERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of a {@link ValueFormatter}.
|
||||
*
|
||||
* <p>If {@code useMultilineStrings} is {@code true}, string values containing newline characters
|
||||
* are formatted as multiline string literals.
|
||||
*
|
||||
* <p>If {@code useCustomStringDelimiters} is {@code true}, custom string delimiters (such as
|
||||
* {@code #"..."#}) are preferred over escaping quotes and backslashes.
|
||||
*/
|
||||
public ValueFormatter(boolean useMultilineStrings, boolean useCustomStringDelimiters) {
|
||||
this.useMultilineStrings = useMultilineStrings;
|
||||
this.useCustomStringDelimiters = useCustomStringDelimiters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats {@code value} as a Pkl/Pcf string literal (including quotes).
|
||||
*
|
||||
* <p>If {@code value} contains a {@code \n} character, a multiline string literal is returned,
|
||||
* and subsequent lines are indented by {@code lineIndent}. Otherwise, a single line string
|
||||
* literal is returned.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public String formatStringValue(String value, CharSequence lineIndent) {
|
||||
StringBuilder builder = new StringBuilder(value.length() * 2);
|
||||
formatStringValue(value, lineIndent, builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #formatStringValue(String, CharSequence)}, except that output goes to {@code
|
||||
* builder}.
|
||||
*/
|
||||
public void formatStringValue(String value, CharSequence lineIndent, StringBuilder builder) {
|
||||
try {
|
||||
formatStringValue(value, lineIndent, (Appendable) builder);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #formatStringValue(String, CharSequence)}, except that output goes to {@code
|
||||
* appendable} (which may cause {@link IOException}).
|
||||
*/
|
||||
public void formatStringValue(String value, CharSequence lineIndent, Appendable appendable)
|
||||
throws IOException {
|
||||
// Optimization: if we are rendering single line strings and not rendering custom string
|
||||
// delimiters, there is no need to gather string facts.
|
||||
if (!useMultilineStrings && !useCustomStringDelimiters) {
|
||||
formatSingleLineString(value, appendable, "");
|
||||
return;
|
||||
}
|
||||
var stringFacts = StringFacts.gather(value);
|
||||
var isMultiline = useMultilineStrings && stringFacts.isMultiline;
|
||||
if (isMultiline) {
|
||||
var poundChars =
|
||||
useCustomStringDelimiters ? "#".repeat(stringFacts.poundCharCountMultiline) : "";
|
||||
formatMultilineString(value, lineIndent, appendable, poundChars);
|
||||
} else {
|
||||
var poundChars =
|
||||
useCustomStringDelimiters ? "#".repeat(stringFacts.poundCharCountSingleLine) : "";
|
||||
formatSingleLineString(value, appendable, poundChars);
|
||||
}
|
||||
}
|
||||
|
||||
private void formatSingleLineString(String value, Appendable appendable, String poundChars)
|
||||
throws IOException {
|
||||
appendable.append(poundChars).append('"');
|
||||
|
||||
var i = 0;
|
||||
var escapeSequence = "\\" + poundChars;
|
||||
|
||||
if (useCustomStringDelimiters) {
|
||||
if (value.equals("\"")) {
|
||||
// Edge case 1: If the string consists of a single quote, we must escape it. Otherwise the
|
||||
// output is `#"""#`.
|
||||
appendable.append(escapeSequence).append(value);
|
||||
i = 1;
|
||||
} else if (value.startsWith("\"\"")) {
|
||||
// Edge case 2: If the string starts with two quotes, we must escape the second one.
|
||||
// Otherwise, it will
|
||||
// be interpreted as a multiline string start (e.g. `"""`).
|
||||
appendable.append('"').append(escapeSequence).append('"');
|
||||
i = 2;
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < value.length(); i++) {
|
||||
var ch = value.charAt(i);
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
appendable.append(escapeSequence).append('n');
|
||||
break;
|
||||
case '\r':
|
||||
appendable.append(escapeSequence).append('r');
|
||||
break;
|
||||
case '\t':
|
||||
appendable.append(escapeSequence).append('t');
|
||||
break;
|
||||
case '\\':
|
||||
if (useCustomStringDelimiters) {
|
||||
appendable.append(ch);
|
||||
} else {
|
||||
appendable.append("\\\\");
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
if (useCustomStringDelimiters) {
|
||||
appendable.append(ch);
|
||||
} else {
|
||||
appendable.append("\\\"");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
appendable.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
appendable.append('"').append(poundChars);
|
||||
}
|
||||
|
||||
private void formatMultilineString(
|
||||
String value, CharSequence lineIndent, Appendable appendable, String poundChars)
|
||||
throws IOException {
|
||||
var consecutiveQuotes = 0;
|
||||
var escapeSequence = "\\" + poundChars;
|
||||
|
||||
appendable.append(poundChars).append("\"\"\"\n").append(lineIndent);
|
||||
|
||||
for (var i = 0; i < value.length(); i++) {
|
||||
var ch = value.charAt(i);
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
appendable.append('\n').append(lineIndent);
|
||||
consecutiveQuotes = 0;
|
||||
break;
|
||||
case '\r':
|
||||
appendable.append(escapeSequence).append('r');
|
||||
consecutiveQuotes = 0;
|
||||
break;
|
||||
case '\t':
|
||||
appendable.append(escapeSequence).append('t');
|
||||
consecutiveQuotes = 0;
|
||||
break;
|
||||
case '\\':
|
||||
if (useCustomStringDelimiters) {
|
||||
appendable.append(ch);
|
||||
} else {
|
||||
appendable.append("\\\\");
|
||||
}
|
||||
consecutiveQuotes = 0;
|
||||
break;
|
||||
case '"':
|
||||
if (consecutiveQuotes == 2 && !useCustomStringDelimiters) {
|
||||
appendable.append("\\\"");
|
||||
consecutiveQuotes = 0;
|
||||
} else {
|
||||
appendable.append('"');
|
||||
consecutiveQuotes += 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
appendable.append(ch);
|
||||
consecutiveQuotes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
appendable.append("\n").append(lineIndent).append("\"\"\"").append(poundChars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores basic facts about a string. This is used to assist with pretty-formatting Pcf strings;
|
||||
* e.g. determining whether to render as a multiline string, and whether to wrap the string with
|
||||
* {@code #} delimiters.
|
||||
*/
|
||||
private static final class StringFacts {
|
||||
private final boolean isMultiline;
|
||||
|
||||
/** The number of pound characters that should wrap a string if rendering as single line. */
|
||||
private final int poundCharCountSingleLine;
|
||||
|
||||
/** The number of pound characters that should wrap a string if rendering as multiline. */
|
||||
private final int poundCharCountMultiline;
|
||||
|
||||
private StringFacts(
|
||||
boolean isMultiline, int poundCharCountSingleLine, int poundCharCountMultiline) {
|
||||
this.isMultiline = isMultiline;
|
||||
this.poundCharCountSingleLine = poundCharCountSingleLine;
|
||||
this.poundCharCountMultiline = poundCharCountMultiline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers the following pieces of information about a string:
|
||||
*
|
||||
* <ul>
|
||||
* <li>What is the maximum number of consecutive pound characters that follow a multiline
|
||||
* quote ({@code """})?
|
||||
* <li>What is the maximum number of consecutive pound characters that follow a single line
|
||||
* quote ({@code "})?
|
||||
* <li>Are there newline characters in the string?
|
||||
* </ul>
|
||||
*
|
||||
* This is used to assist with rendering custom delimited strings (e.g. {@code #"..."#}).
|
||||
*
|
||||
* <p>Algorithm:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Determine the current token context (backlash, single line quote, multiline quote,
|
||||
* other).
|
||||
* <li>If there is a current token context, count the number of pound characters succeeding
|
||||
* that token.
|
||||
* <li>Keep track of the maximum number of pound characters for each token type.
|
||||
* </ol>
|
||||
*/
|
||||
static StringFacts gather(final String value) {
|
||||
var isMultiline = false;
|
||||
var consecutiveQuoteCount = 0;
|
||||
var currentPoundContext = PoundContext.OTHER;
|
||||
var currentPoundCountSingleQuote = 0;
|
||||
var currentPoundCountMultilineQuote = 0;
|
||||
var currentPoundCountBackslash = 0;
|
||||
var poundCountSingleQuote = 0;
|
||||
var poundCountMultilineQuote = 0;
|
||||
var poundCountBackslash = 0;
|
||||
for (var i = 0; i < value.length(); i++) {
|
||||
var ch = value.charAt(i);
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
currentPoundContext = PoundContext.BACKSLASH;
|
||||
currentPoundCountBackslash = 1;
|
||||
poundCountBackslash = Math.max(poundCountBackslash, currentPoundCountBackslash);
|
||||
break;
|
||||
case '"':
|
||||
consecutiveQuoteCount += 1;
|
||||
if (consecutiveQuoteCount < 3) {
|
||||
currentPoundContext = PoundContext.SINGLELINE_QUOTE;
|
||||
currentPoundCountSingleQuote = 1;
|
||||
poundCountSingleQuote = Math.max(poundCountSingleQuote, currentPoundCountSingleQuote);
|
||||
} else {
|
||||
currentPoundContext = PoundContext.MULTILINE_QUOTE;
|
||||
currentPoundCountMultilineQuote = 1;
|
||||
poundCountMultilineQuote =
|
||||
Math.max(poundCountMultilineQuote, currentPoundCountMultilineQuote);
|
||||
}
|
||||
break;
|
||||
case '#':
|
||||
consecutiveQuoteCount = 0;
|
||||
switch (currentPoundContext) {
|
||||
case SINGLELINE_QUOTE:
|
||||
currentPoundCountSingleQuote += 1;
|
||||
poundCountSingleQuote =
|
||||
Math.max(poundCountSingleQuote, currentPoundCountSingleQuote);
|
||||
break;
|
||||
case MULTILINE_QUOTE:
|
||||
currentPoundCountMultilineQuote += 1;
|
||||
poundCountMultilineQuote =
|
||||
Math.max(poundCountMultilineQuote, currentPoundCountMultilineQuote);
|
||||
break;
|
||||
case BACKSLASH:
|
||||
currentPoundCountBackslash += 1;
|
||||
poundCountBackslash = Math.max(poundCountBackslash, currentPoundCountBackslash);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case '\n':
|
||||
isMultiline = true;
|
||||
default:
|
||||
consecutiveQuoteCount = 0;
|
||||
currentPoundContext = PoundContext.OTHER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new StringFacts(
|
||||
isMultiline,
|
||||
Math.max(poundCountBackslash, poundCountSingleQuote),
|
||||
Math.max(poundCountBackslash, poundCountMultilineQuote));
|
||||
}
|
||||
|
||||
/** Represents the context in which the pound character ({@code #}) succeeds. */
|
||||
private enum PoundContext {
|
||||
OTHER,
|
||||
SINGLELINE_QUOTE,
|
||||
MULTILINE_QUOTE,
|
||||
BACKSLASH,
|
||||
}
|
||||
}
|
||||
}
|
||||
32
pkl-core/src/main/java/org/pkl/core/ValueRenderer.java
Normal file
32
pkl-core/src/main/java/org/pkl/core/ValueRenderer.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
/** Renders Pkl values in some output format. */
|
||||
public interface ValueRenderer {
|
||||
/**
|
||||
* Renders the given value as a complete document.
|
||||
*
|
||||
* <p>Some renderers impose restrictions on which types of values can be rendered as document.
|
||||
*
|
||||
* <p>A typical implementation of this method renders a document header/footer and otherwise
|
||||
* delegates to {@link #renderValue}.
|
||||
*/
|
||||
void renderDocument(Object value);
|
||||
|
||||
/** Renders the given value. */
|
||||
void renderValue(Object value);
|
||||
}
|
||||
70
pkl-core/src/main/java/org/pkl/core/ValueRenderers.java
Normal file
70
pkl-core/src/main/java/org/pkl/core/ValueRenderers.java
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.Writer;
|
||||
|
||||
/** Predefined {@link ValueRenderer}s for Pcf, JSON, YAML, and XML property lists. */
|
||||
public final class ValueRenderers {
|
||||
private ValueRenderers() {}
|
||||
|
||||
/**
|
||||
* Creates a renderer for Pcf, a static subset of Pkl. If {@code omitNullProperties} is {@code
|
||||
* true}, object properties whose value is {@code null} will not be rendered. If {@code
|
||||
* useCustomDelimiters} is {@code true}, custom string delimiters (such as {@code #"..."#}) are
|
||||
* preferred over escaping quotes and backslashes.
|
||||
*/
|
||||
public static ValueRenderer pcf(
|
||||
Writer writer, String indent, boolean omitNullProperties, boolean useCustomStringDelimiters) {
|
||||
return new PcfRenderer(writer, indent, omitNullProperties, useCustomStringDelimiters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a renderer for JSON. If {@code omitNullProperties} is {@code true}, object properties
|
||||
* whose value is {@code null} will not be rendered.
|
||||
*/
|
||||
public static ValueRenderer json(Writer writer, String indent, boolean omitNullProperties) {
|
||||
return new JsonRenderer(writer, indent, omitNullProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a renderer for YAML. If {@code omitNullProperties} is {@code true}, object properties
|
||||
* whose value is {@code null} will not be rendered. If {@code isStream} is {@code true}, {@link
|
||||
* ValueRenderer#renderDocument} expects an argument of type {@link Iterable} and renders it as
|
||||
* YAML stream.
|
||||
*/
|
||||
public static ValueRenderer yaml(
|
||||
Writer writer, int indent, boolean omitNullProperties, boolean isStream) {
|
||||
return new YamlRenderer(writer, indent, omitNullProperties, isStream);
|
||||
}
|
||||
|
||||
/** Creates a renderer for XML property lists. */
|
||||
public static ValueRenderer plist(Writer writer, String indent) {
|
||||
return new PListRenderer(writer, indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a renderer for {@link java.util.Properties} file format. If {@code omitNullProperties}
|
||||
* is {@code true}, object properties and map entries whose value is {@code null} will not be
|
||||
* rendered. If {@code restrictCharset} is {@code true} characters outside the printable US-ASCII
|
||||
* charset range will be rendererd as Unicode escapes (see
|
||||
* https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.3).
|
||||
*/
|
||||
public static ValueRenderer properties(
|
||||
Writer writer, boolean omitNullProperties, boolean restrictCharset) {
|
||||
return new PropertiesRenderer(writer, omitNullProperties, restrictCharset);
|
||||
}
|
||||
}
|
||||
115
pkl-core/src/main/java/org/pkl/core/ValueVisitor.java
Normal file
115
pkl-core/src/main/java/org/pkl/core/ValueVisitor.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Visitor for data models generated by [Evaluator]. */
|
||||
public interface ValueVisitor {
|
||||
default void visitDefault(@Nullable Object value) {}
|
||||
|
||||
default void visitNull() {
|
||||
visitDefault(null);
|
||||
}
|
||||
|
||||
default void visitString(String value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitBoolean(Boolean value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitInt(Long value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitFloat(Double value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitDuration(Duration value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitDataSize(DataSize value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitPair(Pair<?, ?> value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitList(List<?> value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitSet(Set<?> value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitMap(Map<?, ?> value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitObject(PObject value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitModule(PModule value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitClass(PClass value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitTypeAlias(TypeAlias value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visitRegex(Pattern value) {
|
||||
visitDefault(value);
|
||||
}
|
||||
|
||||
default void visit(Object value) {
|
||||
if (value instanceof Value) {
|
||||
((Value) value).accept(this);
|
||||
} else if (value instanceof String) {
|
||||
visitString((String) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
visitBoolean((Boolean) value);
|
||||
} else if (value instanceof Long) {
|
||||
visitInt((Long) value);
|
||||
} else if (value instanceof Double) {
|
||||
visitFloat((Double) value);
|
||||
} else if (value instanceof List) {
|
||||
visitList((List<?>) value);
|
||||
} else if (value instanceof Set) {
|
||||
visitSet((Set<?>) value);
|
||||
} else if (value instanceof Map) {
|
||||
visitMap((Map<?, ?>) value);
|
||||
} else if (value instanceof Pattern) {
|
||||
visitRegex((Pattern) value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot visit value with unexpected type: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
255
pkl-core/src/main/java/org/pkl/core/Version.java
Normal file
255
pkl-core/src/main/java/org/pkl/core/Version.java
Normal file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.*;
|
||||
import org.pkl.core.util.LateInit;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/**
|
||||
* A semantic version (https://semver.org/spec/v2.0.0.html).
|
||||
*
|
||||
* <p>This class guarantees that valid semantic version numbers are handled correctly, but does
|
||||
* <em>not</em> guarantee that invalid semantic version numbers are rejected.
|
||||
*/
|
||||
// copied by `org.pkl.executor.Version` to avoid dependency on pkl-core
|
||||
@SuppressWarnings("Duplicates")
|
||||
public final class Version implements Comparable<Version> {
|
||||
// https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions
|
||||
private static final Pattern VERSION =
|
||||
Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(?:-([^+]+))?(?:\\+(.+))?");
|
||||
|
||||
private static final Pattern NUMERIC_IDENTIFIER = Pattern.compile("(0|[1-9]\\d*)");
|
||||
|
||||
private static final Comparator<Version> COMPARATOR =
|
||||
Comparator.comparingInt(Version::getMajor)
|
||||
.thenComparingInt(Version::getMinor)
|
||||
.thenComparingInt(Version::getPatch)
|
||||
.thenComparing(
|
||||
(v1, v2) -> {
|
||||
if (v1.preRelease == null) return v2.preRelease == null ? 0 : 1;
|
||||
if (v2.preRelease == null) return -1;
|
||||
var ids1 = v1.getPreReleaseIdentifiers();
|
||||
var ids2 = v2.getPreReleaseIdentifiers();
|
||||
var minSize = Math.min(ids1.length, ids2.length);
|
||||
for (var i = 0; i < minSize; i++) {
|
||||
var result = ids1[i].compareTo(ids2[i]);
|
||||
if (result != 0) return result;
|
||||
}
|
||||
return Integer.compare(ids1.length, ids2.length);
|
||||
});
|
||||
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int patch;
|
||||
private final @Nullable String preRelease;
|
||||
private final @Nullable String build;
|
||||
|
||||
@LateInit private volatile Identifier[] __preReleaseIdentifiers;
|
||||
|
||||
/** Constructs a semantic version. */
|
||||
public Version(
|
||||
int major, int minor, int patch, @Nullable String preRelease, @Nullable String build) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
this.preRelease = preRelease;
|
||||
this.build = build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given string as a semantic version number.
|
||||
*
|
||||
* <p>Throws {@link IllegalArgumentException} if the given string could not be parsed as a
|
||||
* semantic version number or is too large to fit into a {@link Version}.
|
||||
*/
|
||||
public static Version parse(String version) {
|
||||
var result = parseOrNull(version);
|
||||
if (result != null) return result;
|
||||
|
||||
if (VERSION.matcher(version).matches()) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("`%s` is too large to fit into a Version.", version));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("`%s` could not be parsed as a semantic version number.", version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given string as a semantic version number.
|
||||
*
|
||||
* <p>Returns {@code null} if the given string could not be parsed as a semantic version number or
|
||||
* is too large to fit into a {@link Version}.
|
||||
*/
|
||||
public static @Nullable Version parseOrNull(String version) {
|
||||
var matcher = VERSION.matcher(version);
|
||||
if (!matcher.matches()) return null;
|
||||
|
||||
try {
|
||||
return new Version(
|
||||
Integer.parseInt(matcher.group(1)),
|
||||
Integer.parseInt(matcher.group(2)),
|
||||
Integer.parseInt(matcher.group(3)),
|
||||
matcher.group(4),
|
||||
matcher.group(5));
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a comparator for semantic versions. */
|
||||
public static Comparator<Version> comparator() {
|
||||
return COMPARATOR;
|
||||
}
|
||||
|
||||
/** Returns the major version. */
|
||||
public int getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
/** Returns a copy of this version with the given major version. */
|
||||
public Version withMajor(int major) {
|
||||
return new Version(major, minor, patch, preRelease, build);
|
||||
}
|
||||
|
||||
/** Returns the minor version. */
|
||||
public int getMinor() {
|
||||
return minor;
|
||||
}
|
||||
|
||||
/** Returns a copy of this version with the given minor version. */
|
||||
public Version withMinor(int minor) {
|
||||
return new Version(major, minor, patch, preRelease, build);
|
||||
}
|
||||
|
||||
/** Returns the patch version. */
|
||||
public int getPatch() {
|
||||
return patch;
|
||||
}
|
||||
|
||||
/** Returns a copy of this version with the given patch version. */
|
||||
public Version withPatch(int patch) {
|
||||
return new Version(major, minor, patch, preRelease, build);
|
||||
}
|
||||
|
||||
/** Returns the pre-release version (if any). */
|
||||
public @Nullable String getPreRelease() {
|
||||
return preRelease;
|
||||
}
|
||||
|
||||
/** Returns a copy of this version with the given pre-release version. */
|
||||
public Version withPreRelease(@Nullable String preRelease) {
|
||||
return new Version(major, minor, patch, preRelease, build);
|
||||
}
|
||||
|
||||
/** Returns the build metadata (if any). */
|
||||
public @Nullable String getBuild() {
|
||||
return build;
|
||||
}
|
||||
|
||||
/** Returns a copy of this version with the given build metadata. */
|
||||
public Version withBuild(@Nullable String build) {
|
||||
return new Version(major, minor, patch, preRelease, build);
|
||||
}
|
||||
|
||||
/** Tells if this version has no pre-release version or build metadata. */
|
||||
public boolean isNormal() {
|
||||
return preRelease == null && build == null;
|
||||
}
|
||||
|
||||
/** Tells if this version has a non-zero major version and no pre-release version. */
|
||||
public boolean isStable() {
|
||||
return major != 0 && preRelease == null;
|
||||
}
|
||||
|
||||
/** Strips any pre-release version and build metadata from this version. */
|
||||
public Version toNormal() {
|
||||
return preRelease == null && build == null
|
||||
? this
|
||||
: new Version(major, minor, patch, null, null);
|
||||
}
|
||||
|
||||
/** Compares this version to the given version according to semantic versioning rules. */
|
||||
@Override
|
||||
public int compareTo(Version other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
/** Tells if this version is equal to {@code obj} according to semantic versioning rules. */
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Version)) return false;
|
||||
|
||||
var other = (Version) obj;
|
||||
return major == other.major
|
||||
&& minor == other.minor
|
||||
&& patch == other.patch
|
||||
&& Objects.equals(preRelease, other.preRelease);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(major, minor, patch, preRelease);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ""
|
||||
+ major
|
||||
+ "."
|
||||
+ minor
|
||||
+ "."
|
||||
+ patch
|
||||
+ (preRelease != null ? "-" + preRelease : "")
|
||||
+ (build != null ? "+" + build : "");
|
||||
}
|
||||
|
||||
private Identifier[] getPreReleaseIdentifiers() {
|
||||
if (__preReleaseIdentifiers == null) {
|
||||
__preReleaseIdentifiers =
|
||||
preRelease == null
|
||||
? new Identifier[0]
|
||||
: Arrays.stream(preRelease.split("\\."))
|
||||
.map(
|
||||
str ->
|
||||
NUMERIC_IDENTIFIER.matcher(str).matches()
|
||||
? new Identifier(Long.parseLong(str), null)
|
||||
: new Identifier(-1, str))
|
||||
.toArray(Identifier[]::new);
|
||||
}
|
||||
return __preReleaseIdentifiers;
|
||||
}
|
||||
|
||||
private static final class Identifier implements Comparable<Identifier> {
|
||||
private final long numericId;
|
||||
private final @Nullable String alphanumericId;
|
||||
|
||||
Identifier(long numericId, @Nullable String alphanumericId) {
|
||||
this.numericId = numericId;
|
||||
this.alphanumericId = alphanumericId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Identifier other) {
|
||||
return alphanumericId != null
|
||||
? other.alphanumericId != null ? alphanumericId.compareTo(other.alphanumericId) : 1
|
||||
: other.alphanumericId != null ? -1 : Long.compare(numericId, other.numericId);
|
||||
}
|
||||
}
|
||||
}
|
||||
236
pkl-core/src/main/java/org/pkl/core/YamlRenderer.java
Normal file
236
pkl-core/src/main/java/org/pkl/core/YamlRenderer.java
Normal file
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.util.Nullable;
|
||||
import org.pkl.core.util.yaml.snake.YamlUtils;
|
||||
import org.snakeyaml.engine.v2.api.DumpSettings;
|
||||
import org.snakeyaml.engine.v2.api.StreamDataWriter;
|
||||
import org.snakeyaml.engine.v2.common.FlowStyle;
|
||||
import org.snakeyaml.engine.v2.emitter.Emitter;
|
||||
import org.snakeyaml.engine.v2.events.*;
|
||||
import org.snakeyaml.engine.v2.nodes.Tag;
|
||||
import org.snakeyaml.engine.v2.resolver.ScalarResolver;
|
||||
|
||||
final class YamlRenderer implements ValueRenderer {
|
||||
private final ScalarResolver resolver = YamlUtils.getEmitterResolver("compat");
|
||||
private final Visitor visitor = new Visitor();
|
||||
private final Emitter emitter;
|
||||
private final boolean omitNullProperties;
|
||||
private final boolean isStream;
|
||||
|
||||
public YamlRenderer(Writer writer, int indent, boolean omitNullProperties, boolean isStream) {
|
||||
var dumpSettings =
|
||||
DumpSettings.builder()
|
||||
.setIndent(indent)
|
||||
.setBestLineBreak("\n")
|
||||
.setScalarResolver(resolver)
|
||||
.build();
|
||||
|
||||
emitter =
|
||||
new Emitter(
|
||||
dumpSettings,
|
||||
new StreamDataWriter() {
|
||||
@Override
|
||||
public void write(String str) {
|
||||
try {
|
||||
writer.write(str);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String str, int off, int len) {
|
||||
try {
|
||||
writer.write(str, off, len);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.omitNullProperties = omitNullProperties;
|
||||
this.isStream = isStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderDocument(Object value) {
|
||||
if (isStream) {
|
||||
if (!(value instanceof Iterable)) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"The top-level value of a YAML stream must have type `Collection`, but got type `%s`.",
|
||||
value.getClass().getTypeName()));
|
||||
}
|
||||
var iterable = (Iterable<?>) value;
|
||||
emitter.emit(new StreamStartEvent());
|
||||
for (var elem : iterable) {
|
||||
emitter.emit(new DocumentStartEvent(false, Optional.empty(), Map.of()));
|
||||
visitor.visit(elem);
|
||||
emitter.emit(new DocumentEndEvent(false));
|
||||
}
|
||||
emitter.emit(new StreamEndEvent());
|
||||
} else {
|
||||
// a top-level YAML value can have any type
|
||||
renderValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderValue(Object value) {
|
||||
emitter.emit(new StreamStartEvent());
|
||||
emitter.emit(new DocumentStartEvent(false, Optional.empty(), Map.of()));
|
||||
|
||||
visitor.visit(value);
|
||||
|
||||
emitter.emit(new DocumentEndEvent(false));
|
||||
emitter.emit(new StreamEndEvent());
|
||||
}
|
||||
|
||||
protected class Visitor implements ValueVisitor {
|
||||
@Override
|
||||
public void visitString(String value) {
|
||||
emitter.emit(YamlUtils.stringScalar(value, resolver));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInt(Long value) {
|
||||
emitter.emit(YamlUtils.plainScalar(value.toString(), Tag.INT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFloat(Double value) {
|
||||
emitter.emit(YamlUtils.plainScalar(value.toString(), Tag.FLOAT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitBoolean(Boolean value) {
|
||||
emitter.emit(YamlUtils.plainScalar(value.toString(), Tag.BOOL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDuration(Duration value) {
|
||||
throw new RendererException(
|
||||
String.format("Values of type `Duration` cannot be rendered as YAML. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDataSize(DataSize value) {
|
||||
throw new RendererException(
|
||||
String.format("Values of type `DataSize` cannot be rendered as YAML. Value: %s", value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPair(Pair<?, ?> value) {
|
||||
doVisitIterable(value, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitList(List<?> value) {
|
||||
doVisitIterable(value, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSet(Set<?> value) {
|
||||
doVisitIterable(value, "!!set");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMap(Map<?, ?> value) {
|
||||
for (var key : value.keySet()) {
|
||||
if (!(key instanceof String)) {
|
||||
// http://stackoverflow.com/questions/33987316/what-is-a-complex-mapping-key-in-yaml
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Maps with non-String keys cannot currently be rendered as YAML. Key: %s", key));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var mapValue = (Map<String, ?>) value;
|
||||
doVisitProperties(mapValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitObject(PObject value) {
|
||||
doVisitProperties(value.getProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitModule(PModule value) {
|
||||
doVisitProperties(value.getProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClass(PClass value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `Class` cannot be rendered as YAML. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTypeAlias(TypeAlias value) {
|
||||
throw new RendererException(
|
||||
String.format(
|
||||
"Values of type `TypeAlias` cannot be rendered as YAML. Value: %s",
|
||||
value.getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNull() {
|
||||
emitter.emit(YamlUtils.plainScalar("null", Tag.NULL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegex(Pattern value) {
|
||||
throw new RendererException(
|
||||
String.format("Values of type `Regex` cannot be rendered as YAML. Value: %s", value));
|
||||
}
|
||||
|
||||
private void doVisitIterable(Iterable<?> iterable, @Nullable String tag) {
|
||||
emitter.emit(
|
||||
new SequenceStartEvent(
|
||||
Optional.empty(), Optional.ofNullable(tag), true, FlowStyle.BLOCK));
|
||||
for (var elem : iterable) visit(elem);
|
||||
emitter.emit(new SequenceEndEvent(Optional.empty(), Optional.empty()));
|
||||
}
|
||||
|
||||
private void doVisitProperties(Map<String, ?> properties) {
|
||||
emitter.emit(
|
||||
new MappingStartEvent(Optional.empty(), Optional.empty(), true, FlowStyle.BLOCK));
|
||||
|
||||
for (var entry : properties.entrySet()) {
|
||||
var value = entry.getValue();
|
||||
|
||||
if (omitNullProperties && value instanceof PNull) {
|
||||
continue;
|
||||
}
|
||||
|
||||
emitter.emit(YamlUtils.stringScalar(entry.getKey(), resolver));
|
||||
visit(value);
|
||||
}
|
||||
|
||||
emitter.emit(new MappingEndEvent(Optional.empty(), Optional.empty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
20
pkl-core/src/main/java/org/pkl/core/ast/ConstantNode.java
Normal file
20
pkl-core/src/main/java/org/pkl/core/ast/ConstantNode.java
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
public interface ConstantNode {
|
||||
Object getValue();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
|
||||
@NodeInfo(shortName = "const")
|
||||
public final class ConstantValueNode extends ExpressionNode implements ConstantNode {
|
||||
private final Object value;
|
||||
|
||||
public ConstantValueNode(SourceSection sourceSection, Object value) {
|
||||
super(sourceSection);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ConstantValueNode(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
46
pkl-core/src/main/java/org/pkl/core/ast/ExpressionNode.java
Normal file
46
pkl-core/src/main/java/org/pkl/core/ast/ExpressionNode.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.UnexpectedResultException;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmTypesGen;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
|
||||
public abstract class ExpressionNode extends PklNode {
|
||||
protected ExpressionNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
protected ExpressionNode() {
|
||||
this(VmUtils.unavailableSourceSection());
|
||||
}
|
||||
|
||||
public abstract Object executeGeneric(VirtualFrame frame);
|
||||
|
||||
public long executeInt(VirtualFrame frame) throws UnexpectedResultException {
|
||||
return VmTypesGen.expectLong(executeGeneric(frame));
|
||||
}
|
||||
|
||||
public double executeFloat(VirtualFrame frame) throws UnexpectedResultException {
|
||||
return VmTypesGen.expectDouble(executeGeneric(frame));
|
||||
}
|
||||
|
||||
public boolean executeBoolean(VirtualFrame frame) throws UnexpectedResultException {
|
||||
return VmTypesGen.expectBoolean(executeGeneric(frame));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
public enum MemberLookupMode {
|
||||
/** Lookup of a local member in the lexical scope. */
|
||||
IMPLICIT_LOCAL,
|
||||
|
||||
/** Lookup of a non-local member in the lexical scope. */
|
||||
IMPLICIT_LEXICAL,
|
||||
|
||||
/** Member lookup whose implicit receiver is the {@code pkl.base} module. */
|
||||
IMPLICIT_BASE,
|
||||
|
||||
/** Member lookup whose implicit receiver is {@code this}. */
|
||||
IMPLICIT_THIS,
|
||||
|
||||
/** Member lookup with explicit receiver (e.g., {@code foo.bar}). */
|
||||
EXPLICIT_RECEIVER
|
||||
}
|
||||
95
pkl-core/src/main/java/org/pkl/core/ast/MemberNode.java
Normal file
95
pkl-core/src/main/java/org/pkl/core/ast/MemberNode.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.util.function.Function;
|
||||
import org.pkl.core.ast.member.DefaultPropertyBodyNode;
|
||||
import org.pkl.core.ast.member.Member;
|
||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||
import org.pkl.core.runtime.VmLanguage;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public abstract class MemberNode extends PklRootNode {
|
||||
protected final Member member;
|
||||
@Child protected ExpressionNode bodyNode;
|
||||
|
||||
protected MemberNode(
|
||||
@Nullable VmLanguage language,
|
||||
FrameDescriptor descriptor,
|
||||
Member member,
|
||||
ExpressionNode bodyNode) {
|
||||
|
||||
super(language, descriptor);
|
||||
this.member = member;
|
||||
this.bodyNode = bodyNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SourceSection getSourceSection() {
|
||||
return member.getSourceSection();
|
||||
}
|
||||
|
||||
public final SourceSection getHeaderSection() {
|
||||
return member.getHeaderSection();
|
||||
}
|
||||
|
||||
public final SourceSection getBodySection() {
|
||||
return bodyNode.getSourceSection();
|
||||
}
|
||||
|
||||
public final ExpressionNode getBodyNode() {
|
||||
return bodyNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getName() {
|
||||
return member.getQualifiedName();
|
||||
}
|
||||
|
||||
public final void replaceBody(Function<ExpressionNode, ExpressionNode> replacer) {
|
||||
bodyNode = insert(replacer.apply(bodyNode));
|
||||
}
|
||||
|
||||
protected final Object executeBody(VirtualFrame frame) {
|
||||
return executeBody(frame, bodyNode);
|
||||
}
|
||||
|
||||
protected final VmExceptionBuilder exceptionBuilder() {
|
||||
return new VmExceptionBuilder().withSourceSection(member.getHeaderSection());
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, the property value computed by this node is not the final value exposed to user code
|
||||
* but will still be amended.
|
||||
*
|
||||
* <p>Used to disable type check for to-be-amended properties. See {@link
|
||||
* org.pkl.core.runtime.VmUtils#SKIP_TYPECHECK_MARKER}. IDEA: might be more appropriate to only
|
||||
* skip constraints check
|
||||
*/
|
||||
protected final boolean shouldRunTypecheck(VirtualFrame frame) {
|
||||
return frame.getArguments().length == 4
|
||||
&& frame.getArguments()[3] == VmUtils.SKIP_TYPECHECK_MARKER;
|
||||
}
|
||||
|
||||
public boolean isUndefined() {
|
||||
return bodyNode instanceof DefaultPropertyBodyNode
|
||||
&& ((DefaultPropertyBodyNode) bodyNode).isUndefined();
|
||||
}
|
||||
}
|
||||
64
pkl-core/src/main/java/org/pkl/core/ast/PklNode.java
Normal file
64
pkl-core/src/main/java/org/pkl/core/ast/PklNode.java
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.TypeSystemReference;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||
import org.pkl.core.runtime.VmTypes;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
|
||||
@NodeInfo(language = "Pkl")
|
||||
@TypeSystemReference(VmTypes.class)
|
||||
public abstract class PklNode extends Node {
|
||||
protected final SourceSection sourceSection;
|
||||
|
||||
protected PklNode(SourceSection sourceSection) {
|
||||
this.sourceSection = sourceSection;
|
||||
}
|
||||
|
||||
protected PklNode() {
|
||||
this(VmUtils.unavailableSourceSection());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceSection getSourceSection() {
|
||||
return sourceSection;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
protected VmExceptionBuilder exceptionBuilder() {
|
||||
return new VmExceptionBuilder().withLocation(this);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
protected final String getShortName() {
|
||||
return VmUtils.getNodeInfo(this).shortName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"(%s:%d) %s",
|
||||
sourceSection.getSource().getName(),
|
||||
sourceSection.getStartLine(),
|
||||
sourceSection.getCharacters());
|
||||
}
|
||||
}
|
||||
57
pkl-core/src/main/java/org/pkl/core/ast/PklRootNode.java
Normal file
57
pkl-core/src/main/java/org/pkl/core/ast/PklRootNode.java
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.TypeSystemReference;
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.nodes.RootNode;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@NodeInfo(language = "Pkl")
|
||||
@TypeSystemReference(VmTypes.class)
|
||||
public abstract class PklRootNode extends RootNode {
|
||||
protected PklRootNode(@Nullable VmLanguage language, FrameDescriptor descriptor) {
|
||||
super(language, descriptor);
|
||||
}
|
||||
|
||||
public abstract SourceSection getSourceSection();
|
||||
|
||||
public abstract @Nullable String getName();
|
||||
|
||||
protected final Object executeBody(VirtualFrame frame, ExpressionNode bodyNode) {
|
||||
try {
|
||||
return bodyNode.executeGeneric(frame);
|
||||
} catch (VmException e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw e;
|
||||
} catch (StackOverflowError e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new VmStackOverflowException(e);
|
||||
} catch (Exception e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().bug(e.getMessage()).withCause(e).build();
|
||||
}
|
||||
}
|
||||
|
||||
protected VmExceptionBuilder exceptionBuilder() {
|
||||
return new VmExceptionBuilder().withLocation(this);
|
||||
}
|
||||
}
|
||||
55
pkl-core/src/main/java/org/pkl/core/ast/SimpleRootNode.java
Normal file
55
pkl-core/src/main/java/org/pkl/core/ast/SimpleRootNode.java
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmLanguage;
|
||||
|
||||
public final class SimpleRootNode extends PklRootNode {
|
||||
private final SourceSection sourceSection;
|
||||
private final String qualifiedName;
|
||||
@Child private ExpressionNode bodyNode;
|
||||
|
||||
public SimpleRootNode(
|
||||
VmLanguage language,
|
||||
FrameDescriptor descriptor,
|
||||
SourceSection sourceSection,
|
||||
String qualifiedName,
|
||||
ExpressionNode bodyNode) {
|
||||
|
||||
super(language, descriptor);
|
||||
this.sourceSection = sourceSection;
|
||||
this.qualifiedName = qualifiedName;
|
||||
this.bodyNode = bodyNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceSection getSourceSection() {
|
||||
return sourceSection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return qualifiedName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(VirtualFrame frame) {
|
||||
return executeBody(frame, bodyNode);
|
||||
}
|
||||
}
|
||||
193
pkl-core/src/main/java/org/pkl/core/ast/VmModifier.java
Normal file
193
pkl-core/src/main/java/org/pkl/core/ast/VmModifier.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import org.pkl.core.Modifier;
|
||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||
import org.pkl.core.runtime.VmSet;
|
||||
|
||||
public final class VmModifier {
|
||||
// user-facing modifiers
|
||||
|
||||
public static final int ABSTRACT = 0x1;
|
||||
|
||||
public static final int OPEN = 0x2;
|
||||
|
||||
public static final int LOCAL = 0x4;
|
||||
|
||||
// absent from rendered output but present in module schema (e.g. for pkldoc purposes)
|
||||
public static final int HIDDEN = 0x8;
|
||||
|
||||
public static final int EXTERNAL = 0x10;
|
||||
|
||||
public static final int FIXED = 0x20;
|
||||
|
||||
public static final int CONST = 0x40;
|
||||
|
||||
// internal modifiers
|
||||
|
||||
public static final int IMPORT = 0x80;
|
||||
|
||||
public static final int CLASS = 0x100;
|
||||
|
||||
public static final int TYPE_ALIAS = 0x200;
|
||||
|
||||
public static final int ENTRY = 0x400;
|
||||
|
||||
public static final int ELEMENT = 0x800;
|
||||
|
||||
public static final int GLOB = 0x1000;
|
||||
|
||||
// modifier sets
|
||||
|
||||
public static final int NONE = 0;
|
||||
|
||||
public static final int VALID_MODULE_MODIFIERS = ABSTRACT | OPEN;
|
||||
|
||||
public static final int VALID_AMENDING_MODULE_MODIFIERS = 0;
|
||||
|
||||
public static final int VALID_CLASS_MODIFIERS = ABSTRACT | OPEN | LOCAL | EXTERNAL;
|
||||
|
||||
public static final int VALID_TYPE_ALIAS_MODIFIERS = LOCAL | EXTERNAL;
|
||||
|
||||
public static final int VALID_METHOD_MODIFIERS = ABSTRACT | LOCAL | EXTERNAL | CONST;
|
||||
|
||||
public static final int VALID_PROPERTY_MODIFIERS =
|
||||
ABSTRACT | LOCAL | HIDDEN | EXTERNAL | FIXED | CONST;
|
||||
|
||||
public static final int VALID_OBJECT_MEMBER_MODIFIERS = LOCAL;
|
||||
|
||||
public static boolean isLocal(int modifiers) {
|
||||
return (modifiers & LOCAL) != 0;
|
||||
}
|
||||
|
||||
public static boolean isAbstract(int modifiers) {
|
||||
return (modifiers & ABSTRACT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isFixed(int modifiers) {
|
||||
return (modifiers & FIXED) != 0;
|
||||
}
|
||||
|
||||
public static boolean isOpen(int modifiers) {
|
||||
return (modifiers & OPEN) != 0;
|
||||
}
|
||||
|
||||
public static boolean isHidden(int modifiers) {
|
||||
return (modifiers & HIDDEN) != 0;
|
||||
}
|
||||
|
||||
public static boolean isExternal(int modifiers) {
|
||||
return (modifiers & EXTERNAL) != 0;
|
||||
}
|
||||
|
||||
public static boolean isClass(int modifiers) {
|
||||
return (modifiers & CLASS) != 0;
|
||||
}
|
||||
|
||||
public static boolean isTypeAlias(int modifiers) {
|
||||
return (modifiers & TYPE_ALIAS) != 0;
|
||||
}
|
||||
|
||||
public static boolean isImport(int modifiers) {
|
||||
return (modifiers & IMPORT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isGlob(int modifiers) {
|
||||
return (modifiers & GLOB) != 0;
|
||||
}
|
||||
|
||||
public static boolean isConst(int modifiers) {
|
||||
return (modifiers & CONST) != 0;
|
||||
}
|
||||
|
||||
public static boolean isElement(int modifiers) {
|
||||
return (modifiers & ELEMENT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isEntry(int modifiers) {
|
||||
return (modifiers & ENTRY) != 0;
|
||||
}
|
||||
|
||||
public static boolean isType(int modifiers) {
|
||||
return (modifiers & (CLASS | TYPE_ALIAS | IMPORT)) != 0 && (modifiers & GLOB) == 0;
|
||||
}
|
||||
|
||||
public static boolean isLocalOrExternalOrHidden(int modifiers) {
|
||||
return (modifiers & (LOCAL | EXTERNAL | HIDDEN)) != 0;
|
||||
}
|
||||
|
||||
public static boolean isLocalOrExternalOrAbstract(int modifiers) {
|
||||
return (modifiers & (LOCAL | EXTERNAL | ABSTRACT)) != 0;
|
||||
}
|
||||
|
||||
public static boolean isConstOrFixed(int modifiers) {
|
||||
return (modifiers & (CONST | FIXED)) != 0;
|
||||
}
|
||||
|
||||
public static Set<Modifier> export(int modifiers, boolean isClass) {
|
||||
var result = EnumSet.noneOf(Modifier.class);
|
||||
|
||||
if (isAbstract(modifiers)) result.add(Modifier.ABSTRACT);
|
||||
if (isOpen(modifiers)) result.add(Modifier.OPEN);
|
||||
if (isHidden(modifiers)) result.add(Modifier.HIDDEN);
|
||||
// `external` modifier is part of class contract but not part of property/method contract
|
||||
if (isExternal(modifiers) && isClass) result.add(Modifier.EXTERNAL);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String toString(int modifier) {
|
||||
switch (modifier) {
|
||||
case ABSTRACT:
|
||||
return "abstract";
|
||||
case OPEN:
|
||||
return "open";
|
||||
case LOCAL:
|
||||
return "local";
|
||||
case HIDDEN:
|
||||
return "hidden";
|
||||
case EXTERNAL:
|
||||
return "external";
|
||||
default:
|
||||
throw new VmExceptionBuilder()
|
||||
.bug("Cannot convert internal modifier `%s` to a string.", toString(modifier))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public static VmSet getMirrors(int modifiers, boolean isClass) {
|
||||
var builder = VmSet.EMPTY.builder();
|
||||
|
||||
if (isAbstract(modifiers)) builder.add(toString(ABSTRACT));
|
||||
if (isOpen(modifiers)) builder.add(toString(OPEN));
|
||||
if (isHidden(modifiers)) builder.add(toString(HIDDEN));
|
||||
// `external` modifier is part of class contract but not part of property/method contract
|
||||
if (isExternal(modifiers) && isClass) builder.add(toString(EXTERNAL));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static boolean isClosed(int modifiers) {
|
||||
return (modifiers & (ABSTRACT | OPEN)) == 0;
|
||||
}
|
||||
|
||||
public static boolean isInstantiable(int modifiers) {
|
||||
return (modifiers & (ABSTRACT | EXTERNAL)) == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.builder;
|
||||
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.util.List;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.pkl.core.parser.antlr.PklLexer;
|
||||
import org.pkl.core.parser.antlr.PklParser.ModifierContext;
|
||||
import org.pkl.core.parser.antlr.PklParserBaseVisitor;
|
||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public abstract class AbstractAstBuilder<T> extends PklParserBaseVisitor<T> {
|
||||
|
||||
protected final Source source;
|
||||
|
||||
protected AbstractAstBuilder(Source source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
protected abstract VmExceptionBuilder exceptionBuilder();
|
||||
|
||||
protected String doVisitSingleLineConstantStringPart(List<Token> ts) {
|
||||
if (ts.isEmpty()) return "";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
for (var token : ts) {
|
||||
switch (token.getType()) {
|
||||
case PklLexer.SLCharacters:
|
||||
builder.append(token.getText());
|
||||
break;
|
||||
case PklLexer.SLCharacterEscape:
|
||||
builder.append(parseCharacterEscapeSequence(token));
|
||||
break;
|
||||
case PklLexer.SLUnicodeEscape:
|
||||
builder.appendCodePoint(parseUnicodeEscapeSequence(token));
|
||||
break;
|
||||
default:
|
||||
throw exceptionBuilder().unreachableCode().build();
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
protected int parseUnicodeEscapeSequence(Token token) {
|
||||
var text = token.getText();
|
||||
var lastIndex = text.length() - 1;
|
||||
|
||||
if (text.charAt(lastIndex) != '}') {
|
||||
throw exceptionBuilder()
|
||||
.evalError("unterminatedUnicodeEscapeSequence", token.getText())
|
||||
.withSourceSection(createSourceSection(token))
|
||||
.build();
|
||||
}
|
||||
|
||||
var startIndex = text.indexOf('{', 2);
|
||||
assert startIndex != -1; // guaranteed by lexer
|
||||
|
||||
try {
|
||||
return Integer.parseInt(text.substring(startIndex + 1, lastIndex), 16);
|
||||
} catch (NumberFormatException e) {
|
||||
throw exceptionBuilder()
|
||||
.evalError("invalidUnicodeEscapeSequence", token.getText(), text.substring(0, startIndex))
|
||||
.withSourceSection(createSourceSection(token))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
protected String parseCharacterEscapeSequence(Token token) {
|
||||
var text = token.getText();
|
||||
var lastChar = text.charAt(text.length() - 1);
|
||||
|
||||
switch (lastChar) {
|
||||
case 'n':
|
||||
return "\n";
|
||||
case 'r':
|
||||
return "\r";
|
||||
case 't':
|
||||
return "\t";
|
||||
case '"':
|
||||
return "\"";
|
||||
case '\\':
|
||||
return "\\";
|
||||
default:
|
||||
throw exceptionBuilder()
|
||||
.evalError("invalidCharacterEscapeSequence", text, text.substring(0, text.length() - 1))
|
||||
.withSourceSection(createSourceSection(token))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
protected final SourceSection createSourceSection(ParserRuleContext ctx) {
|
||||
return createSourceSection(ctx.getStart(), ctx.getStop());
|
||||
}
|
||||
|
||||
protected final SourceSection createSourceSection(TerminalNode node) {
|
||||
return createSourceSection(node.getSymbol());
|
||||
}
|
||||
|
||||
protected final @Nullable SourceSection createSourceSection(@Nullable Token token) {
|
||||
return token != null ? createSourceSection(token, token) : null;
|
||||
}
|
||||
|
||||
protected final SourceSection createSourceSection(Token start, Token stop) {
|
||||
return source.createSection(
|
||||
start.getStartIndex(), stop.getStopIndex() - start.getStartIndex() + 1);
|
||||
}
|
||||
|
||||
protected final SourceSection createSourceSection(
|
||||
List<? extends ModifierContext> modifierCtxs, int symbol) {
|
||||
|
||||
var modifierCtx =
|
||||
modifierCtxs.stream().filter(ctx -> ctx.t.getType() == symbol).findFirst().orElseThrow();
|
||||
|
||||
return createSourceSection(modifierCtx);
|
||||
}
|
||||
|
||||
protected static SourceSection createSourceSection(Source source, ParserRuleContext ctx) {
|
||||
var start = ctx.start.getStartIndex();
|
||||
var stop = ctx.stop.getStopIndex();
|
||||
return source.createSection(start, stop - start + 1);
|
||||
}
|
||||
|
||||
protected static @Nullable SourceSection createSourceSection(
|
||||
Source source, @Nullable Token token) {
|
||||
if (token == null) return null;
|
||||
|
||||
var start = token.getStartIndex();
|
||||
var stop = token.getStopIndex();
|
||||
return source.createSection(start, stop - start + 1);
|
||||
}
|
||||
}
|
||||
2985
pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java
Normal file
2985
pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.builder;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
|
||||
public final class CannotInvokeAbstractFunctionNode extends ExpressionNode {
|
||||
private final String functionName;
|
||||
|
||||
public CannotInvokeAbstractFunctionNode(SourceSection section, String functionName) {
|
||||
super(section);
|
||||
this.functionName = functionName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().evalError("cannotInvokeAbstractMethod", functionName).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.builder;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
|
||||
public final class CannotInvokeAbstractPropertyNode extends ExpressionNode {
|
||||
private final String propertyName;
|
||||
|
||||
public CannotInvokeAbstractPropertyNode(SourceSection section, String propertyName) {
|
||||
super(section);
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().evalError("cannotInvokeAbstractProperty", propertyName).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.builder;
|
||||
|
||||
public enum ConstLevel {
|
||||
NONE,
|
||||
MODULE,
|
||||
ALL;
|
||||
|
||||
public boolean isConst() {
|
||||
return this != NONE;
|
||||
}
|
||||
|
||||
public boolean biggerOrEquals(ConstLevel other) {
|
||||
return this.ordinal() >= other.ordinal();
|
||||
}
|
||||
|
||||
public boolean bigger(ConstLevel other) {
|
||||
return this.ordinal() > other.ordinal();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.builder;
|
||||
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.pkl.core.module.ModuleKey;
|
||||
import org.pkl.core.module.ResolvedModuleKey;
|
||||
import org.pkl.core.parser.Parser;
|
||||
import org.pkl.core.parser.antlr.PklParser.ImportClauseContext;
|
||||
import org.pkl.core.parser.antlr.PklParser.ImportExprContext;
|
||||
import org.pkl.core.parser.antlr.PklParser.ModuleExtendsOrAmendsClauseContext;
|
||||
import org.pkl.core.parser.antlr.PklParser.ReadExprContext;
|
||||
import org.pkl.core.parser.antlr.PklParser.SingleLineStringLiteralContext;
|
||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
import org.pkl.core.util.Pair;
|
||||
|
||||
/**
|
||||
* Collects module uris and resource uris imported within a module.
|
||||
*
|
||||
* <p>Gathers the following:
|
||||
*
|
||||
* <ul>
|
||||
* <li>amends/extends URI's
|
||||
* <li>import declarations
|
||||
* <li>import expressions
|
||||
* <li>read expressions
|
||||
* </ul>
|
||||
*/
|
||||
public class ImportsAndReadsParser
|
||||
extends AbstractAstBuilder<@Nullable List<Pair<String, SourceSection>>> {
|
||||
|
||||
/** Parses a module, and collects all imports and reads. */
|
||||
public static @Nullable List<Pair<String, SourceSection>> parse(
|
||||
ModuleKey moduleKey, ResolvedModuleKey resolvedModuleKey) throws IOException {
|
||||
var parser = new Parser();
|
||||
var text = resolvedModuleKey.loadSource();
|
||||
var source = VmUtils.createSource(moduleKey, text);
|
||||
var importListParser = new ImportsAndReadsParser(source);
|
||||
return parser.parseModule(text).accept(importListParser);
|
||||
}
|
||||
|
||||
public ImportsAndReadsParser(Source source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VmExceptionBuilder exceptionBuilder() {
|
||||
return new VmExceptionBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<Pair<String, SourceSection>> visitModuleExtendsOrAmendsClause(
|
||||
ModuleExtendsOrAmendsClauseContext ctx) {
|
||||
var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts);
|
||||
var sourceSection = createSourceSection(ctx.stringConstant());
|
||||
return Collections.singletonList(Pair.of(importStr, sourceSection));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, SourceSection>> visitImportClause(ImportClauseContext ctx) {
|
||||
var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts);
|
||||
var sourceSection = createSourceSection(ctx.stringConstant());
|
||||
return Collections.singletonList(Pair.of(importStr, sourceSection));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, SourceSection>> visitImportExpr(ImportExprContext ctx) {
|
||||
var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts);
|
||||
var sourceSection = createSourceSection(ctx.stringConstant());
|
||||
return Collections.singletonList(Pair.of(importStr, sourceSection));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, SourceSection>> visitReadExpr(ReadExprContext ctx) {
|
||||
var expr = ctx.expr();
|
||||
if (!(expr instanceof SingleLineStringLiteralContext)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// best-effort approach; only collect read expressions that are string constants.
|
||||
var slCtx = (SingleLineStringLiteralContext) expr;
|
||||
var singleParts = slCtx.singleLineStringPart();
|
||||
String importString;
|
||||
if (singleParts.isEmpty()) {
|
||||
importString = "";
|
||||
} else if (singleParts.size() == 1) {
|
||||
var ts = singleParts.get(0).ts;
|
||||
if (!ts.isEmpty()) {
|
||||
importString = doVisitSingleLineConstantStringPart(ts);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(Pair.of(importString, createSourceSection(slCtx)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable List<Pair<String, SourceSection>> aggregateResult(
|
||||
@Nullable List<Pair<String, SourceSection>> aggregate,
|
||||
@Nullable List<Pair<String, SourceSection>> nextResult) {
|
||||
if (aggregate == null || aggregate.isEmpty()) {
|
||||
return nextResult;
|
||||
}
|
||||
if (nextResult == null || nextResult.isEmpty()) {
|
||||
return aggregate;
|
||||
}
|
||||
var ret = new ArrayList<Pair<String, SourceSection>>(aggregate.size() + nextResult.size());
|
||||
ret.addAll(aggregate);
|
||||
ret.addAll(nextResult);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
485
pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java
Normal file
485
pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java
Normal file
@@ -0,0 +1,485 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.builder;
|
||||
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor.Builder;
|
||||
import com.oracle.truffle.api.frame.FrameSlotKind;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import org.pkl.core.TypeParameter;
|
||||
import org.pkl.core.ast.ConstantNode;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.parser.Lexer;
|
||||
import org.pkl.core.parser.antlr.PklParser.ParameterContext;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
import org.pkl.core.runtime.ModuleInfo;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public final class SymbolTable {
|
||||
private Scope currentScope;
|
||||
|
||||
public static Object FOR_GENERATOR_VARIABLE = new Object();
|
||||
|
||||
public SymbolTable(ModuleInfo moduleInfo) {
|
||||
currentScope = new ModuleScope(moduleInfo);
|
||||
}
|
||||
|
||||
public Scope getCurrentScope() {
|
||||
return currentScope;
|
||||
}
|
||||
|
||||
public @Nullable TypeParameter findTypeParameter(String name) {
|
||||
TypeParameter result;
|
||||
for (var scope = currentScope; scope != null; scope = scope.getParent()) {
|
||||
result = scope.getTypeParameter(name);
|
||||
if (result != null) return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ObjectMember enterClass(
|
||||
Identifier name,
|
||||
List<TypeParameter> typeParameters,
|
||||
Function<ClassScope, ObjectMember> nodeFactory) {
|
||||
return doEnter(
|
||||
new ClassScope(
|
||||
currentScope,
|
||||
name,
|
||||
toQualifiedName(name),
|
||||
FrameDescriptor.newBuilder(),
|
||||
typeParameters),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
public ObjectMember enterTypeAlias(
|
||||
Identifier name,
|
||||
List<TypeParameter> typeParameters,
|
||||
Function<TypeAliasScope, ObjectMember> nodeFactory) {
|
||||
return doEnter(
|
||||
new TypeAliasScope(
|
||||
currentScope,
|
||||
name,
|
||||
toQualifiedName(name),
|
||||
FrameDescriptor.newBuilder(),
|
||||
typeParameters),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterMethod(
|
||||
Identifier name,
|
||||
ConstLevel constLevel,
|
||||
Builder frameDescriptorBuilder,
|
||||
List<TypeParameter> typeParameters,
|
||||
Function<MethodScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
new MethodScope(
|
||||
currentScope,
|
||||
name,
|
||||
toQualifiedName(name),
|
||||
constLevel,
|
||||
frameDescriptorBuilder,
|
||||
typeParameters),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterLambda(
|
||||
FrameDescriptor.Builder frameDescriptorBuilder, Function<LambdaScope, T> nodeFactory) {
|
||||
|
||||
// flatten names of lambdas nested inside other lambdas for presentation purposes
|
||||
var parentScope = currentScope;
|
||||
while (parentScope instanceof LambdaScope) {
|
||||
parentScope = parentScope.getParent();
|
||||
}
|
||||
|
||||
assert parentScope != null;
|
||||
var qualifiedName = parentScope.qualifiedName + "." + parentScope.getNextLambdaName();
|
||||
|
||||
return doEnter(
|
||||
new LambdaScope(currentScope, qualifiedName, frameDescriptorBuilder), nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterProperty(
|
||||
Identifier name, ConstLevel constLevel, Function<PropertyScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
new PropertyScope(
|
||||
currentScope, name, toQualifiedName(name), constLevel, FrameDescriptor.newBuilder()),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterEntry(
|
||||
@Nullable ExpressionNode keyNode, // null for listing elements
|
||||
Function<EntryScope, T> nodeFactory) {
|
||||
|
||||
var qualifiedName = currentScope.getQualifiedName() + currentScope.getNextEntryName(keyNode);
|
||||
|
||||
return doEnter(
|
||||
new EntryScope(currentScope, qualifiedName, FrameDescriptor.newBuilder()), nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterCustomThisScope(Function<CustomThisScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
new CustomThisScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterAnnotationScope(Function<AnnotationScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
new AnnotationScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterObjectScope(Function<ObjectScope, T> nodeFactory) {
|
||||
return doEnter(new ObjectScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
||||
}
|
||||
|
||||
private <T, S extends Scope> T doEnter(S scope, Function<S, T> nodeFactory) {
|
||||
var parentScope = currentScope;
|
||||
currentScope = scope;
|
||||
try {
|
||||
return nodeFactory.apply(scope);
|
||||
} finally {
|
||||
currentScope = parentScope;
|
||||
}
|
||||
}
|
||||
|
||||
private String toQualifiedName(Identifier name) {
|
||||
var separator = currentScope instanceof ModuleScope ? "#" : ".";
|
||||
return currentScope.qualifiedName + separator + Lexer.maybeQuoteIdentifier(name.toString());
|
||||
}
|
||||
|
||||
public abstract static class Scope {
|
||||
private final @Nullable Scope parent;
|
||||
private final @Nullable Identifier name;
|
||||
private final String qualifiedName;
|
||||
private final Deque<Identifier> forGeneratorVariables = new ArrayDeque<>();
|
||||
private int lambdaCount = 0;
|
||||
private int entryCount = 0;
|
||||
private final FrameDescriptor.Builder frameDescriptorBuilder;
|
||||
private final ConstLevel constLevel;
|
||||
|
||||
private Scope(
|
||||
@Nullable Scope parent,
|
||||
@Nullable Identifier name,
|
||||
String qualifiedName,
|
||||
ConstLevel constLevel,
|
||||
FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
this.qualifiedName = qualifiedName;
|
||||
this.frameDescriptorBuilder = frameDescriptorBuilder;
|
||||
// const level can never decrease
|
||||
this.constLevel =
|
||||
parent != null && parent.constLevel.biggerOrEquals(constLevel)
|
||||
? parent.constLevel
|
||||
: constLevel;
|
||||
}
|
||||
|
||||
public final @Nullable Scope getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public final Identifier getName() {
|
||||
assert name != null;
|
||||
return name;
|
||||
}
|
||||
|
||||
public final @Nullable Identifier getNameOrNull() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public final String getQualifiedName() {
|
||||
return qualifiedName;
|
||||
}
|
||||
|
||||
public FrameDescriptor buildFrameDescriptor() {
|
||||
return frameDescriptorBuilder.build();
|
||||
}
|
||||
|
||||
public @Nullable TypeParameter getTypeParameter(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public final Scope getLexicalScope() {
|
||||
var scope = this;
|
||||
while (!scope.isLexicalScope()) {
|
||||
scope = scope.parent;
|
||||
assert scope != null;
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
private boolean isConst() {
|
||||
return constLevel.isConst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lexical depth from the current scope to the top-most scope that is const. Depth
|
||||
* is 0-indexed, and -1 means that the scope is not a const scope.
|
||||
*
|
||||
* <p>A const scope is a lexical scope on the right-hand side of a const property.
|
||||
*
|
||||
* <pre>{@code
|
||||
* const foo = new {
|
||||
* bar {
|
||||
* baz // <-- depth == 1
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public int getConstDepth() {
|
||||
var depth = -1;
|
||||
var lexicalScope = getLexicalScope();
|
||||
while (lexicalScope.getConstLevel() == ConstLevel.ALL) {
|
||||
depth += 1;
|
||||
var parent = lexicalScope.getParent();
|
||||
if (parent == null) {
|
||||
return depth;
|
||||
}
|
||||
lexicalScope = parent.getLexicalScope();
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the for generator variable to the frame descriptor.
|
||||
*
|
||||
* <p>Returns {@code -1} if a for-generator variable already exists with this name.
|
||||
*/
|
||||
public int pushForGeneratorVariableContext(ParameterContext ctx) {
|
||||
var variable = Identifier.localProperty(ctx.typedIdentifier().Identifier().getText());
|
||||
if (forGeneratorVariables.contains(variable)) {
|
||||
return -1;
|
||||
}
|
||||
var slot =
|
||||
frameDescriptorBuilder.addSlot(FrameSlotKind.Illegal, variable, FOR_GENERATOR_VARIABLE);
|
||||
forGeneratorVariables.addLast(variable);
|
||||
return slot;
|
||||
}
|
||||
|
||||
public void popForGeneratorVariable() {
|
||||
forGeneratorVariables.removeLast();
|
||||
}
|
||||
|
||||
public Deque<Identifier> getForGeneratorVariables() {
|
||||
return forGeneratorVariables;
|
||||
}
|
||||
|
||||
private String getNextLambdaName() {
|
||||
return "<function#" + (++skipLambdaScopes().lambdaCount) + ">";
|
||||
}
|
||||
|
||||
private String getNextEntryName(@Nullable ExpressionNode keyNode) {
|
||||
if (keyNode instanceof ConstantNode) {
|
||||
var value = ((ConstantNode) keyNode).getValue();
|
||||
if (value instanceof String) {
|
||||
return "[\"" + value + "\"]";
|
||||
}
|
||||
|
||||
if (value instanceof Long
|
||||
|| value instanceof Double
|
||||
|| value instanceof Boolean
|
||||
|| value instanceof VmDuration
|
||||
|| value instanceof VmDataSize) {
|
||||
return "[" + value + "]";
|
||||
}
|
||||
}
|
||||
|
||||
return "[#" + (++entryCount) + "]";
|
||||
}
|
||||
|
||||
public final Scope skipLambdaScopes() {
|
||||
var curr = this;
|
||||
while (curr.isLambdaScope()) {
|
||||
curr = curr.getParent();
|
||||
assert curr != null : "Lambda scope always has a parent";
|
||||
}
|
||||
return curr;
|
||||
}
|
||||
|
||||
public final boolean isModuleScope() {
|
||||
return this instanceof ModuleScope;
|
||||
}
|
||||
|
||||
public final boolean isClassScope() {
|
||||
return this instanceof ClassScope;
|
||||
}
|
||||
|
||||
public final boolean isClassMemberScope() {
|
||||
if (parent == null) return false;
|
||||
|
||||
return parent.isClassScope()
|
||||
|| parent.isModuleScope() && !((ModuleScope) parent).moduleInfo.isAmend();
|
||||
}
|
||||
|
||||
public final boolean isLambdaScope() {
|
||||
return this instanceof LambdaScope;
|
||||
}
|
||||
|
||||
public final boolean isCustomThisScope() {
|
||||
return this instanceof CustomThisScope;
|
||||
}
|
||||
|
||||
public final boolean isLexicalScope() {
|
||||
return this instanceof LexicalScope;
|
||||
}
|
||||
|
||||
public ConstLevel getConstLevel() {
|
||||
return constLevel;
|
||||
}
|
||||
}
|
||||
|
||||
private interface LexicalScope {}
|
||||
|
||||
public static class ObjectScope extends Scope implements LexicalScope {
|
||||
private ObjectScope(Scope parent, Builder frameDescriptorBuilder) {
|
||||
super(
|
||||
parent,
|
||||
parent.getNameOrNull(),
|
||||
parent.getQualifiedName(),
|
||||
ConstLevel.NONE,
|
||||
frameDescriptorBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class TypeParameterizableScope extends Scope {
|
||||
private final List<TypeParameter> typeParameters;
|
||||
|
||||
public TypeParameterizableScope(
|
||||
Scope parent,
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
ConstLevel constLevel,
|
||||
Builder frameDescriptorBuilder,
|
||||
List<TypeParameter> typeParameters) {
|
||||
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder);
|
||||
this.typeParameters = typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TypeParameter getTypeParameter(String name) {
|
||||
for (var param : typeParameters) {
|
||||
if (name.equals(param.getName())) return param;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ModuleScope extends Scope implements LexicalScope {
|
||||
private final ModuleInfo moduleInfo;
|
||||
|
||||
public ModuleScope(ModuleInfo moduleInfo) {
|
||||
super(null, null, moduleInfo.getModuleName(), ConstLevel.NONE, FrameDescriptor.newBuilder());
|
||||
this.moduleInfo = moduleInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MethodScope extends TypeParameterizableScope {
|
||||
public MethodScope(
|
||||
Scope parent,
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
ConstLevel constLevel,
|
||||
Builder frameDescriptorBuilder,
|
||||
List<TypeParameter> typeParameters) {
|
||||
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder, typeParameters);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class LambdaScope extends Scope implements LexicalScope {
|
||||
public LambdaScope(
|
||||
Scope parent, String qualifiedName, FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PropertyScope extends Scope {
|
||||
public PropertyScope(
|
||||
Scope parent,
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
ConstLevel constLevel,
|
||||
FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EntryScope extends Scope {
|
||||
public EntryScope(
|
||||
Scope parent, String qualifiedName, FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ClassScope extends TypeParameterizableScope implements LexicalScope {
|
||||
public ClassScope(
|
||||
Scope parent,
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
Builder frameDescriptorBuilder,
|
||||
List<TypeParameter> typeParameters) {
|
||||
super(parent, name, qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder, typeParameters);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TypeAliasScope extends TypeParameterizableScope {
|
||||
public TypeAliasScope(
|
||||
Scope parent,
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
FrameDescriptor.Builder frameDescriptorBuilder,
|
||||
List<TypeParameter> typeParameters) {
|
||||
super(parent, name, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder, typeParameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A scope where {@code this} has a special meaning (type constraint, object member predicate).
|
||||
*
|
||||
* <p>Technically, a scope where {@code this} isn't {@code frame.getArguments()[0]}, but the value
|
||||
* at an auxiliary slot identified by {@link CustomThisScope#FRAME_SLOT_ID}.
|
||||
*/
|
||||
public static final class CustomThisScope extends Scope {
|
||||
public static final Object FRAME_SLOT_ID =
|
||||
new Object() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "customThisSlot";
|
||||
}
|
||||
};
|
||||
|
||||
public CustomThisScope(Scope parent, FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
super(
|
||||
parent,
|
||||
parent.getNameOrNull(),
|
||||
parent.getQualifiedName(),
|
||||
ConstLevel.NONE,
|
||||
frameDescriptorBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class AnnotationScope extends Scope implements LexicalScope {
|
||||
public AnnotationScope(Scope parent, FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
super(
|
||||
parent,
|
||||
parent.getNameOrNull(),
|
||||
parent.getQualifiedName(),
|
||||
ConstLevel.MODULE,
|
||||
frameDescriptorBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.core.ast.builder;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.*;
|
||||
|
||||
@NodeInfo(shortName = "+")
|
||||
public abstract class AdditionNode extends BinaryExpressionNode {
|
||||
protected AdditionNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(String left, String right) {
|
||||
return left + right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(long left, long right) {
|
||||
try {
|
||||
return StrictMath.addExact(left, right);
|
||||
} catch (ArithmeticException e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().evalError("integerOverflow").build();
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(long left, double right) {
|
||||
return left + right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, long right) {
|
||||
return left + right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, double right) {
|
||||
return left + right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, VmDuration right) {
|
||||
return left.add(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, VmDataSize right) {
|
||||
return left.add(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmCollection eval(VmCollection left, VmCollection right) {
|
||||
return left.concatenate(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmMap eval(VmMap left, VmMap right) {
|
||||
return left.concatenate(right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.NodeChild;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
|
||||
@NodeChild(value = "leftNode", type = ExpressionNode.class)
|
||||
@NodeChild(value = "rightNode", type = ExpressionNode.class)
|
||||
public abstract class BinaryExpressionNode extends ExpressionNode {
|
||||
protected BinaryExpressionNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
protected abstract ExpressionNode getLeftNode();
|
||||
|
||||
protected abstract ExpressionNode getRightNode();
|
||||
|
||||
@Fallback
|
||||
@TruffleBoundary
|
||||
protected Object fallback(Object left, Object right) {
|
||||
throw exceptionBuilder()
|
||||
.evalError(
|
||||
"operatorNotDefined2", getShortName(), VmUtils.getClass(left), VmUtils.getClass(right))
|
||||
.withProgramValue("Left operand", left)
|
||||
.withProgramValue("Right operand", right)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
|
||||
public abstract class ComparatorNode extends BinaryExpressionNode {
|
||||
public ComparatorNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
public abstract boolean executeWith(Object left, Object right);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.*;
|
||||
|
||||
@NodeInfo(shortName = "/")
|
||||
public abstract class DivisionNode extends BinaryExpressionNode {
|
||||
protected DivisionNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(long left, long right) {
|
||||
return (double) left / (double) right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(long left, double right) {
|
||||
return (double) left / right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, long right) {
|
||||
return left / right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, double right) {
|
||||
return left / right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, long right) {
|
||||
return left.divide(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, double right) {
|
||||
return left.divide(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(VmDuration left, VmDuration right) {
|
||||
return left.divide(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, long right) {
|
||||
return left.divide(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, double right) {
|
||||
return left.divide(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(VmDataSize left, VmDataSize right) {
|
||||
return left.divide(right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.NodeChild;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@NodeInfo(shortName = "==")
|
||||
@NodeChild(value = "leftNode", type = ExpressionNode.class)
|
||||
@NodeChild(value = "rightNode", type = ExpressionNode.class)
|
||||
// not extending BinaryExpressionNode because we don't want the latter's fallback
|
||||
public abstract class EqualNode extends ExpressionNode {
|
||||
protected EqualNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(String left, String right) {
|
||||
return left.equals(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, long right) {
|
||||
return left == right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, double right) {
|
||||
return left == right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, long right) {
|
||||
return left == right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, double right) {
|
||||
return left == right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(boolean left, boolean right) {
|
||||
return left == right;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method effectively covers `VmValue left, VmValue right` but is implemented in a more
|
||||
* efficient way. See:
|
||||
* https://www.graalvm.org/22.0/graalvm-as-a-platform/language-implementation-framework/TruffleLibraries/#strategy-2-java-interfaces
|
||||
*/
|
||||
@Specialization(
|
||||
guards = {"left.getClass() == leftJavaClass", "right.getClass() == leftJavaClass"},
|
||||
limit = "99")
|
||||
protected boolean eval(
|
||||
Object left,
|
||||
Object right,
|
||||
@SuppressWarnings("unused") @Cached("getVmValueJavaClassOrNull(left)")
|
||||
Class<? extends VmValue> leftJavaClass) {
|
||||
return equals(left, right);
|
||||
}
|
||||
|
||||
// TODO: Putting the equals call behind a boundary make the above optimization moot.
|
||||
// Without the boundary, native-image 22.0 complains that Object.equals is reachable for
|
||||
// runtime compilation, but with the above optimization, this isn't actually a problem.
|
||||
@TruffleBoundary
|
||||
private boolean equals(Object left, Object right) {
|
||||
return left.equals(right);
|
||||
}
|
||||
|
||||
protected static @Nullable Class<? extends VmValue> getVmValueJavaClassOrNull(Object value) {
|
||||
// OK to perform slow cast here (not a guard)
|
||||
return value instanceof VmValue ? ((VmValue) value).getClass() : null;
|
||||
}
|
||||
|
||||
// covers all remaining cases (else it's a bug)
|
||||
@Specialization(guards = "isIncompatibleTypes(left, right)")
|
||||
protected boolean eval(
|
||||
@SuppressWarnings("unused") Object left, @SuppressWarnings("unused") Object right) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static boolean isIncompatibleTypes(Object left, Object right) {
|
||||
var leftClass = left.getClass();
|
||||
var rightClass = right.getClass();
|
||||
|
||||
return leftClass == Long.class || leftClass == Double.class
|
||||
? rightClass != Long.class && rightClass != Double.class
|
||||
: leftClass != rightClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
import org.pkl.core.runtime.VmSafeMath;
|
||||
|
||||
@NodeInfo(shortName = "**")
|
||||
public abstract class ExponentiationNode extends BinaryExpressionNode {
|
||||
public ExponentiationNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization(guards = "y >= 0")
|
||||
protected long evalPositive(long x, long y) {
|
||||
return VmSafeMath.pow(x, y);
|
||||
}
|
||||
|
||||
@Specialization(guards = "y < 0")
|
||||
protected double evalNegative(long x, long y) {
|
||||
return StrictMath.pow(x, y);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(long x, double y) {
|
||||
return StrictMath.pow(x, y);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double x, long y) {
|
||||
return StrictMath.pow(x, y);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double x, double y) {
|
||||
return StrictMath.pow(x, y);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration x, long y) {
|
||||
return x.pow(y);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration x, double y) {
|
||||
return x.pow(y);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize x, long y) {
|
||||
return x.pow(y);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize x, double y) {
|
||||
return x.pow(y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
|
||||
@NodeInfo(shortName = ">")
|
||||
public abstract class GreaterThanNode extends ComparatorNode {
|
||||
protected GreaterThanNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected boolean eval(String left, String right) {
|
||||
return left.compareTo(right) > 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, long right) {
|
||||
return left > right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, double right) {
|
||||
return left > right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, long right) {
|
||||
return left > right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, double right) {
|
||||
return left > right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmDuration left, VmDuration right) {
|
||||
return left.compareTo(right) > 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmDataSize left, VmDataSize right) {
|
||||
return left.compareTo(right) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
|
||||
@NodeInfo(shortName = ">=")
|
||||
public abstract class GreaterThanOrEqualNode extends BinaryExpressionNode {
|
||||
protected GreaterThanOrEqualNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected boolean eval(String left, String right) {
|
||||
return left.compareTo(right) >= 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, long right) {
|
||||
return left >= right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, double right) {
|
||||
return left >= right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, long right) {
|
||||
return left >= right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, double right) {
|
||||
return left >= right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmDuration left, VmDuration right) {
|
||||
return left.compareTo(right) >= 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmDataSize left, VmDataSize right) {
|
||||
return left.compareTo(right) >= 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
|
||||
@NodeInfo(shortName = "<")
|
||||
public abstract class LessThanNode extends ComparatorNode {
|
||||
protected LessThanNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected boolean eval(String left, String right) {
|
||||
return left.compareTo(right) < 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, long right) {
|
||||
return left < right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, double right) {
|
||||
return left < right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, long right) {
|
||||
return left < right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, double right) {
|
||||
return left < right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmDuration left, VmDuration right) {
|
||||
return left.compareTo(right) < 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmDataSize left, VmDataSize right) {
|
||||
return left.compareTo(right) < 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
|
||||
@NodeInfo(shortName = "<=")
|
||||
public abstract class LessThanOrEqualNode extends BinaryExpressionNode {
|
||||
protected LessThanOrEqualNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
public abstract boolean executeWith(Object left, Object right);
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected boolean eval(String left, String right) {
|
||||
return left.compareTo(right) <= 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, long right) {
|
||||
return left <= right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, double right) {
|
||||
return left <= right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, long right) {
|
||||
return left <= right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, double right) {
|
||||
return left <= right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmDuration left, VmDuration right) {
|
||||
return left.compareTo(right) <= 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmDataSize left, VmDataSize right) {
|
||||
return left.compareTo(right) <= 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.DirectCallNode;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
|
||||
import org.pkl.core.ast.member.FunctionNode;
|
||||
import org.pkl.core.ast.member.UnresolvedFunctionNode;
|
||||
import org.pkl.core.runtime.VmFunction;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.util.LateInit;
|
||||
|
||||
public final class LetExprNode extends ExpressionNode {
|
||||
private @Child UnresolvedFunctionNode unresolvedFunctionNode;
|
||||
private @Child ExpressionNode valueNode;
|
||||
private final boolean isCustomThisScope;
|
||||
|
||||
@CompilationFinal @LateInit private FunctionNode functionNode;
|
||||
@Child @LateInit private DirectCallNode callNode;
|
||||
@CompilationFinal private int customThisSlot = -1;
|
||||
|
||||
public LetExprNode(
|
||||
SourceSection sourceSection,
|
||||
UnresolvedFunctionNode functionNode,
|
||||
ExpressionNode valueNode,
|
||||
boolean isCustomThisScope) {
|
||||
|
||||
super(sourceSection);
|
||||
this.unresolvedFunctionNode = functionNode;
|
||||
this.valueNode = valueNode;
|
||||
this.isCustomThisScope = isCustomThisScope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
if (functionNode == null) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
functionNode = unresolvedFunctionNode.execute(frame);
|
||||
callNode = insert(DirectCallNode.create(functionNode.getCallTarget()));
|
||||
if (isCustomThisScope) {
|
||||
// deferred until execution time s.t. nodes of inlined type aliases get the right frame slot
|
||||
customThisSlot = VmUtils.findAuxiliarySlot(frame, CustomThisScope.FRAME_SLOT_ID);
|
||||
}
|
||||
}
|
||||
|
||||
var function =
|
||||
new VmFunction(
|
||||
frame.materialize(),
|
||||
isCustomThisScope ? frame.getAuxiliarySlot(customThisSlot) : VmUtils.getReceiver(frame),
|
||||
1,
|
||||
functionNode,
|
||||
null);
|
||||
|
||||
var value = valueNode.executeGeneric(frame);
|
||||
|
||||
return callNode.call(function.getThisValue(), function, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.nodes.UnexpectedResultException;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
|
||||
@NodeInfo(shortName = "&&")
|
||||
public abstract class LogicalAndNode extends ShortCircuitingExpressionNode {
|
||||
protected LogicalAndNode(SourceSection sourceSection, ExpressionNode rightNode) {
|
||||
super(sourceSection, rightNode);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VirtualFrame frame, boolean left) {
|
||||
try {
|
||||
return left && rightNode.executeBoolean(frame);
|
||||
} catch (UnexpectedResultException e) {
|
||||
throw operatorNotDefined(true, e.getResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.nodes.UnexpectedResultException;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
|
||||
@NodeInfo(shortName = "||")
|
||||
public abstract class LogicalOrNode extends ShortCircuitingExpressionNode {
|
||||
protected LogicalOrNode(SourceSection sourceSection, ExpressionNode rightNode) {
|
||||
super(sourceSection, rightNode);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VirtualFrame frame, boolean left) {
|
||||
try {
|
||||
return left || rightNode.executeBoolean(frame);
|
||||
} catch (UnexpectedResultException e) {
|
||||
throw operatorNotDefined(false, e.getResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.*;
|
||||
|
||||
@NodeInfo(shortName = "*")
|
||||
public abstract class MultiplicationNode extends BinaryExpressionNode {
|
||||
protected MultiplicationNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(long left, long right) {
|
||||
try {
|
||||
return StrictMath.multiplyExact(left, right);
|
||||
} catch (VmException e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().evalError("integerOverflow").build();
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(long left, double right) {
|
||||
return left * right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, long right) {
|
||||
return left * right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, double right) {
|
||||
return left * right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, long right) {
|
||||
return left.multiply(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, double right) {
|
||||
return left.multiply(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(long left, VmDuration right) {
|
||||
return right.multiply(left);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(double left, VmDuration right) {
|
||||
return right.multiply(left);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, long right) {
|
||||
return left.multiply(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, double right) {
|
||||
return left.multiply(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(long left, VmDataSize right) {
|
||||
return right.multiply(left);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(double left, VmDataSize right) {
|
||||
return right.multiply(left);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.NodeChild;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.runtime.VmValue;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@NodeInfo(shortName = "!=")
|
||||
@NodeChild(value = "leftNode", type = ExpressionNode.class)
|
||||
@NodeChild(value = "rightNode", type = ExpressionNode.class)
|
||||
// not extending BinaryExpressionNode because we don't want the latter's fallback
|
||||
public abstract class NotEqualNode extends ExpressionNode {
|
||||
protected NotEqualNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(String left, String right) {
|
||||
return !left.equals(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, long right) {
|
||||
return left != right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(long left, double right) {
|
||||
return left != right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, long right) {
|
||||
return left != right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(double left, double right) {
|
||||
return left != right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(boolean left, boolean right) {
|
||||
return left != right;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method effectively covers `VmValue left, VmValue right` but is implemented in a more
|
||||
* efficient way. See:
|
||||
* https://www.graalvm.org/22.0/graalvm-as-a-platform/language-implementation-framework/TruffleLibraries/#strategy-2-java-interfaces
|
||||
*/
|
||||
@Specialization(
|
||||
guards = {"left.getClass() == leftJavaClass", "right.getClass() == leftJavaClass"},
|
||||
limit = "99")
|
||||
protected boolean eval(
|
||||
Object left,
|
||||
Object right,
|
||||
@SuppressWarnings("unused") @Cached("getVmValueJavaClassOrNull(left)")
|
||||
Class<? extends VmValue> leftJavaClass) {
|
||||
return !equals(left, right);
|
||||
}
|
||||
|
||||
// TODO: Putting the equals call behind a boundary make the above optimization moot.
|
||||
// Without the boundary, native-image 22.0 complains that Object.equals is reachable for
|
||||
// runtime compilation, but with the above optimization, this isn't actually a problem.
|
||||
@TruffleBoundary
|
||||
private boolean equals(Object left, Object right) {
|
||||
return left.equals(right);
|
||||
}
|
||||
|
||||
protected static @Nullable Class<? extends VmValue> getVmValueJavaClassOrNull(Object value) {
|
||||
// OK to perform slow cast here (not a guard)
|
||||
return value instanceof VmValue ? ((VmValue) value).getClass() : null;
|
||||
}
|
||||
|
||||
// covers all remaining cases (else it's a bug)
|
||||
@Specialization(guards = "isIncompatibleTypes(left, right)")
|
||||
protected boolean eval(
|
||||
@SuppressWarnings("unused") Object left, @SuppressWarnings("unused") Object right) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static boolean isIncompatibleTypes(Object left, Object right) {
|
||||
var leftClass = left.getClass();
|
||||
var rightClass = right.getClass();
|
||||
|
||||
return leftClass == Long.class || leftClass == Double.class
|
||||
? rightClass != Long.class && rightClass != Double.class
|
||||
: leftClass != rightClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.runtime.VmNull;
|
||||
|
||||
@NodeInfo(shortName = "??")
|
||||
public abstract class NullCoalescingNode extends ShortCircuitingExpressionNode {
|
||||
protected NullCoalescingNode(SourceSection sourceSection, ExpressionNode rightNode) {
|
||||
super(sourceSection, rightNode);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
protected Object eval(VirtualFrame frame, VmNull left) {
|
||||
return rightNode.executeGeneric(frame);
|
||||
}
|
||||
|
||||
@Fallback
|
||||
@Override
|
||||
protected Object fallback(Object left) {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.lambda.ApplyVmFunction1Node;
|
||||
import org.pkl.core.runtime.VmFunction;
|
||||
|
||||
@NodeInfo(shortName = "|>")
|
||||
public abstract class PipeNode extends BinaryExpressionNode {
|
||||
@Child private ApplyVmFunction1Node applyFunctionNode = ApplyVmFunction1Node.create();
|
||||
|
||||
protected PipeNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected Object eval(Object left, VmFunction right) {
|
||||
return applyFunctionNode.execute(right, left);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
import org.pkl.core.runtime.VmSafeMath;
|
||||
|
||||
@NodeInfo(shortName = "%")
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
public abstract class RemainderNode extends BinaryExpressionNode {
|
||||
protected RemainderNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(long left, long right) {
|
||||
return VmSafeMath.remainder(left, right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(long left, double right) {
|
||||
return VmSafeMath.remainder(left, right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, long right) {
|
||||
return VmSafeMath.remainder(left, right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, double right) {
|
||||
return VmSafeMath.remainder(left, right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, long right) {
|
||||
return left.remainder(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, double right) {
|
||||
return left.remainder(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, long right) {
|
||||
return left.remainder(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, double right) {
|
||||
return left.remainder(right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.NodeChild;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.runtime.VmException;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
|
||||
/**
|
||||
* A binary expression whose right operand may be short-circuited. Does not inherit from
|
||||
* BinaryExpressionNode for technical reasons.
|
||||
*/
|
||||
@NodeChild(value = "leftNode", type = ExpressionNode.class)
|
||||
public abstract class ShortCircuitingExpressionNode extends ExpressionNode {
|
||||
@Child protected ExpressionNode rightNode;
|
||||
|
||||
protected abstract ExpressionNode getLeftNode();
|
||||
|
||||
protected ShortCircuitingExpressionNode(SourceSection sourceSection, ExpressionNode rightNode) {
|
||||
super(sourceSection);
|
||||
this.rightNode = rightNode;
|
||||
}
|
||||
|
||||
@Fallback
|
||||
@TruffleBoundary
|
||||
protected Object fallback(Object left) {
|
||||
throw operatorNotDefined(left);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
protected VmException operatorNotDefined(Object left) {
|
||||
return exceptionBuilder()
|
||||
.evalError("operatorNotDefinedLeft", getShortName(), VmUtils.getClass(left))
|
||||
.withProgramValue("Left operand", left)
|
||||
.build();
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
protected VmException operatorNotDefined(Object left, Object right) {
|
||||
return exceptionBuilder()
|
||||
.evalError(
|
||||
"operatorNotDefined2", getShortName(), VmUtils.getClass(left), VmUtils.getClass(right))
|
||||
.withProgramValue("Left operand", left)
|
||||
.withProgramValue("Right operand", right)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.*;
|
||||
|
||||
@NodeInfo(shortName = "[]")
|
||||
public abstract class SubscriptNode extends BinaryExpressionNode {
|
||||
protected SubscriptNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(String receiver, long index) {
|
||||
var charIndex = VmUtils.codePointOffsetToCharOffset(receiver, index);
|
||||
if (charIndex == -1 || charIndex == receiver.length()) {
|
||||
throw exceptionBuilder()
|
||||
.evalError(
|
||||
"charIndexOutOfRange", index, 0, receiver.codePointCount(0, receiver.length()) - 1)
|
||||
.withSourceSection(getRightNode().getSourceSection())
|
||||
.withProgramValue("String", receiver)
|
||||
.build();
|
||||
}
|
||||
|
||||
if (Character.isHighSurrogate(receiver.charAt(charIndex))) {
|
||||
return receiver.substring(charIndex, charIndex + 2);
|
||||
}
|
||||
return receiver.substring(charIndex, charIndex + 1);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected Object eval(VmList receiver, long index) {
|
||||
if (index < 0 || index >= receiver.getLength()) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder()
|
||||
.evalError("elementIndexOutOfRange", index, 0, receiver.getLength() - 1)
|
||||
.withProgramValue("Collection", receiver)
|
||||
.build();
|
||||
}
|
||||
return receiver.get(index);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected Object eval(VmMap receiver, Object key) {
|
||||
var result = receiver.getOrNull(key);
|
||||
if (result != null) return result;
|
||||
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().cannotFindKey(receiver, key).build();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected Object eval(
|
||||
VmListing listing, long index, @Cached("create()") IndirectCallNode callNode) {
|
||||
|
||||
var result = VmUtils.readMemberOrNull(listing, index, callNode);
|
||||
if (result != null) return result;
|
||||
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder()
|
||||
.evalError("elementIndexOutOfRange", index, 0, listing.getLength() - 1)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected Object eval(
|
||||
VmMapping mapping, Object key, @Cached("create()") IndirectCallNode callNode) {
|
||||
|
||||
return readMember(mapping, key, callNode);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected Object eval(
|
||||
VmDynamic dynamic, Object key, @Cached("create()") IndirectCallNode callNode) {
|
||||
|
||||
return readMember(dynamic, key, callNode);
|
||||
}
|
||||
|
||||
private Object readMember(VmObject object, Object key, IndirectCallNode callNode) {
|
||||
var result = VmUtils.readMemberOrNull(object, key, callNode);
|
||||
if (result != null) return result;
|
||||
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().cannotFindMember(object, key).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.runtime.*;
|
||||
|
||||
@NodeInfo(shortName = "-")
|
||||
public abstract class SubtractionNode extends BinaryExpressionNode {
|
||||
protected SubtractionNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(long left, long right) {
|
||||
try {
|
||||
return StrictMath.subtractExact(left, right);
|
||||
} catch (ArithmeticException e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().evalError("integerOverflow").build();
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(long left, double right) {
|
||||
return left - right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, long right) {
|
||||
return left - right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected double eval(double left, double right) {
|
||||
return left - right;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, VmDuration right) {
|
||||
return left.subtract(right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, VmDataSize right) {
|
||||
return left.subtract(right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.binary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.math.RoundingMode;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
import org.pkl.core.runtime.VmException.ProgramValue;
|
||||
import org.pkl.core.util.MathUtils;
|
||||
|
||||
@NodeInfo(shortName = "~/")
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
public abstract class TruncatingDivisionNode extends BinaryExpressionNode {
|
||||
protected TruncatingDivisionNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(long left, long right) {
|
||||
if (right == 0) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().evalError("divisionByZero").build();
|
||||
}
|
||||
|
||||
var result = left / right;
|
||||
|
||||
// use same check as com.oracle.truffle.sl.nodes.expression.SLDivNode
|
||||
if ((left & right & result) < 0) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
assert left == Long.MIN_VALUE && right == -1;
|
||||
throw exceptionBuilder().evalError("integerOverflow").build();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(long left, double right) {
|
||||
return doTruncatingDivide(left, right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(double left, long right) {
|
||||
return doTruncatingDivide(left, right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(double left, double right) {
|
||||
return doTruncatingDivide(left, right);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, long right) {
|
||||
var newValue = doTruncatingDivide(left.getValue(), right);
|
||||
return new VmDuration(newValue, left.getUnit());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDuration eval(VmDuration left, double right) {
|
||||
var newValue = doTruncatingDivide(left.getValue(), right);
|
||||
return new VmDuration(newValue, left.getUnit());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(VmDuration left, VmDuration right) {
|
||||
// use same conversion strategy as add/subtract
|
||||
if (left.getUnit().ordinal() <= right.getUnit().ordinal()) {
|
||||
return doTruncatingDivide(left.getValue(right.getUnit()), right.getValue());
|
||||
}
|
||||
return doTruncatingDivide(left.getValue(), right.getValue(left.getUnit()));
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, long right) {
|
||||
var value = doTruncatingDivide(left.getValue(), right);
|
||||
return new VmDataSize(value, left.getUnit());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected VmDataSize eval(VmDataSize left, double right) {
|
||||
var newValue = doTruncatingDivide(left.getValue(), right);
|
||||
return new VmDataSize(newValue, left.getUnit());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected long eval(VmDataSize left, VmDataSize right) {
|
||||
// use same conversion strategy as add/subtract
|
||||
if (left.getUnit().ordinal() <= right.getUnit().ordinal()) {
|
||||
var leftValue = left.convertTo(right.getUnit()).getValue();
|
||||
return doTruncatingDivide(leftValue, right.getValue());
|
||||
}
|
||||
var rightValue = right.convertTo(left.getUnit()).getValue();
|
||||
return doTruncatingDivide(left.getValue(), rightValue);
|
||||
}
|
||||
|
||||
private long doTruncatingDivide(double x, double y) {
|
||||
try {
|
||||
return MathUtils.roundToLong(x / y, RoundingMode.DOWN);
|
||||
} catch (ArithmeticException e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder()
|
||||
.evalError(
|
||||
Double.isFinite(x) ? "cannotConvertLargeFloat" : "cannotConvertNonFiniteFloat",
|
||||
new ProgramValue("Float", x))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.core.ast.expression.binary;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright © 2024 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.core.ast.expression.generator;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.ImportStatic;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.BaseModule;
|
||||
import org.pkl.core.runtime.VmClass;
|
||||
import org.pkl.core.runtime.VmDynamic;
|
||||
import org.pkl.core.runtime.VmListing;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
|
||||
@ImportStatic(BaseModule.class)
|
||||
public abstract class GeneratorElementNode extends GeneratorMemberNode {
|
||||
private final ObjectMember element;
|
||||
|
||||
protected GeneratorElementNode(ObjectMember element) {
|
||||
super(element.getSourceSection());
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@SuppressWarnings("unused")
|
||||
protected void evalDynamic(VmDynamic parent, ObjectData data) {
|
||||
addElement(data);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@SuppressWarnings("unused")
|
||||
protected void evalListing(VmListing parent, ObjectData data) {
|
||||
addElement(data);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Specialization(guards = "parent == getDynamicClass()")
|
||||
protected void evalDynamicClass(VmClass parent, ObjectData data) {
|
||||
addElement(data);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Specialization(guards = "parent == getListingClass()")
|
||||
protected void evalListingClass(VmClass parent, ObjectData data) {
|
||||
addElement(data);
|
||||
}
|
||||
|
||||
@Fallback
|
||||
@SuppressWarnings("unused")
|
||||
void fallback(Object parent, ObjectData data) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().evalError("objectCannotHaveElement", parent).build();
|
||||
}
|
||||
|
||||
private void addElement(ObjectData data) {
|
||||
long index = data.length;
|
||||
EconomicMaps.put(data.members, index, element);
|
||||
data.length += 1;
|
||||
data.persistForBindings(index);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user