diff --git a/ide/languages.hcl/manifest.mf b/ide/languages.hcl/manifest.mf index 5fa00afe70e6..8365e30f581d 100644 --- a/ide/languages.hcl/manifest.mf +++ b/ide/languages.hcl/manifest.mf @@ -3,4 +3,5 @@ OpenIDE-Module: org.netbeans.modules.languages.hcl OpenIDE-Module-Layer: org/netbeans/modules/languages/hcl/layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/languages/hcl/Bundle.properties OpenIDE-Module-Specification-Version: 1.2 +OpenIDE-Module-Java-Dependencies: Java > 11 AutoUpdate-Show-In-Client: true diff --git a/ide/languages.hcl/nbproject/project.properties b/ide/languages.hcl/nbproject/project.properties index 7a05d07a18bd..5163ac1bd4e5 100644 --- a/ide/languages.hcl/nbproject/project.properties +++ b/ide/languages.hcl/nbproject/project.properties @@ -14,5 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -javac.source=1.8 +javac.source=11 +javac.target=11 diff --git a/ide/languages.hcl/nbproject/project.xml b/ide/languages.hcl/nbproject/project.xml index 69722385ef62..0541d1f6d5a5 100644 --- a/ide/languages.hcl/nbproject/project.xml +++ b/ide/languages.hcl/nbproject/project.xml @@ -43,6 +43,15 @@ 1.20 + + org.netbeans.libs.json_simple + + + + 1 + 0.33 + + org.netbeans.modules.csl.api diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLLanguage.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLLanguage.java index ba33dfd2f175..0c978bce85a2 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLLanguage.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLLanguage.java @@ -31,6 +31,7 @@ import org.netbeans.api.lexer.Token; import org.netbeans.core.spi.multiview.MultiViewElement; import org.netbeans.core.spi.multiview.text.MultiViewEditorElement; +import org.netbeans.modules.csl.api.SemanticAnalyzer; import org.netbeans.modules.csl.api.StructureScanner; import org.netbeans.modules.csl.spi.DefaultLanguageConfig; import org.netbeans.modules.csl.spi.LanguageRegistration; @@ -152,6 +153,11 @@ public Parser getParser() { return new NbHCLParser(HCLParserResult::new); } + @Override + public SemanticAnalyzer getSemanticAnalyzer() { + return new HCLSemanticAnalyzer(); + } + @Override public boolean hasStructureScanner() { return true; diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLParserResult.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLParserResult.java index 33a054fda0b1..2746a32863d5 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLParserResult.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLParserResult.java @@ -38,9 +38,9 @@ import org.netbeans.modules.csl.api.Severity; import org.netbeans.modules.csl.spi.DefaultError; import org.netbeans.modules.csl.spi.ParserResult; -import org.netbeans.modules.languages.hcl.ast.ASTBuilderListener; +import org.netbeans.modules.languages.hcl.ast.HCLBlockFactory; import org.netbeans.modules.languages.hcl.ast.HCLDocument; -import org.netbeans.modules.languages.hcl.ast.SourceRef; +import org.netbeans.modules.languages.hcl.ast.HCLElement; import org.netbeans.modules.languages.hcl.grammar.HCLLexer; import org.netbeans.modules.languages.hcl.grammar.HCLParser; import org.netbeans.modules.languages.hcl.grammar.HCLParserBaseListener; @@ -60,10 +60,11 @@ public class HCLParserResult extends ParserResult { public final Map> folds = new HashMap<>(); private HCLDocument document; - private SourceRef references; + private final SourceRef references; public HCLParserResult(Snapshot snapshot) { super(snapshot); + references = new SourceRef(snapshot); } protected final FileObject getFileObject() { @@ -82,7 +83,8 @@ public void compute() { configureParser(parser); - parser.configFile(); + HCLBlockFactory bf = new HCLBlockFactory(references::elementCreated); + document = bf.process(parser.configFile()); lexer.reset(); } @@ -135,17 +137,20 @@ protected void configureParser(HCLParser parser) { parser.addErrorListener(createErrorListener()); parser.addParseListener(createFoldListener()); - - ASTBuilderListener astListener = new ASTBuilderListener(getSnapshot()); - parser.addParseListener(astListener); - - document = astListener.getDocument(); - references = astListener.getReferences(); } protected void processDocument(HCLDocument doc, SourceRef references) { } + protected void addError(HCLElement e, String message) { + references.getOffsetRange(e).ifPresent((range) -> addError(message, range)); + } + + private void addError(String message, OffsetRange range) { + DefaultError error = new DefaultError(null, message, null, getFileObject(), range.getStart() , range.getEnd(), false, Severity.ERROR); + errors.add(error); + } + private void addFold(FoldType ft, Token token) { if (token.getText().contains("\n") && (token.getStartIndex() < token.getStopIndex())) { List foldBag = folds.computeIfAbsent(ft.code(), (t) -> new ArrayList<>()); @@ -175,12 +180,14 @@ private ANTLRErrorListener createErrorListener() { return new BaseErrorListener() { @Override public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { - int errorPosition = 0; + int errorStart = 0; + int errorEnd = 0; if (offendingSymbol instanceof Token) { Token offendingToken = (Token) offendingSymbol; - errorPosition = offendingToken.getStartIndex(); + errorStart = offendingToken.getStartIndex(); + errorEnd = offendingToken.getStopIndex() + 1; } - errors.add(new DefaultError(null, msg, null, getFileObject(), errorPosition, errorPosition, Severity.ERROR)); + errors.add(new DefaultError(null, msg, null, getFileObject(), errorStart, errorEnd, errorStart == errorEnd, Severity.ERROR)); } }; @@ -190,7 +197,7 @@ private ParseTreeListener createFoldListener() { return new HCLParserBaseListener() { @Override - public void exitHeredocTemplate(HCLParser.HeredocTemplateContext ctx) { + public void exitHeredoc(HCLParser.HeredocContext ctx) { addFold(HCLLanguage.HCLFold.HEREDOC, ctx.HEREDOC_START(), ctx.HEREDOC_END()); } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLSemanticAnalyzer.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLSemanticAnalyzer.java new file mode 100644 index 000000000000..86581df92b7b --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLSemanticAnalyzer.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.netbeans.modules.csl.api.ColoringAttributes; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.SemanticAnalyzer; +import org.netbeans.modules.languages.hcl.ast.HCLAttribute; +import org.netbeans.modules.languages.hcl.ast.HCLBlock; +import org.netbeans.modules.languages.hcl.ast.HCLCollection; +import org.netbeans.modules.languages.hcl.ast.HCLDocument; +import org.netbeans.modules.languages.hcl.ast.HCLElement; +import org.netbeans.modules.languages.hcl.ast.HCLExpression; +import org.netbeans.modules.languages.hcl.ast.HCLFunction; +import org.netbeans.modules.languages.hcl.ast.HCLIdentifier; +import org.netbeans.modules.languages.hcl.ast.HCLVariable; +import org.netbeans.modules.parsing.spi.Scheduler; +import org.netbeans.modules.parsing.spi.SchedulerEvent; + +/** + * + * @author lkishalmi + */ +public class HCLSemanticAnalyzer extends SemanticAnalyzer { + private volatile boolean cancelled; + private Map> highlights = Collections.emptyMap(); + + @Override + public Map> getHighlights() { + return highlights; + } + + protected final synchronized boolean isCancelled() { + return cancelled; + } + + protected final synchronized void resume() { + cancelled = false; + } + + @Override + public final void run(HCLParserResult result, SchedulerEvent event) { + resume(); + + Highlighter h = createHighlighter(result); + result.getDocument().accept(h); + highlights = h.work; + } + + protected Highlighter createHighlighter(HCLParserResult result) { + return new DefaultHighlighter(result.getReferences()); + } + + @Override + public int getPriority() { + return 0; + } + + @Override + public Class getSchedulerClass() { + return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER; + } + + @Override + public void cancel() { + cancelled = true; + } + + protected abstract class Highlighter extends HCLElement.BAEVisitor { + protected final Map> work = new HashMap<>(); + + protected final SourceRef refs; + protected Highlighter(SourceRef refs) { + this.refs = refs; + } + + @Override + public final boolean visit(HCLElement e) { + if (isCancelled()) { + return true; + } + return super.visit(e); + } + + protected final void mark(HCLElement e, Set attrs) { + refs.getOffsetRange(e).ifPresent((range) -> work.put(range, attrs)); + } + } + + protected class DefaultHighlighter extends Highlighter { + + public DefaultHighlighter(SourceRef refs) { + super(refs); + } + + @Override + protected boolean visitBlock(HCLBlock block) { + if (block.getParent() instanceof HCLDocument) { + List decl = block.getDeclaration(); + HCLIdentifier type = decl.get(0); + + mark(type, ColoringAttributes.CLASS_SET); + if (decl.size() > 1) { + for (int i = 1; i < decl.size(); i++) { + HCLIdentifier id = decl.get(i); + mark(id, ColoringAttributes.CONSTRUCTOR_SET); + } + } + } else { + //TODO: Handle nested Blocks... + } + return false; + } + + @Override + protected boolean visitAttribute(HCLAttribute attr) { + mark(attr.getName(), ColoringAttributes.FIELD_SET); + return false; + } + + @Override + protected boolean visitExpression(HCLExpression expr) { + if (expr instanceof HCLFunction) { + HCLFunction func = (HCLFunction) expr; + mark(func.getName(), ColoringAttributes.CONSTRUCTOR_SET); + } + + if (expr instanceof HCLCollection.Object) { + HCLCollection.Object obj = (HCLCollection.Object) expr; + for (HCLExpression key : obj.getKeys()) { + if (key instanceof HCLVariable) { + mark(key, ColoringAttributes.FIELD_SET); + } + } + } + return false; + } + + } +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLStructureItem.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLStructureItem.java index bef98358547b..6c8b845feff6 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLStructureItem.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/HCLStructureItem.java @@ -30,11 +30,10 @@ import org.netbeans.modules.csl.api.OffsetRange; import org.netbeans.modules.csl.api.StructureItem; import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.languages.hcl.ast.HCLAddressableElement; import org.netbeans.modules.languages.hcl.ast.HCLAttribute; import org.netbeans.modules.languages.hcl.ast.HCLBlock; import org.netbeans.modules.languages.hcl.ast.HCLContainer; -import org.netbeans.modules.languages.hcl.ast.HCLElement; -import org.netbeans.modules.languages.hcl.ast.SourceRef; import org.openide.filesystems.FileObject; /** @@ -43,11 +42,11 @@ */ public class HCLStructureItem implements ElementHandle, StructureItem { - final HCLElement element; + final HCLAddressableElement element; final SourceRef references; private List nestedCache; - public HCLStructureItem(HCLElement element, SourceRef references) { + public HCLStructureItem(HCLAddressableElement element, SourceRef references) { this.element = element; this.references = references; } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/SourceRef.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/SourceRef.java similarity index 51% rename from ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/SourceRef.java rename to ide/languages.hcl/src/org/netbeans/modules/languages/hcl/SourceRef.java index a4901516c1e3..e83ba786e5ee 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/SourceRef.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/SourceRef.java @@ -16,16 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -package org.netbeans.modules.languages.hcl.ast; +package org.netbeans.modules.languages.hcl; -import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.TreeMap; import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.languages.hcl.ast.HCLElement; import org.netbeans.modules.parsing.api.Snapshot; import org.openide.filesystems.FileObject; @@ -36,30 +38,50 @@ public class SourceRef { public final Snapshot source; private Map elementOffsets = new HashMap<>(); - private final Comparator sourceOrder = Comparator.comparing((HCLElement e) -> elementOffsets.get(e)); + private TreeMap elementAt = new TreeMap<>((o1, o2) -> o1.getStart() != o2.getStart() ? o1.getStart() - o2.getStart() : o2.getEnd() - o1.getEnd()); + public SourceRef(Snapshot source) { this.source = source; } - void add(HCLElement e, OffsetRange r) { - elementOffsets.put(e, r); + private void add(HCLElement e, OffsetRange r) { + if (!elementOffsets.containsKey(e)) { + // Only add the most specific range for the expression. + elementOffsets.put(e, r); + elementAt.put(r, e); + } } - void add(HCLElement e, int startOffset, int endOffset) { - add(e, new OffsetRange(startOffset, endOffset)); + void elementCreated(HCLElement.CreateContext ctx) { + add(ctx.element, new OffsetRange(ctx.start.getStartIndex(), ctx.stop.getStopIndex() + 1)); } public FileObject getFileObject() { return source.getSource().getFileObject(); } + public Optional getOffsetRange(HCLElement e) { return Optional.ofNullable(elementOffsets.get(e)); } - - List sortBySource(List elements) { - List ret = new ArrayList<>(elements); - Collections.sort(ret, sourceOrder); - return ret; + + public List elementsAt(int index) { + if (index < 0) { + return Collections.emptyList(); + } + List ret = new LinkedList<>(); + Iterator> it = elementAt.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry e = it.next(); + OffsetRange or = e.getKey(); + if (or.getStart() > index) { + break; + } + if (or.getEnd() <= index) { + continue; + } + ret.add(e.getValue()); + } + return ret.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(ret); } } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/ASTBuilderListener.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/ASTBuilderListener.java deleted file mode 100644 index 5fb1fd14e262..000000000000 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/ASTBuilderListener.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 - * - * http://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.netbeans.modules.languages.hcl.ast; - -import java.util.ArrayList; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.netbeans.modules.languages.hcl.grammar.HCLParser; -import org.netbeans.modules.languages.hcl.grammar.HCLParserBaseListener; -import org.netbeans.modules.parsing.api.Snapshot; - -/** - * - * @author Laszlo Kishalmi - */ -public class ASTBuilderListener extends HCLParserBaseListener { - - final HCLDocument document = new HCLDocument(); - final SourceRef references; - - private HCLContainer current = document; - - public ASTBuilderListener(Snapshot source) { - this.references = new SourceRef(source); - } - - public HCLDocument getDocument() { - return document; - } - - public SourceRef getReferences() { - return references; - } - - - private void addReference(HCLElement e, Token token) { - references.add(e, token.getStartIndex(), token.getStopIndex() + 1); - } - - private void addReference(HCLElement e, ParserRuleContext ctx) { - - references.add(e, ctx.start.getStartIndex(), ctx.stop.getStopIndex() + 1); - } - - - @Override - public void exitBlock(HCLParser.BlockContext ctx) { - HCLBlock block = (HCLBlock) current; - - ArrayList decl = new ArrayList<>(4); - - for (TerminalNode idn : ctx.IDENTIFIER()) { - Token token = idn.getSymbol(); - HCLIdentifier id = new HCLIdentifier.SimpleId(block, token.getText()); - addReference(id, token); - decl.add(id); - } - for (HCLParser.StringLitContext idn : ctx.stringLit()) { - String sid = idn.getText(); - if (sid.length() > 1) { // Do not process the '"' string literal - sid = sid.substring(1, sid.length() - (sid.endsWith("\"") ? 1 : 0)); - HCLIdentifier id = new HCLIdentifier.StringId(block, sid); - addReference(id, idn); - decl.add(id); - } - } - block.setDeclaration(references.sortBySource(decl)); - - current = current.getContainer(); - current.add(block); - addReference(block, ctx); - } - - @Override - public void exitBody(HCLParser.BodyContext ctx) { - for (HCLParser.AttributeContext actx : ctx.attribute()) { - HCLAttribute attr = new HCLAttribute(current); - attr.name = new HCLIdentifier.SimpleId(attr, actx.IDENTIFIER().getText()); - addReference(attr.name, actx.IDENTIFIER().getSymbol()); - addReference(attr, actx); - current.add(attr); - } - } - - @Override - public void enterBlock(HCLParser.BlockContext ctx) { - current = new HCLBlock(current); - } - - -} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLAddressableElement.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLAddressableElement.java new file mode 100644 index 000000000000..407648d3d4e0 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLAddressableElement.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +/** + * + * @author lkishalmi + */ +public abstract class HCLAddressableElement extends HCLElement { + + final HCLAddressableElement parent; + + public HCLAddressableElement(HCLAddressableElement parent) { + this.parent = parent; + } + + public final HCLAddressableElement getParent() { + return parent; + } + + public HCLContainer getContainer() { + HCLAddressableElement e = parent; + while (e != null && !(e instanceof HCLContainer)) { + e = e.parent; + } + return (HCLContainer) e; + } + + public abstract String id(); + +} \ No newline at end of file diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLArithmeticOperation.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLArithmeticOperation.java new file mode 100644 index 000000000000..3386a8fd79f5 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLArithmeticOperation.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author lkishalmi + */ +public abstract class HCLArithmeticOperation extends HCLExpression { + + public enum Operator { + NOT("!"), + MINUS("-"), + + MUL("*"), + DIV("/"), + MOD("%"), + + ADD("+"), + SUB("-"), + + OR("||"), + AND("&&"), + + LTE("<="), + GTE(">="), + LT("<"), + GT(">"), + EQUALS("=="), + NOT_EQUALS("!="); + + final String op; + + private Operator(String op) { + this.op = op; + } + + @Override + public String toString() { + return op; + } + } + + public final Operator op; + + public HCLArithmeticOperation(Operator op) { + this.op = op; + } + + public final static class Binary extends HCLArithmeticOperation { + public final HCLExpression left; + public final HCLExpression right; + + public Binary(Operator op, HCLExpression left, HCLExpression right) { + super(op); + this.left = left; + this.right = right; + } + + @Override + public String asString() { + return left.toString() + op.toString() + right.toString(); + } + + @Override + public List getChildren() { + return Arrays.asList(left, right); + } + + } + + public final static class Unary extends HCLArithmeticOperation { + public final HCLExpression operand; + + public Unary(Operator op, HCLExpression operand) { + super(op); + this.operand = operand; + } + + @Override + public String asString() { + return op.toString() + operand.toString(); + } + + @Override + public List getChildren() { + return Collections.singletonList(operand); + } + + } +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLAttribute.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLAttribute.java index 726180c758e9..64ea0a16e942 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLAttribute.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLAttribute.java @@ -18,16 +18,22 @@ */ package org.netbeans.modules.languages.hcl.ast; + /** * * @author Laszlo Kishalmi */ -public final class HCLAttribute extends HCLElement { +public final class HCLAttribute extends HCLAddressableElement { - HCLIdentifier name; + final HCLIdentifier name; + final HCLExpression value; + final int group; - public HCLAttribute(HCLContainer parent) { + public HCLAttribute(HCLContainer parent, HCLIdentifier name, HCLExpression value, int group) { super(parent); + this.name = name; + this.value = value; + this.group = group; } @Override @@ -39,4 +45,19 @@ public HCLIdentifier getName() { return name; } + public HCLExpression getValue() { + return value; + } + + public int getGroup() { + return group; + } + + @Override + public void accept(Visitor v) { + if (!v.visit(this) && (value != null)) { + value.accept(v); + } + } + } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLBlock.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLBlock.java index 36a21980f10f..7967d9af61b2 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLBlock.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLBlock.java @@ -28,14 +28,14 @@ */ public class HCLBlock extends HCLContainer { - List declaration; - String id; + private List declaration; + private String id; public HCLBlock(HCLContainer parent) { super(parent); } - public void setDeclaration(List declaration) { + void setDeclaration(List declaration) { this.declaration = Collections.unmodifiableList(declaration); id = declaration.stream().map(d -> d.id()).collect(Collectors.joining(".")); } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLBlockFactory.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLBlockFactory.java new file mode 100644 index 000000000000..3f3bed877358 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLBlockFactory.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.ArrayList; +import java.util.function.Consumer; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.netbeans.modules.languages.hcl.grammar.HCLLexer; +import org.netbeans.modules.languages.hcl.grammar.HCLParser; + +/** + * + * @author lkishalmi + */ +public class HCLBlockFactory { + + private final Consumer createAction; + private final HCLExpressionFactory exprFactory; + + private int group = 0; + private ParserRuleContext prev = null; + + public HCLBlockFactory(Consumer createAction) { + this.createAction = createAction; + this.exprFactory = new HCLExpressionFactory(createAction); + } + + public HCLBlockFactory() { + this(null); + } + + public final HCLDocument process(HCLParser.ConfigFileContext ctx) { + var ret = new HCLDocument(); + if (ctx.body() != null) { + body(ret, ctx.body()); + } + return ret; + } + + protected HCLBlock block(HCLContainer parent, HCLParser.BlockContext ctx) { + HCLBlock ret = created(new HCLBlock(parent), ctx); + + + if (ctx.body() != null) { + body(ret, ctx.body()); + } + + ArrayList decl = new ArrayList<>(4); + + if (ctx.children != null) { + for (ParseTree pt : ctx.children) { + if (pt instanceof TerminalNode) { + Token token = ((TerminalNode) pt).getSymbol(); + if (token.getType() == HCLLexer.IDENTIFIER) { + HCLIdentifier attrName = created(new HCLIdentifier.SimpleId( token.getText()), token); + if (pt instanceof ErrorNode) { + // This happens most probably while adding a new attribute to a block + if (prev != null) { + group += prev.stop.getLine() + 1 < token.getLine() ? 1 : 0; + } + HCLAttribute attr = created(new HCLAttribute(ret, attrName, null, group), token); + ret.add(attr); + } else { + decl.add(attrName); + } + } + } + if (pt instanceof HCLParser.StringLitContext) { + HCLParser.StringLitContext slit = (HCLParser.StringLitContext) pt; + String sid = slit.getText(); + if (sid.length() > 1) { // Do not process the '"' string literal + sid = sid.substring(1, sid.length() - (sid.endsWith("\"") ? 1 : 0)); + HCLIdentifier id = created(new HCLIdentifier.StringId(sid), slit); + decl.add(id); + } + } + } + } + + ret.setDeclaration(decl); + + return ret; + } + + protected void body(HCLContainer c, HCLParser.BodyContext ctx) { + if (ctx.children != null) { + for (ParseTree pt : ctx.children) { + if (pt instanceof HCLParser.AttributeContext) { + HCLParser.AttributeContext actx = (HCLParser.AttributeContext) pt; + if (prev != null) { + group += prev.stop.getLine() + 1 < actx.start.getLine() ? 1 : 0; + } + HCLIdentifier attrName = created(new HCLIdentifier.SimpleId(actx.IDENTIFIER().getText()), actx.IDENTIFIER().getSymbol()); + HCLExpression attrValue = exprFactory.process(actx.expression()); + HCLAttribute attr = created(new HCLAttribute(c, attrName, attrValue, group), actx); + c.add(attr); + prev = actx; + continue; + } + if (pt instanceof HCLParser.BlockContext) { + c.add(block(c, (HCLParser.BlockContext) pt)); + continue; + } + if (pt instanceof ErrorNode) { + Token token = ((ErrorNode) pt).getSymbol(); + if (token.getType() == HCLLexer.IDENTIFIER) { + if (prev != null) { + group += prev.stop.getLine() + 1 < token.getLine() ? 1 : 0; + } + HCLIdentifier attrName = created(new HCLIdentifier.SimpleId(token.getText()), token); + HCLAttribute attr = new HCLAttribute(c, attrName, null, group); + c.add(attr); + } + } + } + } + } + + private E created(E element, Token token) { + elementCreated(element, token, token); + return element; + } + + private E created(E element, ParserRuleContext ctx) { + elementCreated(element, ctx.start, ctx.stop); + return element; + } + + private void elementCreated(HCLElement element, Token start, Token stop) { + if (createAction != null) { + createAction.accept(new HCLElement.CreateContext(element, start, stop)); + } + } + +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLCollection.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLCollection.java new file mode 100644 index 000000000000..bbb740b8ff76 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLCollection.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; + +/** + * + * @author lkishalmi + */ +public abstract class HCLCollection extends HCLExpression { + + public final List elements; + + public HCLCollection(List elements) { + this.elements = Collections.unmodifiableList(elements); + } + + public static final class Tuple extends HCLCollection { + + + public Tuple(List elements) { + super(elements); + } + + @Override + public String asString() { + StringJoiner sj = new StringJoiner(",", "[", "]"); + for (HCLExpression element : elements) { + sj.add(element.asString()); + } + return sj.toString(); + } + + @Override + public List getChildren() { + return elements; + } + + } + + public static final class ObjectElement { + public final HCLExpression key; + public final HCLExpression value; + public final int group; + + public ObjectElement(HCLExpression key, HCLExpression value, int group) { + this.key = key; + this.value = value; + this.group = group; + } + + @Override + public String toString() { + return key.asString() + "=" + value.asString(); + } + + + } + + public static final class Object extends HCLCollection { + + private final List parts; + private final List keys; + private final List values; + + public Object(List elements) { + super(elements); + List p = new ArrayList<>(elements.size() * 2); + List k = new ArrayList<>(elements.size()); + List v = new ArrayList<>(elements.size()); + for (ObjectElement e : elements) { + k.add(e.key); + v.add(e.value); + p.add(e.key); + p.add(e.value); + } + parts = Collections.unmodifiableList(p); + keys = Collections.unmodifiableList(k); + values = Collections.unmodifiableList(v); + } + + @Override + public String asString() { + StringJoiner sj = new StringJoiner(",", "{", "}"); + for (ObjectElement element : elements) { + sj.add(element.toString()); + } + return sj.toString(); + } + + public List getKeys() { + return keys; + } + + public List getValues() { + return values; + } + + @Override + public List getChildren() { + return parts; + } + + } +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLConditionalOperation.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLConditionalOperation.java new file mode 100644 index 000000000000..1980d5b51eb2 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLConditionalOperation.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.Arrays; +import java.util.List; + +/** + * + * @author lkishalmi + */ +public final class HCLConditionalOperation extends HCLExpression { + + final HCLExpression condition; + final HCLExpression trueValue; + final HCLExpression falseValue; + + public HCLConditionalOperation(HCLExpression condition, HCLExpression trueValue, HCLExpression falseValue) { + this.condition = condition; + this.trueValue = trueValue; + this.falseValue = falseValue; + } + + @Override + public String asString() { + return condition.toString() + "?" + trueValue.toString() + ":" + falseValue.toString(); + } + + @Override + public List getChildren() { + return Arrays.asList(condition, trueValue, falseValue); + } + + +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLContainer.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLContainer.java index 6b8178d80b34..87e6559aa701 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLContainer.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLContainer.java @@ -1,4 +1,4 @@ -/* + /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -27,7 +27,7 @@ * * @author Laszlo Kishalmi */ -public abstract class HCLContainer extends HCLElement { +public abstract class HCLContainer extends HCLAddressableElement { final List elements = new LinkedList<>(); final List blocks = new LinkedList<>(); @@ -63,4 +63,14 @@ public Collection getAttributes() { public boolean hasAttributes() { return !attributes.isEmpty(); } + + @Override + public final void accept(Visitor v) { + if (!v.visit(this)) { + for (HCLElement element : elements) { + element.accept(v); + } + } + } + } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLDocument.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLDocument.java index e0233bb7c8a6..dfe65d93b1f9 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLDocument.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLDocument.java @@ -32,4 +32,5 @@ public HCLDocument() { public String id() { return ""; } + } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLElement.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLElement.java index 00e1f2eb7c00..3c4b54f5fddb 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLElement.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLElement.java @@ -18,29 +18,81 @@ */ package org.netbeans.modules.languages.hcl.ast; +import org.antlr.v4.runtime.Token; + /** * * @author Laszlo Kishalmi */ public abstract class HCLElement { - final HCLElement parent; - - public HCLElement(HCLElement parent) { - this.parent = parent; + public abstract void accept(Visitor v); + + public interface Visitor { + /** + * Visit the given element. Shall return {@code true} if the visit + * shall be finished at this level in the element tree. + * + * @param e the element to visit. + * @return {@code false} if the visit shall continue on the subtree of + * the given element. + */ + boolean visit(HCLElement e); } - - public final HCLElement getParent() { - return parent; + + /** + * Convenience Visitor implementation, where the HCLElements are split to + * Block, Attribute, and Expression types. + */ + public abstract static class BAEVisitor implements Visitor { + @Override + public boolean visit(HCLElement e) { + if (e instanceof HCLBlock) { + return visitBlock((HCLBlock)e); + } else if (e instanceof HCLAttribute) { + return visitAttribute((HCLAttribute) e); + } else if (e instanceof HCLExpression) { + return visitExpression((HCLExpression) e); + } + return false; + } + + protected abstract boolean visitBlock(HCLBlock block); + + protected abstract boolean visitAttribute(HCLAttribute attr); + + protected abstract boolean visitExpression(HCLExpression expr); } - public HCLContainer getContainer() { - HCLElement e = parent; - while (e != null && !(e instanceof HCLContainer)) { - e = e.parent; + public static class BAEVisitorAdapter extends BAEVisitor { + + @Override + protected boolean visitBlock(HCLBlock block) { + return false; + } + + @Override + protected boolean visitAttribute(HCLAttribute attr) { + return false; + } + + @Override + protected boolean visitExpression(HCLExpression expr) { + return false; } - return (HCLContainer) e; + } - public abstract String id(); + public static final class CreateContext { + public final HCLElement element; + public final Token start; + public final Token stop; + + public CreateContext(HCLElement element, Token start, Token stop) { + this.element = element; + this.start = start; + this.stop = stop; + } + + } } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLExpression.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLExpression.java new file mode 100644 index 000000000000..15ed91a722f9 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLExpression.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.List; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.netbeans.modules.languages.hcl.grammar.HCLLexer; +import org.netbeans.modules.languages.hcl.grammar.HCLParser; + +/** + * + * @author lkishalmi + */ +public abstract class HCLExpression extends HCLElement { + + public static HCLExpression parse(String expr) { + HCLLexer lexer = new HCLLexer(CharStreams.fromString(expr)); + HCLParser parser = new HCLParser(new CommonTokenStream(lexer)); + return new HCLExpressionFactory().process(parser.expression()); + } + + public String toString() { + return getClass().getSimpleName() + ": " + asString(); + } + + public abstract List getChildren(); + + public abstract String asString(); + + @Override + public final void accept(Visitor v) { + if (!v.visit(this)) { + for (HCLExpression c : getChildren()) { + if (c != null) { + c.accept(v); + } + } + } + } +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLExpressionFactory.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLExpressionFactory.java new file mode 100644 index 000000000000..3ca0ef86650b --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLExpressionFactory.java @@ -0,0 +1,407 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.antlr.v4.runtime.NoViableAltException; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.netbeans.modules.languages.hcl.grammar.HCLLexer; +import static org.netbeans.modules.languages.hcl.grammar.HCLLexer.*; +import org.netbeans.modules.languages.hcl.grammar.HCLParser; + +/** + * + * @author lkishalmi + */ +public class HCLExpressionFactory { + + private final Consumer createAction; + + public HCLExpressionFactory(Consumer createAction) { + this.createAction = createAction; + } + + public HCLExpressionFactory() { + this(null); + } + + + public final HCLExpression process(HCLParser.ExpressionContext ctx) throws UnsupportedOperationException { + return expr(ctx); + } + + + protected HCLExpression expr(HCLParser.ExpressionContext ctx) throws UnsupportedOperationException { + if (ctx == null) { + return null; + } + if (ctx.op != null) { + if (ctx.left != null && ctx.right != null) { + HCLArithmeticOperation.Operator op = binOp(ctx.op.getType()); + return created(new HCLArithmeticOperation.Binary(op, expr(ctx.left), expr(ctx.right)), ctx); + } + if (ctx.right != null) { + switch (ctx.op.getType()) { + case NOT: + return created(new HCLArithmeticOperation.Unary(HCLArithmeticOperation.Operator.NOT, expr(ctx.right)), ctx); + case MINUS: + return created(new HCLArithmeticOperation.Unary(HCLArithmeticOperation.Operator.MINUS, expr(ctx.right)), ctx); + } + } + if (ctx.exprCond != null && ctx.exprTrue != null && ctx.exprFalse != null) { + return created(new HCLConditionalOperation(expr(ctx.exprCond), expr(ctx.exprTrue), expr(ctx.exprFalse)), ctx); + } + } else { + return ctx.exprTerm() != null ? expr(ctx.exprTerm()) : null; + } + throw new UnsupportedOperationException("Unsupported expression: " + ctx.getText()); + } + + protected HCLExpression expr(HCLParser.ExprTermContext ctx) { + if (ctx == null) { + return null; + } + HCLExpression ret = null; + if (ctx.LPAREN() != null && ctx.RPAREN() != null) { + ret = expr(ctx.expression()); + } else if (ctx.literalValue() != null) { + ret = expr(ctx.literalValue()); + } else if (ctx.collectionValue() != null) { + ret = expr(ctx.collectionValue()); + } else if (ctx.functionCall() != null) { + ret = expr(ctx.functionCall()); + } else if (ctx.templateExpr() != null) { + ret = expr(ctx.templateExpr()); + } else if (ctx.forExpr() != null) { + ret = expr(ctx.forExpr()); + } else if (ctx.variableExpr() != null) { + ret = expr(ctx.variableExpr()); + } else if (ctx.getAttr() != null) { + ret = expr(expr(ctx.exprTerm()), ctx.getAttr()); + } else if (ctx.index() != null) { + ret = expr(expr(ctx.exprTerm()), ctx.index()); + } else if (ctx.splat() != null) { + HCLParser.SplatContext splat = ctx.splat(); + ret = expr(ctx.exprTerm(), splat); + } + if (ctx.exception != null) { + if (ctx.exception instanceof NoViableAltException) { + NoViableAltException nva = (NoViableAltException) ctx.exception; + if (nva.getStartToken().getType() == HCLLexer.DOT) { + //Most probably a single DOT would mean a started attribute resolve expression + //Let's create an empty one on the fly + return created(new HCLResolveOperation.Attribute(ret, null), nva.getStartToken()); + } + } + } + return ret; + } + + protected HCLExpression expr(HCLParser.VariableExprContext ctx) { + return ctx != null ? created(new HCLVariable(id(ctx.IDENTIFIER())), ctx) : null; + } + + protected HCLExpression expr(HCLParser.LiteralValueContext ctx) throws UnsupportedOperationException { + if (ctx == null) { + return null; + } + if (ctx.stringLit() != null) { + return ctx.stringLit().stringContent() != null ? created(new HCLLiteral.StringLit(ctx.stringLit().stringContent().getText()), ctx) : created(new HCLLiteral.StringLit(""), ctx); + } + if (ctx.TRUE() != null) { + return created(HCLLiteral.TRUE, ctx); + } + if (ctx.FALSE() != null) { + return created(HCLLiteral.FALSE, ctx); + } + if (ctx.NULL() != null) { + return created(HCLLiteral.NULL, ctx); + } + if (ctx.NUMERIC_LIT() != null) { + return created(new HCLLiteral.NumericLit(ctx.NUMERIC_LIT().getText()), ctx); + } + throw new UnsupportedOperationException("Unsupported literal: " + ctx.getText()); + } + + protected HCLExpression expr(HCLParser.CollectionValueContext ctx) throws UnsupportedOperationException { + if (ctx == null) { + return null; + } + if (ctx.tuple() != null) { + HCLParser.TupleContext tuple = ctx.tuple(); + List elements = new LinkedList<>(); + for (HCLParser.ExpressionContext ec : tuple.expression()) { + elements.add(expr(ec)); + } + return new HCLCollection.Tuple(elements); + } + if (ctx.object() != null) { + HCLParser.ObjectContext object = ctx.object(); + List elements = new LinkedList<>(); + int group = 0; + ParserRuleContext prev = null; + for (HCLParser.ObjectElemContext ec : object.objectElem()) { + if ((prev != null) && (ec.key != null)) { + group += prev.stop.getLine() + 1 < ec.key.start.getLine() ? 1 : 0; + } + elements.add(new HCLCollection.ObjectElement(expr(ec.key), expr(ec.value), group)); + prev = ec; + } + return new HCLCollection.Object(elements); + } + throw new UnsupportedOperationException("Unsupported collection: " + ctx.getText()); + } + + protected HCLExpression expr(HCLParser.FunctionCallContext ctx) { + if (ctx == null) { + return null; + } + List args = Collections.emptyList(); + boolean expand = false; + if (ctx.arguments() != null) { + args = new ArrayList<>(ctx.arguments().expression().size()); + for (HCLParser.ExpressionContext ectx : ctx.arguments().expression()) { + args.add(expr(ectx)); + } + expand = ctx.arguments().ELLIPSIS() != null; + } + return created(new HCLFunction(id(ctx.IDENTIFIER()), args, expand), ctx); + } + + private static HCLArithmeticOperation.Operator binOp(int tokenType) { + switch (tokenType) { + case STAR: + return HCLArithmeticOperation.Operator.MUL; + case SLASH: + return HCLArithmeticOperation.Operator.DIV; + case PERCENT: + return HCLArithmeticOperation.Operator.MOD; + case PLUS: + return HCLArithmeticOperation.Operator.ADD; + case MINUS: + return HCLArithmeticOperation.Operator.SUB; + case OR: + return HCLArithmeticOperation.Operator.OR; + case AND: + return HCLArithmeticOperation.Operator.AND; + case LT: + return HCLArithmeticOperation.Operator.LT; + case LTE: + return HCLArithmeticOperation.Operator.LTE; + case GT: + return HCLArithmeticOperation.Operator.GT; + case GTE: + return HCLArithmeticOperation.Operator.GTE; + case EQUALS: + return HCLArithmeticOperation.Operator.EQUALS; + case NOT_EQUALS: + return HCLArithmeticOperation.Operator.NOT_EQUALS; + default: + return null; + } + } + + protected HCLExpression expr(HCLParser.TemplateExprContext ctx) { + if (ctx == null) { + return null; + } + if (ctx.heredoc() != null) { + return expr(ctx.heredoc()); + } + if (ctx.quotedTemplate() != null) { + return expr(ctx.quotedTemplate()); + } + throw new UnsupportedOperationException("Not supported yet."); + } + + protected HCLExpression expr(HCLParser.HeredocContext ctx) { + if (ctx == null) { + return null; + } + LinkedList parts = new LinkedList<>(); + + String startText = ctx.HEREDOC_START().getText(); + boolean indented = startText.startsWith("<<~"); //NOI18N + int markerStart = indented ? 3 : 2; + int markerEnd = startText.endsWith("\r\n") ? 2 : 1; + String marker = startText.substring(markerStart, startText.length() - markerEnd); + + int indent = -1; + if (indented) { + String content = ctx.heredocTemplate() != null ? ctx.heredocTemplate().getText() : ""; + AtomicInteger mIndent = new AtomicInteger(Integer.MAX_VALUE); + content.lines().filter((line) -> !line.isBlank()).forEach((line) -> { + int left = 0; + while (left < line.length() && line.charAt(left) == ' ') { // HCL Specs says + left++; + } + mIndent.set(Math.min(mIndent.get(), left)); + }); + indent = mIndent.get(); + } + + if (ctx.heredocTemplate() != null && ctx.heredocTemplate().children != null) { + for (ParseTree pt : ctx.heredocTemplate().children) { + if (pt instanceof HCLParser.HeredocContentContext) { + parts.add(new HCLTemplate.StringPart(pt.getText())); + } + if (pt instanceof HCLParser.InterpolationContext) { + parts.add(new HCLTemplate.InterpolationPart(pt.getText())); + } + if (pt instanceof HCLParser.TemplateContext) { + parts.add(new HCLTemplate.TemplatePart(pt.getText())); + } + } + } + return created(new HCLTemplate.HereDoc(marker, indent, parts), ctx); + } + + protected HCLExpression expr(HCLParser.QuotedTemplateContext ctx) { + if (ctx == null) { + return null; + } + LinkedList parts = new LinkedList<>(); + for (ParseTree pt : ctx.children) { + if (pt instanceof HCLParser.StringContentContext) { + parts.add(new HCLTemplate.StringPart(pt.getText())); + } + if (pt instanceof HCLParser.InterpolationContext) { + parts.add(new HCLTemplate.InterpolationPart(pt.getText())); + } + if (pt instanceof HCLParser.TemplateContext) { + parts.add(new HCLTemplate.TemplatePart(pt.getText())); + } + } + return new HCLTemplate.StringTemplate(parts); + } + + protected HCLExpression expr(HCLParser.ForExprContext ctx) { + if (ctx == null) { + return null; + } + boolean isTuple = ctx.forTupleExpr() != null; + + HCLParser.ForIntroContext intro = isTuple ? ctx.forTupleExpr().forIntro() : ctx.forObjectExpr().forIntro(); + + HCLIdentifier keyVar = null; + HCLIdentifier valueVar = null; + if (intro.second != null) { + keyVar = id(intro.first); + valueVar = id(intro.second); + } else { + valueVar = id(intro.first); + } + HCLExpression iterable = expr(intro.expression()); + + HCLParser.ForCondContext cond = isTuple ? ctx.forTupleExpr().forCond() : ctx.forObjectExpr().forCond(); + HCLExpression condExpr = cond != null ? expr(cond.expression()) : null; + + if (isTuple) { + HCLExpression result = expr(ctx.forTupleExpr().expression()); + return created(new HCLForExpression.Tuple(keyVar, valueVar, iterable, condExpr, result), ctx); + } else { + boolean grouping = ctx.forObjectExpr().ELLIPSIS() != null; + HCLExpression resultKey = expr(ctx.forObjectExpr().key); + HCLExpression resultValue = expr(ctx.forObjectExpr().value); + return created(new HCLForExpression.Object(keyVar, valueVar, iterable, condExpr, resultKey, resultValue, grouping), ctx); + } + } + + protected HCLExpression expr(HCLParser.ExprTermContext exprTerm, HCLParser.SplatContext splat) { + HCLExpression base = expr(exprTerm); + if (splat.attrSplat() != null) { + base = expr(base, splat); + for (HCLParser.GetAttrContext ac : splat.attrSplat().getAttr()) { + base = expr(base, ac); + } + } + if (splat.fullSplat() != null) { + base = expr(base, splat); + for (ParseTree pt : splat.fullSplat().children) { + if (pt instanceof HCLParser.GetAttrContext) { + HCLParser.GetAttrContext ac = (HCLParser.GetAttrContext) pt; + base = expr(base, ac); + } + if (pt instanceof HCLParser.IndexContext) { + base = expr(base, (HCLParser.IndexContext) pt); + } + } + } + return base; + } + + protected HCLExpression expr(HCLExpression base, HCLParser.SplatContext ctx) throws UnsupportedOperationException { + if (ctx.attrSplat() != null) { + HCLParser.AttrSplatContext splat = ctx.attrSplat(); + return created(new HCLResolveOperation.AttrSplat(base), splat.DOT().getSymbol(), splat.STAR().getSymbol()); + } + if (ctx.fullSplat() != null) { + HCLParser.FullSplatContext splat = ctx.fullSplat(); + return created(new HCLResolveOperation.FullSplat(base), splat.LBRACK().getSymbol(), splat.RBRACK().getSymbol()); + } + throw new UnsupportedOperationException("Unsupported Splat operation. Should not happen. Check the Grammar!"); + } + + protected HCLExpression expr(HCLExpression base, HCLParser.GetAttrContext ctx) { + return created(new HCLResolveOperation.Attribute(base, id(ctx.IDENTIFIER())), ctx); + } + + protected HCLExpression expr(HCLExpression base, HCLParser.IndexContext idx) { + HCLExpression index = idx.expression() != null + ? expr(idx.expression()) + : created(new HCLLiteral.NumericLit(idx.LEGACY_INDEX().getText().substring(1)), idx); // Split the dot from . + return created(new HCLResolveOperation.Index(base, index, idx.LEGACY_INDEX() != null), idx); + + } + + private HCLIdentifier id(TerminalNode tn) { + return tn != null ? id(tn.getSymbol()) : null; + } + + protected HCLIdentifier id(Token t) { + return (t != null) && (t.getType() == HCLLexer.IDENTIFIER) ? created(new HCLIdentifier.SimpleId(t.getText()), t) : null; + } + + private E created(E element, Token token) { + return created(element, token, token); + } + + private E created(E element, ParserRuleContext ctx) { + return created(element, ctx.start, ctx.stop); + } + + private E created(E element, Token start, Token stop) { + elementCreated(element, start, stop); + return element; + } + + private void elementCreated(HCLElement element, Token start, Token stop) { + if (createAction != null) { + createAction.accept(new HCLElement.CreateContext(element, start, stop)); + } + } +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLForExpression.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLForExpression.java new file mode 100644 index 000000000000..6b499026c7cf --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLForExpression.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.Arrays; +import java.util.List; + +/** + * + * @author lkishalmi + */ +public abstract class HCLForExpression extends HCLExpression { + + public final HCLIdentifier keyVar; + public final HCLIdentifier valueVar; + + public final HCLExpression iterable; + public final HCLExpression condition; + + public HCLForExpression(HCLIdentifier keyVar, HCLIdentifier valueVar, HCLExpression iterable, HCLExpression condition) { + this.keyVar = keyVar; + this.valueVar = valueVar; + this.iterable = iterable; + this.condition = condition; + } + + public final static class Tuple extends HCLForExpression { + + public final HCLExpression result; + + public Tuple(HCLIdentifier keyVar, HCLIdentifier valueVar, HCLExpression iterable, HCLExpression condition, HCLExpression result) { + super(keyVar, valueVar, iterable, condition); + this.result = result; + } + + @Override + public String asString() { + StringBuilder sb = new StringBuilder(); + sb.append("[for "); + if (keyVar != null) { + sb.append(keyVar).append(','); + } + sb.append(valueVar).append(" in ").append(iterable.asString()).append(':'); + sb.append(result.asString()); + if (condition != null) { + sb.append(" if ").append(condition.asString()); + } + sb.append(']'); + return sb.toString(); + } + + @Override + public List getChildren() { + return Arrays.asList(iterable, result, condition); + } + } + + public final static class Object extends HCLForExpression { + public final HCLExpression resultKey; + public final HCLExpression resultValue; + + public final boolean grouping; + + public Object(HCLIdentifier keyVar, HCLIdentifier valueVar, HCLExpression iterable, HCLExpression condition, HCLExpression resultKey, HCLExpression resultValue, boolean grouping) { + super(keyVar, valueVar, iterable, condition); + this.resultKey = resultKey; + this.resultValue = resultValue; + this.grouping = grouping; + } + + @Override + public String asString() { + StringBuilder sb = new StringBuilder(); + sb.append("{for "); + if (keyVar != null) { + sb.append(keyVar).append(','); + } + sb.append(valueVar).append(" in ").append(iterable.asString()).append(':'); + sb.append(resultKey.asString()).append("=>").append(resultValue.asString()); + if (grouping) { + sb.append("..."); + } + if (condition != null) { + sb.append(" if ").append(condition.asString()); + } + sb.append('}'); + return sb.toString(); + } + + @Override + public List getChildren() { + return Arrays.asList(iterable, resultKey, resultValue, condition); + } + + } + +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLFunction.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLFunction.java new file mode 100644 index 000000000000..2900359f9e3a --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLFunction.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.List; +import java.util.StringJoiner; + +/** + * + * @author lkishalmi + */ +public class HCLFunction extends HCLExpression { + + final HCLIdentifier name; + final List args; + final boolean expand; + + public HCLFunction(HCLIdentifier name, List args, boolean expand) { + this.name = name; + this.args = args; + this.expand = expand; + } + + public HCLIdentifier getName() { + return name; + } + + public List getArgs() { + return args; + } + + @Override + public String asString() { + StringJoiner sargs = new StringJoiner(",", "(", expand ? "...)" : ")"); + args.forEach((arg) -> sargs.add(arg.toString())); + return name + sargs.toString(); + } + + @Override + public List getChildren() { + return args; + } + + +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLIdentifier.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLIdentifier.java index f7d0e5126b97..32270c80c45a 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLIdentifier.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLIdentifier.java @@ -24,22 +24,25 @@ */ public abstract class HCLIdentifier extends HCLElement { - final String id; - - public HCLIdentifier(HCLElement parent, String id) { - super(parent); + public final String id; + + public HCLIdentifier(String id) { this.id = id; } - @Override public String id() { return id; } + @Override + public final void accept(Visitor v) { + v.visit(this); + } + public final static class SimpleId extends HCLIdentifier { - public SimpleId(HCLElement parent, String id) { - super(parent, id); + public SimpleId(String id) { + super(id); } @Override @@ -50,8 +53,8 @@ public String toString() { public final static class StringId extends HCLIdentifier { - public StringId(HCLElement parent, String id) { - super(parent, id); + public StringId(String id) { + super(id); } @Override diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLLiteral.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLLiteral.java new file mode 100644 index 000000000000..7ecd99d2dc6d --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLLiteral.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.Collections; +import java.util.List; + +/** + * + * @author lkishalmi + */ +public abstract class HCLLiteral extends HCLExpression { + + public static final Bool TRUE = new Bool(true); + public static final Bool FALSE = new Bool(false); + public static final Null NULL = new Null(); + + @Override + public final List getChildren() { + return Collections.emptyList(); + } + + public static final class Bool extends HCLLiteral { + + final boolean value; + + private Bool(boolean value) { + this.value = value; + } + + @Override + public String asString() { + return value ? "true" : "false"; + } + } + + public static final class StringLit extends HCLLiteral { + + final String value; + + public StringLit(String value) { + this.value = value; + } + + @Override + public String asString() { + return "\"" + value + "\""; + } + } + + public static final class Null extends HCLLiteral { + + + private Null() { + } + + @Override + public String asString() { + return "null"; //NOI18N + } + } + + public static final class NumericLit extends HCLLiteral { + + final String value; + + public NumericLit(String value) { + this.value = value; + } + + @Override + public String asString() { + return value; + } + } + +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLResolveOperation.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLResolveOperation.java new file mode 100644 index 000000000000..6586b61fec1e --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLResolveOperation.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author lkishalmi + */ +public abstract class HCLResolveOperation extends HCLExpression { + + public final HCLExpression base; + + public HCLResolveOperation(HCLExpression base) { + this.base = base; + } + + @Override + public List getChildren() { + return Collections.singletonList(base); + } + + public final static class Attribute extends HCLResolveOperation { + public final HCLIdentifier attr; + + public Attribute(HCLExpression base, HCLIdentifier attr) { + super(base); + this.attr = attr; + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": ." + attr; + } + + + @Override + public String asString() { + return base.asString() + "." + attr; + } + + } + + public final static class Index extends HCLResolveOperation { + public final HCLExpression index; + public final boolean legacy; + + public Index(HCLExpression base, HCLExpression index, boolean legacy) { + super(base); + this.index = index; + this.legacy = legacy; + } + + @Override + public String asString() { + return base.asString() + (legacy ? "." + index : "[" + index + "]"); + } + + @Override + public List getChildren() { + return Arrays.asList(base, index); + } + } + + public final static class AttrSplat extends HCLResolveOperation { + + public AttrSplat(HCLExpression base) { + super(base); + } + + @Override + public String asString() { + return base.asString() + ".*"; + } + + } + + public final static class FullSplat extends HCLResolveOperation { + + public FullSplat(HCLExpression base) { + super(base); + } + + @Override + public String asString() { + return base.asString() + "[*]"; + } + + } +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLTemplate.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLTemplate.java new file mode 100644 index 000000000000..785b97a95af7 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLTemplate.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.Collections; +import java.util.List; + +/** + * + * @author lkishalmi + */ +public abstract class HCLTemplate extends HCLExpression { + + public final List parts; + + public HCLTemplate(List parts) { + this.parts = Collections.unmodifiableList(parts); + } + + @Override + public List getChildren() { + return Collections.emptyList(); + } + + public abstract static class Part { + public final String value; + + public Part(String value) { + this.value = value; + } + } + + public final static class StringPart extends Part { + + public static final StringPart NL = new StringPart("\n"); + + public StringPart(String value) { + super(value); + } + + @Override + public String toString() { + return value; + } + } + + public final static class InterpolationPart extends Part { + + public InterpolationPart(String value) { + super(value); + } + + @Override + public String toString() { + return "${" + value + "}"; + } + } + + /** + * This is just a temporal implementation as the template expression + * should really form a tree. + */ + public final static class TemplatePart extends Part { + + public TemplatePart(String value) { + super(value); + } + + @Override + public String toString() { + return "%{" + value + "}"; + } + + } + + public final static class HereDoc extends HCLTemplate { + public final String marker; + public final int indent; + + public HereDoc(String marker, int indent, List parts) { + super(parts); + this.marker = marker; + this.indent = indent; + } + + public boolean isIndented() { + return indent != -1; + } + + @Override + public String asString() { + StringBuilder sb = new StringBuilder(); + sb.append(indent > -1 ? "<<-" : "<<").append(marker).append('\n'); + for (Part part : parts) { + sb.append(part); + } + sb.append(marker); + return sb.toString(); + } + } + + public final static class StringTemplate extends HCLTemplate { + + public StringTemplate(List parts) { + super(parts); + } + + @Override + public String asString() { + StringBuilder sb = new StringBuilder(); + sb.append('"'); + for (Part part : parts) { + sb.append(part); + } + sb.append('"'); + return sb.toString(); + } + } + +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLVariable.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLVariable.java new file mode 100644 index 000000000000..306b825fc7f5 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/ast/HCLVariable.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import java.util.Collections; +import java.util.List; + +/** + * + * @author lkishalmi + */ +public final class HCLVariable extends HCLExpression { + + public final HCLIdentifier name; + + public HCLVariable(HCLIdentifier name) { + this.name = name; + } + + @Override + public String asString() { + return name.id; + } + + @Override + public List getChildren() { + return Collections.emptyList(); + } +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLExpressionParser.g4 b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLExpressionParser.g4 index 8a2604c41fa5..aa5bd8e6f155 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLExpressionParser.g4 +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLExpressionParser.g4 @@ -26,7 +26,7 @@ expression | left=expression op=(PLUS | MINUS) right=expression | left=expression op=(AND | OR) right=expression | left=expression op=(LTE | GTE | LT | GT | EQUALS | NOT_EQUALS) right=expression - | exprCond=expression QUESTION (exprTrue=expression COLON exprFalse=expression) + | exprCond=expression op=QUESTION (exprTrue=expression COLON exprFalse=expression) ; exprTerm @@ -40,11 +40,11 @@ exprTerm | exprTerm index | exprTerm getAttr | exprTerm splat - | literalValue ; literalValue - : NUMERIC_LIT + : stringLit // See: https://github.com/hashicorp/hcl/issues/619 + | NUMERIC_LIT | TRUE | FALSE | NULL @@ -57,24 +57,38 @@ collectionValue tuple - // The original separator in HCL is Comma or NewLine - : LBRACK expression (COMMA? expression)* COMMA? RBRACK + // The original separator in HCL is Comma or NewLine, TF says, it is Comma only + // See: https://github.com/hashicorp/hcl/issues/618 + // Using TF which is stricter + : LBRACK expression (COMMA expression)* COMMA? RBRACK | LBRACK RBRACK ; object - // The original separator in HCL is Comma or NewLine + // The original separator in HCL is Comma or NewLine (NL) + // HCL uses NewLine sometimes inconsistent. To have things simplified NL + // is sent to the HIDDEN channel, so it is not available here. + // The only thing we can do is make COMMA optional, that's result more + // permissive grammar than HCL. { a = 1 b = 2 } is a valid object here. : LBRACE objectElem (COMMA? objectElem)* COMMA? RBRACE | LBRACE RBRACE ; objectElem - : (IDENTIFIER | expression) (EQUAL | COLON) expression + // HCL spec says (IDENTIFIER | expression) though a single IDENTIFIER + // would map to variableExpression, so this is redundant here + // AST implementation is easier simply using expression + : key=expression (EQUAL | COLON) value=expression ; templateExpr : quotedTemplate - | heredocTemplate + | heredoc + ; + +stringLit + : QUOTE QUOTE + | QUOTE stringContent QUOTE ; quotedTemplate @@ -107,7 +121,11 @@ heredocContent ; heredocTemplate - : HEREDOC_START (heredocContent | interpolation | template)* HEREDOC_END + : (heredocContent | interpolation | template)* + ; + +heredoc + : HEREDOC_START heredocTemplate HEREDOC_END ; variableExpr @@ -133,11 +151,11 @@ forTupleExpr ; forObjectExpr - : LBRACE forIntro expression RARROW expression ELLIPSIS? forCond? RBRACE + : LBRACE forIntro key=expression RARROW value=expression ELLIPSIS? forCond? RBRACE ; forIntro - : FOR IDENTIFIER (COMMA IDENTIFIER)? IN expression COLON + : FOR first=IDENTIFIER (COMMA second=IDENTIFIER)? IN expression COLON ; forCond diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLLexerBasics.g4 b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLLexerBasics.g4 index 210c47fc1f7b..599309835841 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLLexerBasics.g4 +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLLexerBasics.g4 @@ -28,12 +28,11 @@ fragment Hws ; fragment Vws - : [\r\n] - | [\n] + : '\r'? '\n' ; fragment NonVws - : ~[\r\n\f] + : ~[\r\n] ; fragment BlockComment diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLParser.g4 b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLParser.g4 index f4bdb6d55b1d..9bb888e3cc59 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLParser.g4 +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/grammar/g4/HCLParser.g4 @@ -37,6 +37,3 @@ block : IDENTIFIER (stringLit | IDENTIFIER)* LBRACE body RBRACE ; -stringLit - : QUOTE stringContent+ QUOTE - ; diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/TerraformParserResult.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/TerraformParserResult.java index facf8b5267de..929d467d6242 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/TerraformParserResult.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/TerraformParserResult.java @@ -23,16 +23,15 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.netbeans.modules.csl.api.OffsetRange; -import org.netbeans.modules.csl.api.Severity; -import org.netbeans.modules.csl.spi.DefaultError; import org.netbeans.modules.languages.hcl.HCLParserResult; import org.netbeans.modules.languages.hcl.ast.HCLAttribute; import org.netbeans.modules.languages.hcl.ast.HCLBlock; import org.netbeans.modules.languages.hcl.ast.HCLContainer; import org.netbeans.modules.languages.hcl.ast.HCLDocument; import org.netbeans.modules.languages.hcl.ast.HCLIdentifier; -import org.netbeans.modules.languages.hcl.ast.SourceRef; +import org.netbeans.modules.languages.hcl.SourceRef; +import org.netbeans.modules.languages.hcl.ast.HCLElement; +import org.netbeans.modules.languages.hcl.ast.HCLElement.Visitor; import org.netbeans.modules.parsing.api.Snapshot; import org.openide.util.NbBundle.Messages; @@ -42,7 +41,8 @@ */ public class TerraformParserResult extends HCLParserResult { - + private Map definedBlocks = new HashMap<>(); + public enum BlockType { CHECK("check", 2), @@ -97,55 +97,66 @@ public TerraformParserResult(Snapshot snapshot) { }) protected void processDocument(HCLDocument doc, SourceRef references) { - Set defined = new HashSet<>(); - for (HCLBlock block : doc.getBlocks()) { - List decl = block.getDeclaration(); - HCLIdentifier type = decl.get(0); - - BlockType bt = BlockType.get(type.id()); - if (bt != null) { - if (decl.size() != bt.definitionLength) { - references.getOffsetRange(type).ifPresent((range) -> addError(Bundle.INVALID_BLOCK_DECLARATION(bt.type, bt.definitionLength - 1), range)); - } else { - if (!defined.add(block.id())) { - switch (bt) { - case CHECK: - case DATA: - case MODULE: - case OUTPUT: - case RESOURCE: - case VARIABLE: - references.getOffsetRange(type).ifPresent((range) -> addError(Bundle.DUPLICATE_BLOCK(block.id()), range)); + doc.accept(this::duplicateAttributeVisitor); + doc.accept(this::checkBlockDeclarationVisitor); + } + + + private boolean checkBlockDeclarationVisitor(HCLElement e) { + if (e instanceof HCLBlock) { + HCLBlock block = (HCLBlock) e; + if (block.getParent() instanceof HCLDocument) { + List decl = block.getDeclaration(); + HCLIdentifier type = decl.get(0); + + BlockType bt = BlockType.get(type.id()); + if (bt != null) { + if (decl.size() != bt.definitionLength) { + addError(type, Bundle.INVALID_BLOCK_DECLARATION(bt.type, bt.definitionLength - 1)); + } else { + if (definedBlocks.put(block.id(), block) != null) { + switch (bt) { + case CHECK: + case DATA: + case MODULE: + case OUTPUT: + case RESOURCE: + case VARIABLE: + addError(decl.get(bt.definitionLength - 1), Bundle.DUPLICATE_BLOCK(block.id())); + } } } + } else { + addError(type, Bundle.UNKNOWN_BLOCK(type.id())); } - } else { - references.getOffsetRange(type).ifPresent((range) -> addError(Bundle.UNKNOWN_BLOCK(type.id()), range)); } - checkDuplicateAttribute(block, references); - } - for (HCLAttribute attribute : doc.getAttributes()) { - references.getOffsetRange(attribute.getName()).ifPresent((range) -> addError(Bundle.UNEXPECTED_DOCUMENT_ATTRIBUTE(attribute.id()), range)); + return true; } + return !(e instanceof HCLDocument); } - - private void checkDuplicateAttribute(HCLContainer c, SourceRef references) { - for (HCLBlock block : c.getBlocks()) { - checkDuplicateAttribute(block, references); + + private boolean duplicateAttributeVisitor(HCLElement e) { + if (e instanceof HCLDocument) { + HCLDocument doc = (HCLDocument) e; + for (HCLAttribute attr : doc.getAttributes()) { + addError(attr, Bundle.UNEXPECTED_DOCUMENT_ATTRIBUTE(attr.id())); + } + return false; } - if (c.hasAttributes()) { - Set defined = new HashSet<>(); - for (HCLAttribute attr : c.getAttributes()) { - if (!defined.add(attr.id())) { - references.getOffsetRange(attr.getName()).ifPresent((range) -> addError(Bundle.DUPLICATE_ATTRIBUTE(attr.id()), range)); + if (e instanceof HCLContainer) { + HCLContainer c = (HCLContainer) e; + if (c.hasAttributes()) { + Set defined = new HashSet<>(); + for (HCLAttribute attr : c.getAttributes()) { + if (!defined.add(attr.id())) { + addError(attr.getName(), Bundle.DUPLICATE_ATTRIBUTE(attr.id())); + } } } + return false; } + return true; } - - private void addError(String message, OffsetRange range) { - DefaultError error = new DefaultError(null, message, null, getFileObject(), range.getStart() , range.getEnd(), Severity.ERROR); - errors.add(error); - } + } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/TerraformSemanticAnalyzer.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/TerraformSemanticAnalyzer.java index 7d992dff2928..beb209b693c1 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/TerraformSemanticAnalyzer.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/TerraformSemanticAnalyzer.java @@ -18,93 +18,107 @@ */ package org.netbeans.modules.languages.hcl.terraform; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import org.netbeans.modules.csl.api.ColoringAttributes; -import org.netbeans.modules.csl.api.OffsetRange; -import org.netbeans.modules.csl.api.SemanticAnalyzer; -import org.netbeans.modules.languages.hcl.ast.HCLAttribute; +import org.netbeans.modules.languages.hcl.HCLSemanticAnalyzer; +import org.netbeans.modules.languages.hcl.HCLParserResult; import org.netbeans.modules.languages.hcl.ast.HCLBlock; +import org.netbeans.modules.languages.hcl.ast.HCLDocument; +import org.netbeans.modules.languages.hcl.ast.HCLExpression; import org.netbeans.modules.languages.hcl.ast.HCLIdentifier; -import org.netbeans.modules.languages.hcl.ast.SourceRef; -import org.netbeans.modules.parsing.spi.Scheduler; -import org.netbeans.modules.parsing.spi.SchedulerEvent; +import org.netbeans.modules.languages.hcl.ast.HCLResolveOperation; +import org.netbeans.modules.languages.hcl.ast.HCLVariable; +import org.netbeans.modules.languages.hcl.SourceRef; +import org.netbeans.modules.languages.hcl.terraform.TerraformParserResult.BlockType; +import static org.netbeans.modules.languages.hcl.terraform.TerraformParserResult.BlockType.CHECK; +import static org.netbeans.modules.languages.hcl.terraform.TerraformParserResult.BlockType.DATA; +import static org.netbeans.modules.languages.hcl.terraform.TerraformParserResult.BlockType.LOCALS; +import static org.netbeans.modules.languages.hcl.terraform.TerraformParserResult.BlockType.MODULE; +import static org.netbeans.modules.languages.hcl.terraform.TerraformParserResult.BlockType.OUTPUT; +import static org.netbeans.modules.languages.hcl.terraform.TerraformParserResult.BlockType.PROVIDER; +import static org.netbeans.modules.languages.hcl.terraform.TerraformParserResult.BlockType.RESOURCE; /** * * @author lkishalmi */ -public final class TerraformSemanticAnalyzer extends SemanticAnalyzer { +public final class TerraformSemanticAnalyzer extends HCLSemanticAnalyzer { - private volatile boolean cancelled; - private Map> semanticHighlights; + private static final Set RESOLVE_BASES = Set.of( + "data", + "local", + "module", + "path", + "provider", + "var" + ); - @Override - public Map> getHighlights() { - return semanticHighlights; - } - - protected final synchronized boolean isCancelled() { - return cancelled; - } - - protected final synchronized void resume() { - cancelled = false; - } + private static final Set LITERAL_TYPES = Set.of( + "bool", + "number", + "null", + "string" + ); + @Override - public void run(TerraformParserResult result, SchedulerEvent event) { - resume(); + protected Highlighter createHighlighter(HCLParserResult result) { + return new TerraformHighlighter(result.getReferences()); + } + + private class TerraformHighlighter extends DefaultHighlighter { + private BlockType rootBlockType; - if (isCancelled()) { - return; + protected TerraformHighlighter(SourceRef refs) { + super(refs); } - Map> highlights = new HashMap<>(); - SourceRef refs = result.getReferences(); - for (HCLBlock block : result.getDocument().getBlocks()) { - List decl = block.getDeclaration(); - HCLIdentifier type = decl.get(0); - TerraformParserResult.BlockType bt = TerraformParserResult.BlockType.get(type.id()); - if (bt != null) { - refs.getOffsetRange(type).ifPresent((range) -> highlights.put(range, ColoringAttributes.CLASS_SET)); - if (decl.size() > 1) { - for (int i = 1; i < decl.size(); i++) { - HCLIdentifier id = decl.get(i); - refs.getOffsetRange(id).ifPresent((range) -> highlights.put(range, ColoringAttributes.CONSTRUCTOR_SET)); - } + @Override + protected boolean visitBlock(HCLBlock block) { + if (block.getParent() instanceof HCLDocument) { + List dcl = block.getDeclaration(); + if (!dcl.isEmpty()) { + rootBlockType = TerraformParserResult.BlockType.get(dcl.get(0).id()); } } - markAttributes(highlights, refs, block); + return super.visitBlock(block); } - semanticHighlights = highlights; - } + + + @Override + protected boolean visitExpression(HCLExpression expr) { + if (expr instanceof HCLResolveOperation.Attribute) { + HCLResolveOperation.Attribute attr = (HCLResolveOperation.Attribute) expr; + if ((rootBlockType != null) && (attr.base instanceof HCLVariable)) { + String name = ((HCLVariable)attr.base).name.id; + switch (rootBlockType) { + case CHECK: + case DATA: + case LOCALS: + case MODULE: + case OUTPUT: + case PROVIDER: + case RESOURCE: + if (RESOLVE_BASES.contains(name)) { + mark(attr.base, ColoringAttributes.FIELD_SET); + } + break; + } + return false; + } + } - private void markAttributes(Map> highlights, SourceRef refs, HCLBlock block) { - for (HCLAttribute attr : block.getAttributes()) { - refs.getOffsetRange(attr.getName()).ifPresent((range) -> highlights.put(range, ColoringAttributes.FIELD_SET)); - } - for (HCLBlock nested : block.getBlocks()) { - markAttributes(highlights, refs, nested); + if (rootBlockType == BlockType.VARIABLE && (expr instanceof HCLVariable)) { + String name = ((HCLVariable) expr).name.id; + if (LITERAL_TYPES.contains(name)) { + mark(expr, ColoringAttributes.FIELD_SET); + } + return false; + } + return super.visitExpression(expr); } - } - @Override - public int getPriority() { - return 0; } - - @Override - public Class getSchedulerClass() { - return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER; - } - - @Override - public void cancel() { - cancelled = true; - } - } diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/snippets.xml b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/snippets.xml index 9164ef287d6c..6c1213d1f923 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/snippets.xml +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/snippets.xml @@ -43,6 +43,12 @@ for_each = + + [ for ${NAME newVarName default="i" ordering=2} in ${COLL newVarName default="local." ordering=1} : ${cursor} ] + + + { for ${KEY newVarName default="k" ordering=2}, ${VALUE newVarName default="v" ordering=3} in ${COLL newVarName default="local." ordering=1} : ${KEY} => ${cursor} } + each. diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/terraform-functions-1.4.json b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/terraform-functions-1.4.json new file mode 100644 index 000000000000..963367cf8733 --- /dev/null +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/terraform/terraform-functions-1.4.json @@ -0,0 +1,1439 @@ +{ + "format_version": "1.0", + "function_signatures": { + "abs": { + "description": "`abs` returns the absolute value of the given number. In other words, if the number is zero or positive then it is returned as-is, but if it is negative then it is multiplied by -1 to make it positive before returning it.", + "return_type": "number", + "parameters": [ + { + "name": "num", + "type": "number" + } + ] + }, + "abspath": { + "description": "`abspath` takes a string containing a filesystem path and converts it to an absolute path. That is, if the path is not absolute, it will be joined with the current working directory.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "alltrue": { + "description": "`alltrue` returns `true` if all elements in a given collection are `true` or `\"true\"`. It also returns `true` if the collection is empty.", + "return_type": "bool", + "parameters": [ + { + "name": "list", + "type": [ + "list", + "bool" + ] + } + ] + }, + "anytrue": { + "description": "`anytrue` returns `true` if any element in a given collection is `true` or `\"true\"`. It also returns `false` if the collection is empty.", + "return_type": "bool", + "parameters": [ + { + "name": "list", + "type": [ + "list", + "bool" + ] + } + ] + }, + "base64decode": { + "description": "`base64decode` takes a string containing a Base64 character sequence and returns the original string.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "base64encode": { + "description": "`base64encode` applies Base64 encoding to a string.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "base64gzip": { + "description": "`base64gzip` compresses a string with gzip and then encodes the result in Base64 encoding.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "base64sha256": { + "description": "`base64sha256` computes the SHA256 hash of a given string and encodes it with Base64. This is not equivalent to `base64encode(sha256(\"test\"))` since `sha256()` returns hexadecimal representation.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "base64sha512": { + "description": "`base64sha512` computes the SHA512 hash of a given string and encodes it with Base64. This is not equivalent to `base64encode(sha512(\"test\"))` since `sha512()` returns hexadecimal representation.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "basename": { + "description": "`basename` takes a string containing a filesystem path and removes all except the last portion from it.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "bcrypt": { + "description": "`bcrypt` computes a hash of the given string using the Blowfish cipher, returning a string in [the _Modular Crypt Format_](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html) usually expected in the shadow password file on many Unix systems.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ], + "variadic_parameter": { + "name": "cost", + "description": "The `cost` argument is optional and will default to 10 if unspecified.", + "type": "number" + } + }, + "can": { + "description": "`can` evaluates the given expression and returns a boolean value indicating whether the expression produced a result without any errors.", + "return_type": "bool", + "parameters": [ + { + "name": "expression", + "type": "dynamic" + } + ] + }, + "ceil": { + "description": "`ceil` returns the closest whole number that is greater than or equal to the given value, which may be a fraction.", + "return_type": "number", + "parameters": [ + { + "name": "num", + "type": "number" + } + ] + }, + "chomp": { + "description": "`chomp` removes newline characters at the end of a string.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "chunklist": { + "description": "`chunklist` splits a single list into fixed-size chunks, returning a list of lists.", + "return_type": [ + "list", + [ + "list", + "dynamic" + ] + ], + "parameters": [ + { + "name": "list", + "type": [ + "list", + "dynamic" + ] + }, + { + "name": "size", + "description": "The maximum length of each chunk. All but the last element of the result is guaranteed to be of exactly this size.", + "type": "number" + } + ] + }, + "cidrhost": { + "description": "`cidrhost` calculates a full host IP address for a given host number within a given IP network address prefix.", + "return_type": "string", + "parameters": [ + { + "name": "prefix", + "description": "`prefix` must be given in CIDR notation, as defined in [RFC 4632 section 3.1](https://tools.ietf.org/html/rfc4632#section-3.1).", + "type": "string" + }, + { + "name": "hostnum", + "description": "`hostnum` is a whole number that can be represented as a binary integer with no more than the number of digits remaining in the address after the given prefix.", + "type": "number" + } + ] + }, + "cidrnetmask": { + "description": "`cidrnetmask` converts an IPv4 address prefix given in CIDR notation into a subnet mask address.", + "return_type": "string", + "parameters": [ + { + "name": "prefix", + "description": "`prefix` must be given in CIDR notation, as defined in [RFC 4632 section 3.1](https://tools.ietf.org/html/rfc4632#section-3.1).", + "type": "string" + } + ] + }, + "cidrsubnet": { + "description": "`cidrsubnet` calculates a subnet address within given IP network address prefix.", + "return_type": "string", + "parameters": [ + { + "name": "prefix", + "description": "`prefix` must be given in CIDR notation, as defined in [RFC 4632 section 3.1](https://tools.ietf.org/html/rfc4632#section-3.1).", + "type": "string" + }, + { + "name": "newbits", + "description": "`newbits` is the number of additional bits with which to extend the prefix.", + "type": "number" + }, + { + "name": "netnum", + "description": "`netnum` is a whole number that can be represented as a binary integer with no more than `newbits` binary digits, which will be used to populate the additional bits added to the prefix.", + "type": "number" + } + ] + }, + "cidrsubnets": { + "description": "`cidrsubnets` calculates a sequence of consecutive IP address ranges within a particular CIDR prefix.", + "return_type": [ + "list", + "string" + ], + "parameters": [ + { + "name": "prefix", + "description": "`prefix` must be given in CIDR notation, as defined in [RFC 4632 section 3.1](https://tools.ietf.org/html/rfc4632#section-3.1).", + "type": "string" + } + ], + "variadic_parameter": { + "name": "newbits", + "type": "number" + } + }, + "coalesce": { + "description": "`coalesce` takes any number of arguments and returns the first one that isn't null or an empty string.", + "return_type": "dynamic", + "variadic_parameter": { + "name": "vals", + "is_nullable": true, + "type": "dynamic" + } + }, + "coalescelist": { + "description": "`coalescelist` takes any number of list arguments and returns the first one that isn't empty.", + "return_type": "dynamic", + "variadic_parameter": { + "name": "vals", + "description": "List or tuple values to test in the given order.", + "is_nullable": true, + "type": "dynamic" + } + }, + "compact": { + "description": "`compact` takes a list of strings and returns a new list with any empty string elements removed.", + "return_type": [ + "list", + "string" + ], + "parameters": [ + { + "name": "list", + "type": [ + "list", + "string" + ] + } + ] + }, + "concat": { + "description": "`concat` takes two or more lists and combines them into a single list.", + "return_type": "dynamic", + "variadic_parameter": { + "name": "seqs", + "type": "dynamic" + } + }, + "contains": { + "description": "`contains` determines whether a given list or set contains a given single value as one of its elements.", + "return_type": "dynamic", + "parameters": [ + { + "name": "list", + "type": "dynamic" + }, + { + "name": "value", + "type": "dynamic" + } + ] + }, + "csvdecode": { + "description": "`csvdecode` decodes a string containing CSV-formatted data and produces a list of maps representing that data.", + "return_type": "dynamic", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "dirname": { + "description": "`dirname` takes a string containing a filesystem path and removes the last portion from it.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "distinct": { + "description": "`distinct` takes a list and returns a new list with any duplicate elements removed.", + "return_type": [ + "list", + "dynamic" + ], + "parameters": [ + { + "name": "list", + "type": [ + "list", + "dynamic" + ] + } + ] + }, + "element": { + "description": "`element` retrieves a single element from a list.", + "return_type": "dynamic", + "parameters": [ + { + "name": "list", + "type": "dynamic" + }, + { + "name": "index", + "type": "number" + } + ] + }, + "endswith": { + "description": "`endswith` takes two values: a string to check and a suffix string. The function returns true if the first string ends with that exact suffix.", + "return_type": "bool", + "parameters": [ + { + "name": "str", + "type": "string" + }, + { + "name": "suffix", + "type": "string" + } + ] + }, + "file": { + "description": "`file` reads the contents of a file at the given path and returns them as a string.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "filebase64": { + "description": "`filebase64` reads the contents of a file at the given path and returns them as a base64-encoded string.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "filebase64sha256": { + "description": "`filebase64sha256` is a variant of `base64sha256` that hashes the contents of a given file rather than a literal string.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "filebase64sha512": { + "description": "`filebase64sha512` is a variant of `base64sha512` that hashes the contents of a given file rather than a literal string.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "fileexists": { + "description": "`fileexists` determines whether a file exists at a given path.", + "return_type": "bool", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "filemd5": { + "description": "`filemd5` is a variant of `md5` that hashes the contents of a given file rather than a literal string.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "fileset": { + "description": "`fileset` enumerates a set of regular file names given a path and pattern. The path is automatically removed from the resulting set of file names and any result still containing path separators always returns forward slash (`/`) as the path separator for cross-system compatibility.", + "return_type": [ + "set", + "string" + ], + "parameters": [ + { + "name": "path", + "type": "string" + }, + { + "name": "pattern", + "type": "string" + } + ] + }, + "filesha1": { + "description": "`filesha1` is a variant of `sha1` that hashes the contents of a given file rather than a literal string.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "filesha256": { + "description": "`filesha256` is a variant of `sha256` that hashes the contents of a given file rather than a literal string.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "filesha512": { + "description": "`filesha512` is a variant of `sha512` that hashes the contents of a given file rather than a literal string.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "flatten": { + "description": "`flatten` takes a list and replaces any elements that are lists with a flattened sequence of the list contents.", + "return_type": "dynamic", + "parameters": [ + { + "name": "list", + "type": "dynamic" + } + ] + }, + "floor": { + "description": "`floor` returns the closest whole number that is less than or equal to the given value, which may be a fraction.", + "return_type": "number", + "parameters": [ + { + "name": "num", + "type": "number" + } + ] + }, + "format": { + "description": "The `format` function produces a string by formatting a number of other values according to a specification string. It is similar to the `printf` function in C, and other similar functions in other programming languages.", + "return_type": "dynamic", + "parameters": [ + { + "name": "format", + "type": "string" + } + ], + "variadic_parameter": { + "name": "args", + "is_nullable": true, + "type": "dynamic" + } + }, + "formatdate": { + "description": "`formatdate` converts a timestamp into a different time format.", + "return_type": "string", + "parameters": [ + { + "name": "format", + "type": "string" + }, + { + "name": "time", + "type": "string" + } + ] + }, + "formatlist": { + "description": "`formatlist` produces a list of strings by formatting a number of other values according to a specification string.", + "return_type": "dynamic", + "parameters": [ + { + "name": "format", + "type": "string" + } + ], + "variadic_parameter": { + "name": "args", + "is_nullable": true, + "type": "dynamic" + } + }, + "indent": { + "description": "`indent` adds a given number of spaces to the beginnings of all but the first line in a given multi-line string.", + "return_type": "string", + "parameters": [ + { + "name": "spaces", + "description": "Number of spaces to add after each newline character.", + "type": "number" + }, + { + "name": "str", + "type": "string" + } + ] + }, + "index": { + "description": "`index` finds the element index for a given value in a list.", + "return_type": "dynamic", + "parameters": [ + { + "name": "list", + "type": "dynamic" + }, + { + "name": "value", + "type": "dynamic" + } + ] + }, + "join": { + "description": "`join` produces a string by concatenating together all elements of a given list of strings with the given delimiter.", + "return_type": "string", + "parameters": [ + { + "name": "separator", + "description": "Delimiter to insert between the given strings.", + "type": "string" + } + ], + "variadic_parameter": { + "name": "lists", + "description": "One or more lists of strings to join.", + "type": [ + "list", + "string" + ] + } + }, + "jsondecode": { + "description": "`jsondecode` interprets a given string as JSON, returning a representation of the result of decoding that string.", + "return_type": "dynamic", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "jsonencode": { + "description": "`jsonencode` encodes a given value to a string using JSON syntax.", + "return_type": "string", + "parameters": [ + { + "name": "val", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "keys": { + "description": "`keys` takes a map and returns a list containing the keys from that map.", + "return_type": "dynamic", + "parameters": [ + { + "name": "inputMap", + "description": "The map to extract keys from. May instead be an object-typed value, in which case the result is a tuple of the object attributes.", + "type": "dynamic" + } + ] + }, + "length": { + "description": "`length` determines the length of a given list, map, or string.", + "return_type": "number", + "parameters": [ + { + "name": "value", + "type": "dynamic" + } + ] + }, + "log": { + "description": "`log` returns the logarithm of a given number in a given base.", + "return_type": "number", + "parameters": [ + { + "name": "num", + "type": "number" + }, + { + "name": "base", + "type": "number" + } + ] + }, + "lookup": { + "description": "`lookup` retrieves the value of a single element from a map, given its key. If the given key does not exist, the given default value is returned instead.", + "return_type": "dynamic", + "parameters": [ + { + "name": "inputMap", + "type": "dynamic" + }, + { + "name": "key", + "type": "string" + } + ], + "variadic_parameter": { + "name": "default", + "is_nullable": true, + "type": "dynamic" + } + }, + "lower": { + "description": "`lower` converts all cased letters in the given string to lowercase.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "matchkeys": { + "description": "`matchkeys` constructs a new list by taking a subset of elements from one list whose indexes match the corresponding indexes of values in another list.", + "return_type": [ + "list", + "dynamic" + ], + "parameters": [ + { + "name": "values", + "type": [ + "list", + "dynamic" + ] + }, + { + "name": "keys", + "type": [ + "list", + "dynamic" + ] + }, + { + "name": "searchset", + "type": [ + "list", + "dynamic" + ] + } + ] + }, + "max": { + "description": "`max` takes one or more numbers and returns the greatest number from the set.", + "return_type": "number", + "variadic_parameter": { + "name": "numbers", + "type": "number" + } + }, + "md5": { + "description": "`md5` computes the MD5 hash of a given string and encodes it with hexadecimal digits.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "merge": { + "description": "`merge` takes an arbitrary number of maps or objects, and returns a single map or object that contains a merged set of elements from all arguments.", + "return_type": "dynamic", + "variadic_parameter": { + "name": "maps", + "is_nullable": true, + "type": "dynamic" + } + }, + "min": { + "description": "`min` takes one or more numbers and returns the smallest number from the set.", + "return_type": "number", + "variadic_parameter": { + "name": "numbers", + "type": "number" + } + }, + "nonsensitive": { + "description": "`nonsensitive` takes a sensitive value and returns a copy of that value with the sensitive marking removed, thereby exposing the sensitive value.", + "return_type": "dynamic", + "parameters": [ + { + "name": "value", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "one": { + "description": "`one` takes a list, set, or tuple value with either zero or one elements. If the collection is empty, `one` returns `null`. Otherwise, `one` returns the first element. If there are two or more elements then `one` will return an error.", + "return_type": "dynamic", + "parameters": [ + { + "name": "list", + "type": "dynamic" + } + ] + }, + "parseint": { + "description": "`parseint` parses the given string as a representation of an integer in the specified base and returns the resulting number. The base must be between 2 and 62 inclusive.", + "return_type": "dynamic", + "parameters": [ + { + "name": "number", + "type": "dynamic" + }, + { + "name": "base", + "type": "number" + } + ] + }, + "pathexpand": { + "description": "`pathexpand` takes a filesystem path that might begin with a `~` segment, and if so it replaces that segment with the current user's home directory path.", + "return_type": "string", + "parameters": [ + { + "name": "path", + "type": "string" + } + ] + }, + "pow": { + "description": "`pow` calculates an exponent, by raising its first argument to the power of the second argument.", + "return_type": "number", + "parameters": [ + { + "name": "num", + "type": "number" + }, + { + "name": "power", + "type": "number" + } + ] + }, + "range": { + "description": "`range` generates a list of numbers using a start value, a limit value, and a step value.", + "return_type": [ + "list", + "number" + ], + "variadic_parameter": { + "name": "params", + "type": "number" + } + }, + "regex": { + "description": "`regex` applies a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) to a string and returns the matching substrings.", + "return_type": "dynamic", + "parameters": [ + { + "name": "pattern", + "type": "string" + }, + { + "name": "string", + "type": "string" + } + ] + }, + "regexall": { + "description": "`regexall` applies a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) to a string and returns a list of all matches.", + "return_type": [ + "list", + "dynamic" + ], + "parameters": [ + { + "name": "pattern", + "type": "string" + }, + { + "name": "string", + "type": "string" + } + ] + }, + "replace": { + "description": "`replace` searches a given string for another given substring, and replaces each occurrence with a given replacement string.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + }, + { + "name": "substr", + "type": "string" + }, + { + "name": "replace", + "type": "string" + } + ] + }, + "reverse": { + "description": "`reverse` takes a sequence and produces a new sequence of the same length with all of the same elements as the given sequence but in reverse order.", + "return_type": "dynamic", + "parameters": [ + { + "name": "list", + "type": "dynamic" + } + ] + }, + "rsadecrypt": { + "description": "`rsadecrypt` decrypts an RSA-encrypted ciphertext, returning the corresponding cleartext.", + "return_type": "string", + "parameters": [ + { + "name": "ciphertext", + "type": "string" + }, + { + "name": "privatekey", + "type": "string" + } + ] + }, + "sensitive": { + "description": "`sensitive` takes any value and returns a copy of it marked so that Terraform will treat it as sensitive, with the same meaning and behavior as for [sensitive input variables](/language/values/variables#suppressing-values-in-cli-output).", + "return_type": "dynamic", + "parameters": [ + { + "name": "value", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "setintersection": { + "description": "The `setintersection` function takes multiple sets and produces a single set containing only the elements that all of the given sets have in common. In other words, it computes the [intersection](https://en.wikipedia.org/wiki/Intersection_\\(set_theory\\)) of the sets.", + "return_type": [ + "set", + "dynamic" + ], + "parameters": [ + { + "name": "first_set", + "type": [ + "set", + "dynamic" + ] + } + ], + "variadic_parameter": { + "name": "other_sets", + "type": [ + "set", + "dynamic" + ] + } + }, + "setproduct": { + "description": "The `setproduct` function finds all of the possible combinations of elements from all of the given sets by computing the [Cartesian product](https://en.wikipedia.org/wiki/Cartesian_product).", + "return_type": "dynamic", + "variadic_parameter": { + "name": "sets", + "description": "The sets to consider. Also accepts lists and tuples, and if all arguments are of list or tuple type then the result will preserve the input ordering", + "type": "dynamic" + } + }, + "setsubtract": { + "description": "The `setsubtract` function returns a new set containing the elements from the first set that are not present in the second set. In other words, it computes the [relative complement](https://en.wikipedia.org/wiki/Complement_\\(set_theory\\)#Relative_complement) of the second set.", + "return_type": [ + "set", + "dynamic" + ], + "parameters": [ + { + "name": "a", + "type": [ + "set", + "dynamic" + ] + }, + { + "name": "b", + "type": [ + "set", + "dynamic" + ] + } + ] + }, + "setunion": { + "description": "The `setunion` function takes multiple sets and produces a single set containing the elements from all of the given sets. In other words, it computes the [union](https://en.wikipedia.org/wiki/Union_\\(set_theory\\)) of the sets.", + "return_type": [ + "set", + "dynamic" + ], + "parameters": [ + { + "name": "first_set", + "type": [ + "set", + "dynamic" + ] + } + ], + "variadic_parameter": { + "name": "other_sets", + "type": [ + "set", + "dynamic" + ] + } + }, + "sha1": { + "description": "`sha1` computes the SHA1 hash of a given string and encodes it with hexadecimal digits.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "sha256": { + "description": "`sha256` computes the SHA256 hash of a given string and encodes it with hexadecimal digits.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "sha512": { + "description": "`sha512` computes the SHA512 hash of a given string and encodes it with hexadecimal digits.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "signum": { + "description": "`signum` determines the sign of a number, returning a number between -1 and 1 to represent the sign.", + "return_type": "number", + "parameters": [ + { + "name": "num", + "type": "number" + } + ] + }, + "slice": { + "description": "`slice` extracts some consecutive elements from within a list.", + "return_type": "dynamic", + "parameters": [ + { + "name": "list", + "type": "dynamic" + }, + { + "name": "start_index", + "type": "number" + }, + { + "name": "end_index", + "type": "number" + } + ] + }, + "sort": { + "description": "`sort` takes a list of strings and returns a new list with those strings sorted lexicographically.", + "return_type": [ + "list", + "string" + ], + "parameters": [ + { + "name": "list", + "type": [ + "list", + "string" + ] + } + ] + }, + "split": { + "description": "`split` produces a list by dividing a given string at all occurrences of a given separator.", + "return_type": [ + "list", + "string" + ], + "parameters": [ + { + "name": "separator", + "type": "string" + }, + { + "name": "str", + "type": "string" + } + ] + }, + "startswith": { + "description": "`startswith` takes two values: a string to check and a prefix string. The function returns true if the string begins with that exact prefix.", + "return_type": "bool", + "parameters": [ + { + "name": "str", + "type": "string" + }, + { + "name": "prefix", + "type": "string" + } + ] + }, + "strrev": { + "description": "`strrev` reverses the characters in a string. Note that the characters are treated as _Unicode characters_ (in technical terms, Unicode [grapheme cluster boundaries](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) are respected).", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "substr": { + "description": "`substr` extracts a substring from a given string by offset and (maximum) length.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + }, + { + "name": "offset", + "type": "number" + }, + { + "name": "length", + "type": "number" + } + ] + }, + "sum": { + "description": "`sum` takes a list or set of numbers and returns the sum of those numbers.", + "return_type": "dynamic", + "parameters": [ + { + "name": "list", + "type": "dynamic" + } + ] + }, + "templatefile": { + "description": "`templatefile` reads the file at the given path and renders its content as a template using a supplied set of template variables.", + "return_type": "dynamic", + "parameters": [ + { + "name": "path", + "type": "string" + }, + { + "name": "vars", + "type": "dynamic" + } + ] + }, + "textdecodebase64": { + "description": "`textdecodebase64` function decodes a string that was previously Base64-encoded, and then interprets the result as characters in a specified character encoding.", + "return_type": "string", + "parameters": [ + { + "name": "source", + "type": "string" + }, + { + "name": "encoding", + "type": "string" + } + ] + }, + "textencodebase64": { + "description": "`textencodebase64` encodes the unicode characters in a given string using a specified character encoding, returning the result base64 encoded because Terraform language strings are always sequences of unicode characters.", + "return_type": "string", + "parameters": [ + { + "name": "string", + "type": "string" + }, + { + "name": "encoding", + "type": "string" + } + ] + }, + "timeadd": { + "description": "`timeadd` adds a duration to a timestamp, returning a new timestamp.", + "return_type": "string", + "parameters": [ + { + "name": "timestamp", + "type": "string" + }, + { + "name": "duration", + "type": "string" + } + ] + }, + "timecmp": { + "description": "`timecmp` compares two timestamps and returns a number that represents the ordering of the instants those timestamps represent.", + "return_type": "number", + "parameters": [ + { + "name": "timestamp_a", + "type": "string" + }, + { + "name": "timestamp_b", + "type": "string" + } + ] + }, + "timestamp": { + "description": "`timestamp` returns a UTC timestamp string in [RFC 3339](https://tools.ietf.org/html/rfc3339) format.", + "return_type": "string" + }, + "title": { + "description": "`title` converts the first letter of each word in the given string to uppercase.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "tobool": { + "description": "`tobool` converts its argument to a boolean value.", + "return_type": "bool", + "parameters": [ + { + "name": "v", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "tolist": { + "description": "`tolist` converts its argument to a list value.", + "return_type": [ + "list", + "dynamic" + ], + "parameters": [ + { + "name": "v", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "tomap": { + "description": "`tomap` converts its argument to a map value.", + "return_type": [ + "map", + "dynamic" + ], + "parameters": [ + { + "name": "v", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "tonumber": { + "description": "`tonumber` converts its argument to a number value.", + "return_type": "number", + "parameters": [ + { + "name": "v", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "toset": { + "description": "`toset` converts its argument to a set value.", + "return_type": [ + "set", + "dynamic" + ], + "parameters": [ + { + "name": "v", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "tostring": { + "description": "`tostring` converts its argument to a string value.", + "return_type": "string", + "parameters": [ + { + "name": "v", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "transpose": { + "description": "`transpose` takes a map of lists of strings and swaps the keys and values to produce a new map of lists of strings.", + "return_type": [ + "map", + [ + "list", + "string" + ] + ], + "parameters": [ + { + "name": "values", + "type": [ + "map", + [ + "list", + "string" + ] + ] + } + ] + }, + "trim": { + "description": "`trim` removes the specified set of characters from the start and end of the given string.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + }, + { + "name": "cutset", + "description": "A string containing all of the characters to trim. Each character is taken separately, so the order of characters is insignificant.", + "type": "string" + } + ] + }, + "trimprefix": { + "description": "`trimprefix` removes the specified prefix from the start of the given string. If the string does not start with the prefix, the string is returned unchanged.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + }, + { + "name": "prefix", + "type": "string" + } + ] + }, + "trimspace": { + "description": "`trimspace` removes any space characters from the start and end of the given string.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "trimsuffix": { + "description": "`trimsuffix` removes the specified suffix from the end of the given string.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + }, + { + "name": "suffix", + "type": "string" + } + ] + }, + "try": { + "description": "`try` evaluates all of its argument expressions in turn and returns the result of the first one that does not produce any errors.", + "return_type": "dynamic", + "variadic_parameter": { + "name": "expressions", + "type": "dynamic" + } + }, + "upper": { + "description": "`upper` converts all cased letters in the given string to uppercase.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "urlencode": { + "description": "`urlencode` applies URL encoding to a given string.", + "return_type": "string", + "parameters": [ + { + "name": "str", + "type": "string" + } + ] + }, + "uuid": { + "description": "`uuid` generates a unique identifier string.", + "return_type": "string" + }, + "uuidv5": { + "description": "`uuidv5` generates a _name-based_ UUID, as described in [RFC 4122 section 4.3](https://tools.ietf.org/html/rfc4122#section-4.3), also known as a \"version 5\" UUID.", + "return_type": "string", + "parameters": [ + { + "name": "namespace", + "type": "string" + }, + { + "name": "name", + "type": "string" + } + ] + }, + "values": { + "description": "`values` takes a map and returns a list containing the values of the elements in that map.", + "return_type": "dynamic", + "parameters": [ + { + "name": "mapping", + "type": "dynamic" + } + ] + }, + "yamldecode": { + "description": "`yamldecode` parses a string as a subset of YAML, and produces a representation of its value.", + "return_type": "dynamic", + "parameters": [ + { + "name": "src", + "type": "string" + } + ] + }, + "yamlencode": { + "description": "`yamlencode` encodes a given value to a string using [YAML 1.2](https://yaml.org/spec/1.2/spec.html) block syntax.", + "return_type": "string", + "parameters": [ + { + "name": "value", + "is_nullable": true, + "type": "dynamic" + } + ] + }, + "zipmap": { + "description": "`zipmap` constructs a map from a list of keys and a corresponding list of values.", + "return_type": "dynamic", + "parameters": [ + { + "name": "keys", + "type": [ + "list", + "string" + ] + }, + { + "name": "values", + "type": "dynamic" + } + ] + } + } +} diff --git a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/tfvars/TFVarsParserResult.java b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/tfvars/TFVarsParserResult.java index 25ef3a65cb5c..63e5f41d9b0c 100644 --- a/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/tfvars/TFVarsParserResult.java +++ b/ide/languages.hcl/src/org/netbeans/modules/languages/hcl/tfvars/TFVarsParserResult.java @@ -20,13 +20,11 @@ import java.util.HashSet; import java.util.Set; -import org.netbeans.modules.csl.api.Severity; -import org.netbeans.modules.csl.spi.DefaultError; import org.netbeans.modules.languages.hcl.HCLParserResult; import org.netbeans.modules.languages.hcl.ast.HCLAttribute; import org.netbeans.modules.languages.hcl.ast.HCLBlock; import org.netbeans.modules.languages.hcl.ast.HCLDocument; -import org.netbeans.modules.languages.hcl.ast.SourceRef; +import org.netbeans.modules.languages.hcl.SourceRef; import org.netbeans.modules.parsing.api.Snapshot; import org.openide.util.NbBundle; @@ -44,22 +42,16 @@ public TFVarsParserResult(Snapshot snapshot) { @NbBundle.Messages({ "INVALID_BLOCK=Blocks are not supported in TFVars files.", "# {0} - The name of duplicated variable", - "DUPLICATE_VARIABLE=Valiable {0} is already defined." + "DUPLICATE_VARIABLE=Variable {0} is already defined." }) protected void processDocument(HCLDocument doc, SourceRef references) { for (HCLBlock block : doc.getBlocks()) { - references.getOffsetRange(block).ifPresent((range) -> { - DefaultError error = new DefaultError(null, Bundle.INVALID_BLOCK(), null, getFileObject(), range.getStart() , range.getEnd(), Severity.ERROR); - errors.add(error); - }); + addError(block, Bundle.INVALID_BLOCK()); } Set usedAttributes = new HashSet<>(); for (HCLAttribute attr : doc.getAttributes()) { if (!usedAttributes.add(attr.id())) { - references.getOffsetRange(attr.getName()).ifPresent((range) -> { - DefaultError error = new DefaultError(null, Bundle.DUPLICATE_VARIABLE(attr.id()), null, getFileObject(), range.getStart() , range.getEnd(), Severity.ERROR); - errors.add(error); - }); + addError(attr.getName(), Bundle.DUPLICATE_VARIABLE(attr.id())); } } } diff --git a/ide/languages.hcl/test/unit/src/org/netbeans/modules/languages/hcl/ReferenceTest.java b/ide/languages.hcl/test/unit/src/org/netbeans/modules/languages/hcl/ReferenceTest.java new file mode 100644 index 000000000000..a5c6d9a752b2 --- /dev/null +++ b/ide/languages.hcl/test/unit/src/org/netbeans/modules/languages/hcl/ReferenceTest.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl; + +import java.util.List; +import java.util.regex.Pattern; +import static junit.framework.TestCase.assertNotSame; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.junit.Test; +import org.netbeans.modules.languages.hcl.ast.HCLAttribute; +import org.netbeans.modules.languages.hcl.ast.HCLBlock; +import org.netbeans.modules.languages.hcl.ast.HCLElement; +import org.netbeans.modules.languages.hcl.ast.HCLExpression; +import org.netbeans.modules.languages.hcl.ast.HCLIdentifier; +import org.netbeans.modules.languages.hcl.ast.HCLResolveOperation; +import static org.junit.Assert.*; +import org.netbeans.modules.languages.hcl.ast.HCLBlockFactory; +import org.netbeans.modules.languages.hcl.grammar.HCLLexer; +import org.netbeans.modules.languages.hcl.grammar.HCLParser; + +/** + * + * @author lkishalmi + */ +public class ReferenceTest { + + @Test + public void testReferences1() throws Exception { + assertTrue(elementAt("^\na{\nb=true\n}").isEmpty()); + } + + @Test + public void testReferences2() throws Exception { + var at = elementAt("^a{\nb=true\n}"); + assertTrue(at.isEmpty()); + } + + @Test + public void testReferences3() throws Exception { + var at = elementAt("a^{\nb=true\n}"); + assertEquals(2, at.size()); + var it = at.iterator(); + assertTrue(it.next() instanceof HCLBlock); + assertTrue(it.next() instanceof HCLIdentifier); + } + + @Test + public void testReferences4() throws Exception { + var at = elementAt("a{^\nb=true\n}"); + assertEquals(1, at.size()); + var it = at.iterator(); + assertTrue(it.next() instanceof HCLBlock); + } + + @Test + public void testReferences5() throws Exception { + var at = elementAt("a{\nb=^true\n}"); + assertEquals(2, at.size()); + var it = at.iterator(); + assertTrue(it.next() instanceof HCLBlock); + assertTrue(it.next() instanceof HCLAttribute); + } + + @Test + public void testReferences6() throws Exception { + var at = elementAt("a{\nb=t^rue\n}"); + assertEquals(3, at.size()); + var it = at.iterator(); + assertTrue(it.next() instanceof HCLBlock); + assertTrue(it.next() instanceof HCLAttribute); + assertTrue(it.next() instanceof HCLExpression); + } + + @Test + public void testReferences7() throws Exception { + var at = elementAt("a{\nb = local.b.*^.c\n}"); + assertEquals(3, at.size()); + var it = at.iterator(); + assertTrue(it.next() instanceof HCLBlock); + assertTrue(it.next() instanceof HCLAttribute); + assertTrue(it.next() instanceof HCLResolveOperation.AttrSplat); + } + + @Test + public void testReferences8() throws Exception { + var at = elementAt("a{\nb = local.b.*.^c\n}"); + assertEquals(3, at.size()); + var it = at.iterator(); + assertTrue(it.next() instanceof HCLBlock); + assertTrue(it.next() instanceof HCLAttribute); + assertTrue(it.next() instanceof HCLResolveOperation.Attribute); + } + + @Test + public void testReferences9() throws Exception { + var at = elementAt("a{\nb = local.b.*.^\n}"); + assertEquals(3, at.size()); + var it = at.iterator(); + assertTrue(it.next() instanceof HCLBlock); + assertTrue(it.next() instanceof HCLAttribute); + assertTrue(it.next() instanceof HCLResolveOperation.Attribute); + } + + private List elementAt(String code) { + int pos = code.indexOf('^'); + + assertNotSame(-1, pos); + + code = code.replaceAll(Pattern.quote("^"), ""); + + return parse(code).elementsAt(pos - 1); + } + + private SourceRef parse(String expr) { + var lexer = new HCLLexer(CharStreams.fromString(expr)); + var parser = new HCLParser(new CommonTokenStream(lexer)); + var ret = new SourceRef(null); + var bf = new HCLBlockFactory(ret::elementCreated); + bf.process(parser.configFile()); + return ret; + } +} diff --git a/ide/languages.hcl/test/unit/src/org/netbeans/modules/languages/hcl/ast/HCLLiteralsTest.java b/ide/languages.hcl/test/unit/src/org/netbeans/modules/languages/hcl/ast/HCLLiteralsTest.java new file mode 100644 index 000000000000..2b168cda5a1e --- /dev/null +++ b/ide/languages.hcl/test/unit/src/org/netbeans/modules/languages/hcl/ast/HCLLiteralsTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.netbeans.modules.languages.hcl.ast; + +import org.junit.Test; +import static org.junit.Assert.*; + +import static org.netbeans.modules.languages.hcl.ast.HCLExpression.parse; + +/** + * + * @author lkishalmi + */ +public class HCLLiteralsTest { + @Test + public void testBool() throws Exception { + assertEquals(HCLLiteral.TRUE, parse("true")); + assertEquals(HCLLiteral.FALSE, parse("false")); + } + + @Test + public void testNull() throws Exception { + assertEquals(HCLLiteral.NULL, parse("null")); + } + + @Test + public void testNumber() throws Exception { + HCLExpression exp = parse("3.14"); + assertTrue(exp instanceof HCLLiteral.NumericLit); + HCLLiteral.NumericLit num = (HCLLiteral.NumericLit) exp; + assertEquals("3.14", num.value); + } + + @Test + public void testString() throws Exception { + HCLExpression exp = parse("\"Hello\""); + assertTrue(exp instanceof HCLLiteral.StringLit); + HCLLiteral.StringLit str = (HCLLiteral.StringLit) exp; + assertEquals("Hello", str.value); + } + + @Test + public void testStringEmpty1() throws Exception { + HCLExpression exp = parse("\"\""); + assertTrue(exp instanceof HCLLiteral.StringLit); + HCLLiteral.StringLit str = (HCLLiteral.StringLit) exp; + assertEquals("", str.value); + } + + @Test + public void testStringEmpty2() throws Exception { + HCLExpression exp = parse("<