com.oracle.truffle.js.builtins.StringFunctionBuiltins Maven / Gradle / Ivy
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.js.builtins;
import java.util.Arrays;
import java.util.Map;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringBuilder;
import com.oracle.truffle.api.strings.TruffleStringBuilderUTF16;
import com.oracle.truffle.api.strings.TruffleStringIterator;
import com.oracle.truffle.js.builtins.StringFunctionBuiltinsFactory.DedentTemplateStringsArrayNodeGen;
import com.oracle.truffle.js.builtins.StringFunctionBuiltinsFactory.JSFromCharCodeNodeGen;
import com.oracle.truffle.js.builtins.StringFunctionBuiltinsFactory.JSFromCodePointNodeGen;
import com.oracle.truffle.js.builtins.StringFunctionBuiltinsFactory.StringDedentNodeGen;
import com.oracle.truffle.js.builtins.StringFunctionBuiltinsFactory.StringRawNodeGen;
import com.oracle.truffle.js.builtins.helper.JSCollectionsNormalizeNode;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.access.IsObjectNode;
import com.oracle.truffle.js.nodes.access.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.PropertySetNode;
import com.oracle.truffle.js.nodes.access.ReadElementNode;
import com.oracle.truffle.js.nodes.array.JSGetLengthNode;
import com.oracle.truffle.js.nodes.cast.JSToNumberNode;
import com.oracle.truffle.js.nodes.cast.JSToObjectNode;
import com.oracle.truffle.js.nodes.cast.JSToStringNode;
import com.oracle.truffle.js.nodes.cast.JSToUInt16Node;
import com.oracle.truffle.js.nodes.function.JSBuiltin;
import com.oracle.truffle.js.nodes.function.JSBuiltinNode;
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode;
import com.oracle.truffle.js.nodes.unary.IsCallableNode;
import com.oracle.truffle.js.runtime.Boundaries;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.JavaScriptRootNode;
import com.oracle.truffle.js.runtime.Strings;
import com.oracle.truffle.js.runtime.builtins.BuiltinEnum;
import com.oracle.truffle.js.runtime.builtins.JSArray;
import com.oracle.truffle.js.runtime.builtins.JSArrayObject;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSFunctionObject;
import com.oracle.truffle.js.runtime.builtins.JSString;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.PropertyDescriptor;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.SimpleArrayList;
/**
* Contains builtins for {@linkplain JSString} function (constructor).
*/
public final class StringFunctionBuiltins extends JSBuiltinsContainer.SwitchEnum {
public static final JSBuiltinsContainer BUILTINS = new StringFunctionBuiltins();
protected StringFunctionBuiltins() {
super(JSString.CLASS_NAME, StringFunction.class);
}
public enum StringFunction implements BuiltinEnum {
fromCharCode(1),
// ES6
fromCodePoint(1),
raw(1),
// staging
dedent(1);
private final int length;
StringFunction(int length) {
this.length = length;
}
@Override
public int getLength() {
return length;
}
@Override
public int getECMAScriptVersion() {
return switch (this) {
case fromCodePoint -> JSConfig.ECMAScript2015;
case dedent -> JSConfig.StagingECMAScriptVersion;
default -> BuiltinEnum.super.getECMAScriptVersion();
};
}
}
@Override
protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, StringFunction builtinEnum) {
switch (builtinEnum) {
case fromCharCode:
return JSFromCharCodeNodeGen.create(context, builtin, args().varArgs().createArgumentNodes(context));
case fromCodePoint:
return JSFromCodePointNodeGen.create(context, builtin, args().varArgs().createArgumentNodes(context));
case raw:
return StringRawNodeGen.create(context, builtin, args().fixedArgs(1).varArgs().createArgumentNodes(context));
case dedent:
return StringDedentNodeGen.create(context, builtin, args().fixedArgs(1).varArgs().createArgumentNodes(context));
}
return null;
}
public abstract static class JSFromCharCodeNode extends JSBuiltinNode {
public JSFromCharCodeNode(JSContext context, JSBuiltin builtin) {
super(context, builtin);
}
@Specialization(guards = "args.length == 0")
protected Object fromCharCode(@SuppressWarnings("unused") Object[] args) {
return Strings.EMPTY_STRING;
}
@Specialization(guards = "args.length == 1")
protected Object fromCharCodeOneArg(Object[] args,
@Shared @Cached JSToUInt16Node toUint16,
@Cached TruffleString.FromCodePointNode fromCodePointNode) {
return Strings.fromCodePoint(fromCodePointNode, toUint16.executeChar(args[0]));
}
@Specialization(guards = "args.length >= 2")
protected Object fromCharCodeTwoOrMore(Object[] args,
@Shared @Cached JSToUInt16Node toUint16,
@Cached TruffleString.FromCharArrayUTF16Node fromCharArrayNode) {
char[] chars = new char[args.length];
for (int i = 0; i < args.length; i++) {
chars[i] = toUint16.executeChar(args[i]);
}
return Strings.fromCharArray(fromCharArrayNode, chars);
}
}
public abstract static class JSFromCodePointNode extends JSBuiltinNode {
public JSFromCodePointNode(JSContext context, JSBuiltin builtin) {
super(context, builtin);
}
@Specialization
protected Object fromCodePoint(Object[] args,
@Cached JSToNumberNode toNumberNode,
@Cached TruffleString.FromCodePointNode fromCodePointNode,
@Cached TruffleString.ConcatNode concatNode) {
TruffleString st = Strings.EMPTY_STRING;
for (Object arg : args) {
Number value = toNumberNode.executeNumber(arg);
double valueDouble = JSRuntime.doubleValue(value);
int valueInt = JSRuntime.intValue(value);
if (JSRuntime.isNegativeZero(valueDouble)) {
valueInt = 0;
} else if (!JSRuntime.doubleIsRepresentableAsInt(valueDouble) || (valueInt < 0) || (0x10FFFF < valueInt)) {
throwRangeError(value);
}
st = Strings.concat(concatNode, st, Strings.fromCodePoint(fromCodePointNode, valueInt));
}
return st;
}
@TruffleBoundary
private static void throwRangeError(Number value) {
throw Errors.createRangeError("Invalid code point " + value);
}
}
public abstract static class StringRawNode extends JSBuiltinNode {
@Child private JSToObjectNode templateToObjectNode;
@Child private JSToObjectNode rawToObjectNode;
@Child private PropertyGetNode getRawNode;
@Child private JSGetLengthNode getRawLengthNode;
@Child private JSToStringNode segToStringNode;
@Child private JSToStringNode subToStringNode;
@Child private ReadElementNode readRawElementNode;
@Child private TruffleStringBuilder.AppendStringNode appendStringNode;
@Child private TruffleStringBuilder.ToStringNode builderToStringNode;
public StringRawNode(JSContext context, JSBuiltin builtin) {
super(context, builtin);
this.templateToObjectNode = JSToObjectNode.create();
this.rawToObjectNode = JSToObjectNode.create();
this.getRawNode = PropertyGetNode.create(Strings.RAW, false, context);
this.getRawLengthNode = JSGetLengthNode.create(context);
this.segToStringNode = JSToStringNode.create();
this.subToStringNode = JSToStringNode.create();
this.readRawElementNode = ReadElementNode.create(context);
this.appendStringNode = TruffleStringBuilder.AppendStringNode.create();
this.builderToStringNode = TruffleStringBuilder.ToStringNode.create();
}
@Specialization
protected Object raw(Object template, Object[] substitutions,
@Cached InlinedConditionProfile emptyProf) {
int numberOfSubstitutions = substitutions.length;
Object cooked = templateToObjectNode.execute(template);
Object raw = rawToObjectNode.execute(getRawNode.getValue(cooked));
int literalSegments = getRawLength(raw);
if (emptyProf.profile(this, literalSegments <= 0)) {
return Strings.EMPTY_STRING;
}
var result = Strings.builderCreate();
for (int i = 0;; i++) {
Object rawElement = readRawElementNode.executeWithTargetAndIndex(raw, i);
TruffleString nextSeg = segToStringNode.executeString(rawElement);
appendChecked(result, nextSeg);
if (i + 1 == literalSegments) {
break;
}
if (i < numberOfSubstitutions) {
TruffleString nextSub = subToStringNode.executeString(substitutions[i]);
appendChecked(result, nextSub);
}
}
return Strings.builderToString(builderToStringNode, result);
}
private int getRawLength(Object raw) {
long length = getRawLengthNode.executeLong(raw);
try {
return Math.toIntExact(length);
} catch (ArithmeticException e) {
return 0;
}
}
private void appendChecked(TruffleStringBuilderUTF16 result, TruffleString str) {
if (Strings.builderLength(result) + Strings.length(str) > getContext().getStringLengthLimit()) {
CompilerDirectives.transferToInterpreter();
throw Errors.createRangeErrorInvalidStringLength();
}
Strings.builderAppend(appendStringNode, result, str);
}
}
@ImportStatic(StringDedentNode.class)
public abstract static class StringDedentNode extends JSBuiltinNode {
static final HiddenKey TAG_KEY = new HiddenKey("TagKey");
public StringDedentNode(JSContext context, JSBuiltin builtin) {
super(context, builtin);
}
@Specialization(guards = {"isObject.executeBoolean(callback)", "isCallable.executeBoolean(callback)"})
protected Object dedentCallback(Object callback, @SuppressWarnings("unused") Object[] substitutions,
@Cached @Shared @SuppressWarnings("unused") IsCallableNode isCallable,
@Cached @Shared @SuppressWarnings("unused") IsObjectNode isObject,
@Cached("createSetHidden(TAG_KEY, getContext())") PropertySetNode setArgs) {
JSFunctionData functionData = getContext().getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.DedentCallback, (c) -> callbackBody(c));
JSFunctionObject function = JSFunction.create(getRealm(), functionData);
setArgs.setValue(function, callback);
return function;
}
private static JSFunctionData callbackBody(JSContext context) {
class CallbackBody extends JavaScriptRootNode {
@Child private DedentTemplateStringsArrayNode dedentTemplateStringsArray = DedentTemplateStringsArrayNodeGen.create(context);
@Child private PropertyGetNode getTag = PropertyGetNode.createGetHidden(TAG_KEY, context);
@Child private JSFunctionCallNode callResolve = JSFunctionCallNode.createCall();
@Child private IsObjectNode isObject = IsObjectNode.create();
@Override
public Object execute(VirtualFrame frame) {
JSDynamicObject functionObject = JSFrameUtil.getFunctionObject(frame);
Object tag = getTag.getValue(functionObject);
Object r = JSFrameUtil.getThisObj(frame);
Object[] args = JSFrameUtil.getArgumentsArray(frame);
if (args.length < 1) {
throw Errors.createTypeError("Expected at least one argument");
}
Object template = args[0];
if (!isObject.executeBoolean(template)) {
throw Errors.createTypeErrorNotAnObject(template);
}
JSArrayObject dedentedArray = dedentTemplateStringsArray.execute(template, context);
Object[] callbackArgs = Arrays.copyOf(args, args.length);
callbackArgs[0] = dedentedArray;
return callResolve.executeCall(JSArguments.create(r, tag, callbackArgs));
}
}
return JSFunctionData.createCallOnly(context, new CallbackBody().getCallTarget(), 2, Strings.EMPTY_STRING);
}
@Specialization(guards = {"isObject.executeBoolean(template)", "!isCallable.executeBoolean(template)"})
protected static Object dedentTemplate(Object template, Object[] substitutions,
@Bind("this") Node self,
@Bind("getContext()") JSContext context,
@Cached @Shared @SuppressWarnings("unused") IsCallableNode isCallable,
@Cached @Shared @SuppressWarnings("unused") IsObjectNode isObject,
@Cached("create(getContext())") DedentTemplateStringsArrayNode dedentTemplateStringsArray,
@Cached("create(getContext())") ReadElementNode readElementNode,
@Cached JSToStringNode segToStringNode,
@Cached JSToStringNode subToStringNode,
@Cached InlinedBranchProfile errorBranch,
@Cached TruffleStringBuilder.AppendStringNode appendStringNode,
@Cached TruffleStringBuilder.ToStringNode builderToStringNode) {
int stringLengthLimit = context.getStringLengthLimit();
JSArrayObject dedentedArray = dedentTemplateStringsArray.execute(template, context);
// CookTemplateStringsArray (prep = cooked)
int substitutionCount = substitutions.length;
long literalCount = JSArray.arrayGetLength(dedentedArray);
if (literalCount <= 0) {
return Strings.EMPTY_STRING;
}
var result = Strings.builderCreate();
for (int i = 0; i < literalCount; i++) {
TruffleString nextSeg = segToStringNode.executeString(readElementNode.executeWithTargetAndIndex(dedentedArray, i));
appendChecked(result, nextSeg, stringLengthLimit,
self, errorBranch, appendStringNode);
if (i + 1 == literalCount) {
break;
}
if (i < substitutionCount) {
TruffleString nextSub = subToStringNode.executeString(substitutions[i]);
appendChecked(result, nextSub, stringLengthLimit,
self, errorBranch, appendStringNode);
}
}
return Strings.builderToString(builderToStringNode, result);
}
private static void appendChecked(TruffleStringBuilderUTF16 result, TruffleString str, int stringLengthLimit,
Node self,
InlinedBranchProfile errorBranch,
TruffleStringBuilder.AppendStringNode appendStringNode) {
if (Strings.builderLength(result) + Strings.length(str) > stringLengthLimit) {
errorBranch.enter(self);
throw Errors.createRangeErrorInvalidStringLength();
}
Strings.builderAppend(appendStringNode, result, str);
}
@TruffleBoundary(transferToInterpreterOnException = false)
@Fallback
protected static Object notAnObject(Object template, @SuppressWarnings("unused") Object substitutions) {
throw Errors.createTypeErrorNotAnObject(template);
}
}
public abstract static class DedentTemplateStringsArrayNode extends JavaScriptBaseNode {
@Child private PropertyGetNode getRawNode;
@Child private JSGetLengthNode getLengthNode;
@Child private ReadElementNode readRawElementNode;
@Child private TruffleStringBuilder.AppendStringNode appendStringNode;
@Child private TruffleStringBuilder.AppendSubstringByteIndexNode appendSubstringNode;
@Child private TruffleStringBuilder.AppendCharUTF16Node appendCharNode;
@Child private TruffleStringBuilder.AppendCodePointNode appendCodePointNode;
@Child private TruffleStringBuilder.ToStringNode builderToStringNode;
@Child private TruffleString.ReadCharUTF16Node readCharNode;
@Child private TruffleString.SubstringByteIndexNode substringNode;
@Child private TruffleStringIterator.NextNode iteratorNextNode;
@Child private TruffleStringIterator.PreviousNode iteratorPreviousNode;
DedentTemplateStringsArrayNode(JSContext context) {
this.getLengthNode = JSGetLengthNode.create(context);
this.getRawNode = PropertyGetNode.create(Strings.RAW, context);
this.readRawElementNode = ReadElementNode.create(context);
this.appendStringNode = TruffleStringBuilder.AppendStringNode.create();
this.appendSubstringNode = TruffleStringBuilder.AppendSubstringByteIndexNode.create();
this.appendCharNode = TruffleStringBuilder.AppendCharUTF16Node.create();
this.appendCodePointNode = TruffleStringBuilder.AppendCodePointNode.create();
this.builderToStringNode = TruffleStringBuilder.ToStringNode.create();
this.readCharNode = TruffleString.ReadCharUTF16Node.create();
this.substringNode = TruffleString.SubstringByteIndexNode.create();
this.iteratorNextNode = TruffleStringIterator.NextNode.create();
this.iteratorPreviousNode = TruffleStringIterator.PreviousNode.create();
}
protected abstract JSArrayObject execute(Object template, JSContext context);
@Specialization
protected final JSArrayObject dedentTemplateStringsArray(Object template, JSContext context,
@Cached JSToObjectNode rawToObjectNode,
@Cached InlinedConditionProfile emptyProf,
@Cached InlinedBranchProfile errorBranch,
@Cached InlinedBranchProfile growBranch,
@Cached TruffleString.CreateCodePointIteratorNode createCodePointIterator,
@Cached JSCollectionsNormalizeNode collectionsNormalize) {
Object rawInput = collectionsNormalize.execute(getRawNode.getValue(template));
JSRealm realm = getRealm();
Map
© 2015 - 2025 Weber Informatics LLC | Privacy Policy