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 extends Scheduler> 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 extends StructureItem> 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 extends E> elements) {
- List ret = new ArrayList<>(elements);
- Collections.sort(ret, sourceOrder);
- return ret;
+
+ public List extends HCLElement> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> parts;
+ private final List extends HCLExpression> keys;
+ private final List extends HCLExpression> 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 extends HCLExpression> getKeys() {
+ return keys;
+ }
+
+ public List extends HCLExpression> getValues() {
+ return values;
+ }
+
+ @Override
+ public List extends HCLExpression> 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 extends HCLExpression> 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 extends HCLAttribute> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends HCLExpression> 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 extends Scheduler> 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 extends HCLElement> 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("<