All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.smithy.model.loader.IdlNodeParser Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. 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.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.model.loader;

import java.util.function.Consumer;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.node.ArrayNode;
import software.amazon.smithy.model.node.BooleanNode;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NullNode;
import software.amazon.smithy.model.node.NumberNode;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.StringNode;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.utils.Pair;

/**
 * Parses Node values from a {@link IdlInternalTokenizer}.
 */
final class IdlNodeParser {

    private static final String SYNTACTIC_SHAPE_ID_TARGET = "SyntacticShapeIdTarget";

    private IdlNodeParser() { }

    /**
     * Expects that the current token is a valid Node, and parses it into a {@link Node} value.
     *
     * 

The tokenizer is advanced to the next token after parsing the Node value.

* * @param loader IDL parser. * @return Returns the parsed node value. * @throws ModelSyntaxException if the Node is not well-formed. */ static Node expectAndSkipNode(IdlModelLoader loader) { return expectAndSkipNode(loader, loader.getTokenizer().getCurrentTokenLocation()); } /** * Expects that the current token is a valid Node, parses it into a {@link Node} value, and assigns it a custom * {@link SourceLocation}. * *

The tokenizer is advanced to the next token after parsing the Node value.

* * @param loader IDL loader. * @param location Source location to assign to the node. * @return Returns the parsed node value. * @throws ModelSyntaxException if the Node is not well-formed. */ static Node expectAndSkipNode(IdlModelLoader loader, SourceLocation location) { IdlInternalTokenizer tokenizer = loader.getTokenizer(); IdlToken token = tokenizer.expect(IdlToken.STRING, IdlToken.TEXT_BLOCK, IdlToken.NUMBER, IdlToken.IDENTIFIER, IdlToken.LBRACE, IdlToken.LBRACKET); switch (token) { case STRING: case TEXT_BLOCK: Node result = new StringNode(tokenizer.getCurrentTokenStringSlice().toString(), location); tokenizer.next(); return result; case IDENTIFIER: String shapeId = loader.internString(IdlShapeIdParser.expectAndSkipShapeId(tokenizer)); return createIdentifier(loader, shapeId, location); case NUMBER: Number number = tokenizer.getCurrentTokenNumberValue(); tokenizer.next(); return new NumberNode(number, location); case LBRACE: return parseObjectNode(loader, location); case LBRACKET: default: return parseArrayNode(loader, location); } } /** * Parse a Node identifier String, taking into account keywords and forward references. * * @param loader IDL parser. * @param identifier Identifier to parse. * @param location Source location to assign to the identifier. * @return Returns the parsed identifier. */ static Node createIdentifier(IdlModelLoader loader, String identifier, SourceLocation location) { Keyword keyword = Keyword.from(identifier); return keyword == null ? createSyntacticShapeId(loader, identifier, location) : keyword.createNode(location); } private enum Keyword { TRUE { @Override protected Node createNode(SourceLocation location) { return new BooleanNode(true, location); } }, FALSE { @Override protected Node createNode(SourceLocation location) { return new BooleanNode(false, location); } }, NULL { @Override protected Node createNode(SourceLocation location) { return new NullNode(location); } }; protected abstract Node createNode(SourceLocation location); static Keyword from(String keyword) { switch (keyword) { case "true": return Keyword.TRUE; case "false": return Keyword.FALSE; case "null": return Keyword.NULL; default: return null; } } } private static Node createSyntacticShapeId( IdlModelLoader loader, String identifier, SourceLocation location ) { // Unquoted node values syntactically are assumed to be references to shapes. A lazy string node is // used because the shape ID may not be able to be resolved until after the entire model is loaded. Pair> pair = StringNode.createLazyString(identifier, location); Consumer consumer = pair.right; loader.addForwardReference(identifier, (id, type) -> { consumer.accept(id.toString()); if (type != null) { return null; } else { return ValidationEvent.builder() .id(SYNTACTIC_SHAPE_ID_TARGET) .severity(Severity.DANGER) .message(String.format("Syntactic shape ID `%s` does not resolve to a valid shape ID: " + "`%s`. Did you mean to quote this string? Are you missing a " + "model file?", identifier, id)) .sourceLocation(location) .build(); } }); return pair.left; } private static ArrayNode parseArrayNode(IdlModelLoader loader, SourceLocation location) { loader.increaseNestingLevel(); ArrayNode.Builder builder = ArrayNode.builder().sourceLocation(location); IdlInternalTokenizer tokenizer = loader.getTokenizer(); tokenizer.expect(IdlToken.LBRACKET); tokenizer.next(); tokenizer.skipWsAndDocs(); do { if (tokenizer.getCurrentToken() == IdlToken.RBRACKET) { break; } else { builder.withValue(expectAndSkipNode(loader)); tokenizer.skipWsAndDocs(); } } while (true); tokenizer.expect(IdlToken.RBRACKET); tokenizer.next(); loader.decreaseNestingLevel(); return builder.build(); } private static ObjectNode parseObjectNode(IdlModelLoader loader, SourceLocation location) { IdlInternalTokenizer tokenizer = loader.getTokenizer(); tokenizer.expect(IdlToken.LBRACE); tokenizer.next(); tokenizer.skipWsAndDocs(); loader.increaseNestingLevel(); ObjectNode.Builder builder = ObjectNode.builder().sourceLocation(location); while (tokenizer.hasNext()) { if (tokenizer.expect(IdlToken.RBRACE, IdlToken.STRING, IdlToken.IDENTIFIER) == IdlToken.RBRACE) { break; } String key = loader.internString(tokenizer.getCurrentTokenStringSlice()); SourceLocation keyLocation = tokenizer.getCurrentTokenLocation(); tokenizer.next(); tokenizer.skipWsAndDocs(); tokenizer.expect(IdlToken.COLON); tokenizer.next(); tokenizer.skipWsAndDocs(); Node value = expectAndSkipNode(loader); if (builder.hasMember(key)) { throw new ModelSyntaxException("Duplicate member: '" + key + '\'', keyLocation); } builder.withMember(key, value); tokenizer.skipWsAndDocs(); } tokenizer.expect(IdlToken.RBRACE); tokenizer.next(); loader.decreaseNestingLevel(); return builder.build(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy