package com.laytonsmith.core.functions;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.annotations.api;
import com.laytonsmith.annotations.core;
import com.laytonsmith.annotations.hide;
import com.laytonsmith.annotations.noboilerplate;
import com.laytonsmith.annotations.noprofile;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.core.Optimizable;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.Script;
import com.laytonsmith.core.compiler.FileOptions;
import com.laytonsmith.core.constructs.CBracket;
import com.laytonsmith.core.constructs.CClassType;
import com.laytonsmith.core.constructs.CEntry;
import com.laytonsmith.core.constructs.CFunction;
import com.laytonsmith.core.constructs.CLabel;
import com.laytonsmith.core.constructs.CNull;
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.CSymbol;
import com.laytonsmith.core.constructs.CVoid;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.IVariable;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.exceptions.CRE.CREThrowable;
import com.laytonsmith.core.exceptions.CancelCommandException;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
*
*/
@core
public class Compiler {
public static String docs() {
return "Compiler internal functions should be declared here. If you're reading this from anywhere"
+ " but the source code, there's a bug, because these functions shouldn't be public or used"
+ " in a script.";
}
@api
@noprofile
@hide("This is only used internally by the compiler.")
public static class p extends DummyFunction {
@Override
public String getName() {
return "p";
}
@Override
public String docs() {
return "mixed {c...} Used internally by the compiler. You shouldn't use it.";
}
@Override
public boolean useSpecialExec() {
return true;
}
@Override
public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) {
switch (nodes.length) {
case 0:
return CVoid.VOID;
case 1:
return parent.eval(nodes[0], env);
default:
return new __autoconcat__().execs(t, env, parent, nodes);
}
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException {
return CVoid.VOID;
}
}
@api
@noprofile
@hide("This is only used internally by the compiler.")
public static class centry extends DummyFunction {
@Override
public String docs() {
return "CEntry {label, content} Dynamically creates a CEntry. This is used internally by the "
+ "compiler.";
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
return new CEntry(args[0], args[1], t);
}
}
@api
@noprofile
@hide("This is only used internally by the compiler.")
public static class __autoconcat__ extends DummyFunction implements Optimizable {
public static ParseTree getParseTree(List<ParseTree> children, FileOptions fo, Target t) {
CFunction ac = new CFunction(new __autoconcat__().getName(), t);
ParseTree tree = new ParseTree(ac, fo);
tree.setChildren(children);
return tree;
}
public static ParseTree getParseTree(ParseTree child, FileOptions fo, Target t) {
CFunction ac = new CFunction(new __autoconcat__().getName(), t);
ParseTree tree = new ParseTree(ac, fo);
List<ParseTree> children = new ArrayList<>();
children.add(child);
tree.setChildren(children);
return tree;
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException {
throw new Error("Should not have gotten here, __autoconcat__ was not removed before runtime.");
}
@Override
public String docs() {
return "string {var1, [var2...]} This function should only be used by the compiler, behavior"
+ " may be undefined if it is used in code.";
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(
OptimizationOption.OPTIMIZE_DYNAMIC);
}
@Override
public ParseTree optimizeDynamic(Target t, List<ParseTree> list, FileOptions fileOptions) throws ConfigCompileException {
return optimizeSpecial(list, true);
}
private final static String ASSIGN = new DataHandling.assign().getName();
/**
* __autoconcat__ has special optimization techniques needed, since it's
* really a part of the compiler itself, and not so much a function. It
* being a function is merely a convenience, so we can defer processing
* until after parsing. While it is tightly coupled with the compiler,
* this is ok, since it's really a compiler mechanism more than a
* function.
*
* @param t
* @param list
* @return
*/
public ParseTree optimizeSpecial(List<ParseTree> list, boolean returnSConcat) throws ConfigCompileException {
//If any of our nodes are CSymbols, we have different behavior
boolean inSymbolMode = false; //caching this can save Xn
//Assignment
//Note that we are walking the array in reverse, because multiple assignments,
//say @a = @b = 1 will break if they look like assign(assign(@a, @b), 1),
//they need to be assign(@a, assign(@b, 1)). As a variation, we also have
//to support something like 1 + @a = 2, which will turn into add(1, assign(@a, 2),
//and 1 + @a = @b + 3 would turn into add(1, assign(@a, add(@b, 3))).
for (int i = list.size() - 2; i >= 0; i--) {
ParseTree node = list.get(i + 1);
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isAssignment()) {
CSymbol sy = (CSymbol) node.getData();
String conversionFunction = sy.convertAssignment();
ParseTree lhs = list.get(i);
if (conversionFunction != null) {
ParseTree conversion = new ParseTree(new CFunction(conversionFunction, node.getTarget()), node.getFileOptions());
//grab the entire right side, and turn it into an operation with the left side.
//We have to take the entire right up to the next construct not followed by an
//operator (or the end)
try {
ParseTree rhs;
if (i < list.size() - 3) {
//Need to autoconcat
ParseTree ac = new ParseTree(new CFunction("__autoconcat__", Target.UNKNOWN), lhs.getFileOptions());
int index = i + 2;
ac.addChild(list.get(index));
list.remove(index);
while (true) {
if (list.size() > index && list.get(index).getData() instanceof CSymbol) {
//Add the next two children, (the symbol then the item)
//and continue.
ac.addChild(list.get(index));
ac.addChild(list.get(index + 1));
list.remove(index);
list.remove(index);
continue;
} else {
break;
}
}
//Set this subset into the correct slot, the rest of the
//code will grab it correctly that way.
list.add(i + 2, ac);
}
rhs = list.get(i + 2);
conversion.addChild(lhs);
conversion.addChild(rhs);
list.set(i + 2, conversion);
} catch (IndexOutOfBoundsException e) {
throw new ConfigCompileException("Invalid symbol listed", node.getTarget());
}
}
//Simple assignment now
ParseTree assign = new ParseTree(new CFunction("assign", node.getTarget()), node.getFileOptions());
ParseTree rhs;
if (i < list.size() - 3) {
//Need to autoconcat
ParseTree ac = new ParseTree(new CFunction("__autoconcat__", Target.UNKNOWN), lhs.getFileOptions());
int index = i + 2;
//As an incredibly special case, because (@value = !@value) is supported, and
//! hasn't been reduced yet, we want to check for that case, and if present, grab
//two symbols.
if(list.get(index).getData() instanceof CSymbol && list.get(index).getData().val().equals("!")){
ac.addChild(list.get(index));
list.remove(index);
}
ac.addChild(list.get(index));
list.remove(index);
while (true) {
if (list.size() > index && list.get(index).getData() instanceof CSymbol) {
//Add the next two children, (the symbol then the item)
//and continue.
ac.addChild(list.get(index));
ac.addChild(list.get(index + 1));
list.remove(index);
list.remove(index);
continue;
} else {
break;
}
}
//Set this subset into the correct slot, the rest of the
//code will grab it correctly that way.
list.add(i + 2, ac);
}
rhs = list.get(i + 2);
assign.addChild(lhs);
assign.addChild(rhs);
list.set(i, assign);
list.remove(i + 1);
list.remove(i + 1);
}
}
//postfix
for (int i = 0; i < list.size(); i++) {
ParseTree node = list.get(i);
if (node.getData() instanceof CSymbol) {
inSymbolMode = true;
}
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isPostfix()) {
if (i - 1 >= 0) {// && list.get(i - 1).getData() instanceof IVariable) {
CSymbol sy = (CSymbol) node.getData();
ParseTree conversion;
if (sy.val().equals("++")) {
conversion = new ParseTree(new CFunction("postinc", node.getTarget()), node.getFileOptions());
} else {
conversion = new ParseTree(new CFunction("postdec", node.getTarget()), node.getFileOptions());
}
conversion.addChild(list.get(i - 1));
list.set(i - 1, conversion);
list.remove(i);
i--;
}
}
}
if (inSymbolMode) {
try {
//look for unary operators
for (int i = 0; i < list.size() - 1; i++) {
ParseTree node = list.get(i);
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isUnary()) {
ParseTree conversion;
if (node.getData().val().equals("-") || node.getData().val().equals("+")) {
//These are special, because if the values to the left isn't a symbol,
//it's not unary
if ((i == 0 || list.get(i - 1).getData() instanceof CSymbol)
&& !(list.get(i + 1).getData() instanceof CSymbol)) {
if (node.getData().val().equals("-")) {
//We have to negate it
conversion = new ParseTree(new CFunction("neg", node.getTarget()), node.getFileOptions());
} else {
conversion = new ParseTree(new CFunction("p", node.getTarget()), node.getFileOptions());
}
} else {
continue;
}
} else {
conversion = new ParseTree(new CFunction(((CSymbol) node.getData()).convert(), node.getTarget()), node.getFileOptions());
}
// We actually need to get all the remaining children, and shove them into an autoconcat
List<ParseTree> ac = new ArrayList<>();
list.set(i, conversion);
for(int k = i + 1; k < list.size(); k++){
ParseTree m = list.get(k);
if(m.getData() instanceof CSymbol && ((CSymbol)m.getData()).isUnary()){
ac.add(m);
list.remove(k);
k--;
i--;
continue;
}
ac.add(m);
list.remove(k);
break;
}
conversion.addChild(optimizeSpecial(ac, returnSConcat));
}
}
//Exponential
for (int i = 0; i < list.size() - 1; i++) {
ParseTree next = list.get(i + 1);
if (next.getData() instanceof CSymbol) {
if (((CSymbol) next.getData()).isExponential()) {
ParseTree conversion = new ParseTree(new CFunction(((CSymbol) next.getData()).convert(), next.getTarget()), next.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
}
//Multiplicative
for (int i = 0; i < list.size() - 1; i++) {
ParseTree next = list.get(i + 1);
if (next.getData() instanceof CSymbol) {
CSymbol nextData = (CSymbol) next.getData();
if (nextData.isMultaplicative() && !nextData.isAssignment()) {
ParseTree conversion = new ParseTree(new CFunction(((CSymbol) next.getData()).convert(), next.getTarget()), next.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
}
//Additive
for (int i = 0; i < list.size() - 1; i++) {
ParseTree next = list.get(i + 1);
if (next.getData() instanceof CSymbol && ((CSymbol) next.getData()).isAdditive() && !((CSymbol) next.getData()).isAssignment()) {
ParseTree conversion = new ParseTree(new CFunction(((CSymbol) next.getData()).convert(), next.getTarget()), next.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
//relational
for (int i = 0; i < list.size() - 1; i++) {
ParseTree node = list.get(i + 1);
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isRelational()) {
CSymbol sy = (CSymbol) node.getData();
ParseTree conversion = new ParseTree(new CFunction(sy.convert(), node.getTarget()), node.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
//equality
for (int i = 0; i < list.size() - 1; i++) {
ParseTree node = list.get(i + 1);
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isEquality()) {
CSymbol sy = (CSymbol) node.getData();
ParseTree conversion = new ParseTree(new CFunction(sy.convert(), node.getTarget()), node.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
// default and
for (int i = 0; i < list.size() - 1; i++) {
ParseTree node = list.get(i + 1);
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isDefaultAnd()) {
CSymbol sy = (CSymbol) node.getData();
ParseTree conversion = new ParseTree(new CFunction(sy.convert(), node.getTarget()), node.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
// default or
for (int i = 0; i < list.size() - 1; i++) {
ParseTree node = list.get(i + 1);
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isDefaultOr()) {
CSymbol sy = (CSymbol) node.getData();
ParseTree conversion = new ParseTree(new CFunction(sy.convert(), node.getTarget()), node.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
//logical and
for (int i = 0; i < list.size() - 1; i++) {
ParseTree node = list.get(i + 1);
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isLogicalAnd()) {
CSymbol sy = (CSymbol) node.getData();
ParseTree conversion = new ParseTree(new CFunction(sy.convert(), node.getTarget()), node.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
//logical or
for (int i = 0; i < list.size() - 1; i++) {
ParseTree node = list.get(i + 1);
if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isLogicalOr()) {
CSymbol sy = (CSymbol) node.getData();
ParseTree conversion = new ParseTree(new CFunction(sy.convert(), node.getTarget()), node.getFileOptions());
conversion.addChild(list.get(i));
conversion.addChild(list.get(i + 2));
list.set(i, conversion);
list.remove(i + 1);
list.remove(i + 1);
i--;
}
}
} catch (IndexOutOfBoundsException e) {
throw new ConfigCompileException("Unexpected symbol (" + list.get(list.size() - 1).getData().val() + "). Did you forget to quote your symbols?", list.get(list.size() - 1).getTarget());
}
}
// Look for typed assignments
for(int k = 0; k < list.size(); k++){
if(list.get(k).getData() instanceof CClassType){
if(k == list.size() - 1){
throw new ConfigCompileException("Unexpected ClassType", list.get(k).getTarget());
}
if(list.get(k + 1).getData() instanceof CFunction){
switch(list.get(k + 1).getData().val()){
// closure is missing from this, because "closure" is both a ClassType and a keyword,
// and since keywords take priority over __autoconcat__, it will have already been
// handled by the time we reach this code.
case "assign":
case "proc":
// Typed assign/closure
ParseTree type = list.remove(k);
List<ParseTree> children = list.get(k).getChildren();
children.add(0, type);
list.get(k).setChildren(children);
break;
default:
throw new ConfigCompileException("Unexpected ClassType \"" + list.get(k).getData().val() + "\"", list.get(k).getTarget());
}
} else if(list.get(k + 1).getData() instanceof IVariable){
// Not an assignment, a random variable declaration though.
ParseTree node = new ParseTree(new CFunction(ASSIGN, list.get(k).getTarget()), list.get(k).getFileOptions());
node.addChild(list.get(k));
node.addChild(list.get(k + 1));
node.addChild(new ParseTree(CNull.UNDEFINED, list.get(k).getFileOptions()));
list.set(k, node);
list.remove(k + 1);
} else if(list.get(k + 1).getData() instanceof CLabel){
ParseTree node = new ParseTree(new CFunction(ASSIGN, list.get(k).getTarget()), list.get(k).getFileOptions());
ParseTree labelNode = new ParseTree(new CLabel(node.getData()), list.get(k).getFileOptions());
labelNode.addChild(list.get(k));
labelNode.addChild(new ParseTree(((CLabel)list.get(k + 1).getData()).cVal(), list.get(k).getFileOptions()));
labelNode.addChild(new ParseTree(CNull.UNDEFINED, list.get(k).getFileOptions()));
list.set(k, labelNode);
list.remove(k + 1);
} else {
throw new ConfigCompileException("Unexpected ClassType", list.get(k).getTarget());
}
}
}
//Look for a CEntry here
if (list.size() >= 1) {
ParseTree node = list.get(0);
if (node.getData() instanceof CLabel) {
ParseTree value = new ParseTree(new CFunction("__autoconcat__", node.getTarget()), node.getFileOptions());
for (int i = 1; i < list.size(); i++) {
value.addChild(list.get(i));
}
ParseTree ce = new ParseTree(new CFunction("centry", node.getTarget()), node.getFileOptions());
ce.addChild(node);
ce.addChild(value);
return ce;
}
}
//We've eliminated the need for __autoconcat__ either way, however, if there are still arguments
//left, it needs to go to sconcat, which MAY be able to be further optimized, but that will
//be handled in MethodScriptCompiler's optimize function. Also, we must scan for CPreIdentifiers,
//which may be turned into a function
if (list.size() == 1) {
return list.get(0);
} else {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getData().getCType() == Construct.ConstructType.IDENTIFIER) {
if (i == 0) {
//Yup, it's an identifier
CFunction identifier = new CFunction(list.get(i).getData().val(), list.get(i).getTarget());
list.remove(0);
ParseTree child = list.get(0);
if (list.size() > 1) {
child = new ParseTree(new CFunction("sconcat", Target.UNKNOWN), child.getFileOptions());
child.setChildren(list);
}
try {
Function f = (Function) FunctionList.getFunction(identifier);
ParseTree node = new ParseTree(f.execs(identifier.getTarget(), null, null, child), child.getFileOptions());
return node;
} catch (Exception e) {
throw new Error("Unknown function " + identifier.val() + "?");
}
} else {
//Hmm, this is weird. I'm not sure what condition this can happen in
throw new ConfigCompileException("Unexpected IDENTIFIER? O.o Please report a bug,"
+ " and include the script you used to get this error. At or around:", list.get(i).getTarget());
}
}
}
ParseTree tree;
FileOptions options = new FileOptions(new HashMap<String, String>());
if (!list.isEmpty()) {
options = list.get(0).getFileOptions();
}
if (returnSConcat) {
tree = new ParseTree(new CFunction("sconcat", Target.UNKNOWN), options);
} else {
tree = new ParseTree(new CFunction("concat", Target.UNKNOWN), options);
}
tree.setChildren(list);
return tree;
}
}
}
@api
@hide("This is only used for testing unexpected error handling.")
@noboilerplate
public static class npe extends DummyFunction {
@Override
public Integer[] numArgs() {
return new Integer[]{0, 1};
}
@Override
public boolean isRestricted() {
return true;
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException {
String s = null;
if(args.length == 1){
s = args[0].val();
}
if(s == null){
throw new NullPointerException();
} else {
throw new NullPointerException(s);
}
}
}
@api
@noprofile
@hide("This is only used for testing.")
public static class dyn extends DummyFunction {
@Override
public String docs() {
return "exception {[argument]} Registers as a dynamic component, for optimization testing; that is"
+ " to say, this will not be optimizable ever."
+ " It simply returns the argument provided, or void if none.";
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
if (args.length == 0) {
return CVoid.VOID;
}
return args[0];
}
}
@api
@hide("This is only used internally by the compiler, and will be removed at some point.")
public static class __cbracket__ extends DummyFunction implements Optimizable {
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(
OptimizationOption.OPTIMIZE_DYNAMIC);
}
@Override
public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException {
ParseTree node;
if (children.isEmpty()) {
node = new ParseTree(CVoid.VOID, fileOptions);
} else if (children.size() == 1) {
node = children.get(0);
} else {
//This shouldn't happen. If it does, it means that the autoconcat didn't already run.
throw new ConfigCompileException("Unexpected children. This appears to be an error, as __autoconcat__ should have already been processed. Please"
+ " report this error to the developer.", t);
}
return new ParseTree(new CBracket(node), fileOptions);
}
}
@api
@hide("This is only used internally by the compiler, and will be removed at some point.")
public static class __cbrace__ extends DummyFunction implements Optimizable {
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(
OptimizationOption.OPTIMIZE_DYNAMIC);
}
@Override
public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException {
throw new ConfigCompileException("Unexpected use of braces", t);
}
}
@api
@hide("This is more of a compiler feature, rather than a function, and so it is hidden from normal"
+ " documentation.")
public static class smart_string extends AbstractFunction implements Optimizable {
@Override
public Class<? extends CREThrowable>[] thrown() {
return null;
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
throw new UnsupportedOperationException(getName() + " should have been compiled out. If you are reaching this, an error has occured in the parser."
+ " Please report this error to the developers.");
}
@Override
public String getName() {
return "smart_string";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "none {string} This is a compiler construct, and is not normally used directly. It is created via double quoted strings.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public Set<Optimizable.OptimizationOption> optimizationOptions() {
return EnumSet.of(Optimizable.OptimizationOption.OPTIMIZE_DYNAMIC);
}
@Override
public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException {
if(children.size() != 1){
throw new ConfigCompileException(getName() + " can only take one parameter", t);
}
if(!(children.get(0).getData() instanceof CString)){
throw new ConfigCompileException("Only hardcoded strings may be passed into " + getName(), t);
}
String value = children.get(0).getData().val();
StringBuilder b = new StringBuilder();
boolean inBrace = false;
boolean inSimpleVar = false;
ParseTree root = new ParseTree(new CFunction(new StringHandling.concat().getName(), t), fileOptions);
for(int i = 0; i < value.length(); i++){
char c = value.charAt(i);
char c2 = (i + 1 < value.length() ? value.charAt(i + 1) : '\0');
if(c == '\\' && c2 == '@'){
b.append("@");
i++;
continue;
}
if(c == '@'){
if(c2 == '{'){
//Start of a complex variable
inBrace = true;
i++; // Don't include this
} else if(Character.isLetterOrDigit(c2) || c2 == '_'){
//Start of a simple variable
inSimpleVar = true;
} else {
// Loose @, this is a compile error
throw new ConfigCompileException("Unexpected \"@\" in string. If you want a literal at sign, escape it with \"\\@\".", t);
}
if(b.length() > 0){
root.addChild(new ParseTree(new CString(b.toString(), t), fileOptions));
b = new StringBuilder();
}
continue;
}
if(inSimpleVar && !(Character.isLetterOrDigit(c) || c == '_')){
// End of simple var. The buffer is the variable name.
String vname = b.toString();
b = new StringBuilder();
root.addChild(new ParseTree(new IVariable("@" + vname, t), fileOptions));
inSimpleVar = false;
}
if(inBrace && c == '}'){
// End of complex var. Still more parsing to be done though.
String complex = b.toString().trim();
b = new StringBuilder();
inBrace = false;
if(complex.matches("[a-zA-Z0-9_]+")){
//This is a simple variable name.
root.addChild(new ParseTree(new IVariable("@" + complex, t), fileOptions));
} else {
//Complex variable name, with arrays (or perhaps an error case)
}
continue;
}
b.append(c);
}
if(inBrace){
throw new ConfigCompileException("Missing end brace (}) in double string", t);
}
if(inSimpleVar){
root.addChild(new ParseTree(new IVariable("@" + b.toString(), t), fileOptions));
} else if(b.length() > 0){
root.addChild(new ParseTree(new CString(b.toString(), t), fileOptions));
}
if(root.numberOfChildren() == 1){
return root.getChildAt(0);
}
//throw new ConfigCompileException("Doubly quoted strings are not yet supported...", t);
return root;
}
}
}