Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving the way that the AST Printer prints to the log. #78

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 112 additions & 102 deletions expression-src/main/src/helpers/AstPrinter.cls
Original file line number Diff line number Diff line change
@@ -1,156 +1,166 @@
public with sharing class AstPrinter implements Visitor {
public void printAst(Expr expr) {
Object toPrint = expr.accept(this);
System.debug(toPrint);
System.debug(JSON.serializePretty(toPrint));
}

public Object visit(Expr.Binary binary) {
return parenthesize(
binary.operator.lexeme,
new List<Expr>{
binary.left,
binary.right
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'Binary',
'body' => new Map<String, Object> {
'left' => binary.left.accept(this),
'operator' => binary.operator.lexeme,
'right' => binary.right.accept(this)
}
);
};
return toPrint;
}

public Object visit(Expr.Grouping grouping) {
return parenthesize('group', new List<Expr>{
grouping.expression
});
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'Grouping',
'body' => new Map<String, Object> {
'expression' => grouping.expression.accept(this)
}
};
return toPrint;
}

public Object visit(Expr.Literal literal) {
return literal.value;
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'Literal',
'body' => new Map<String, Object> {
'value' => literal.value
}
};
return toPrint;
}

public Object visit(Expr.StringLiteral literal) {
String stringValue = '';
List<Object> stringsAndInterpolations = new List<Object>();
for (Object current : literal.stringsAndInterpolations) {
if (current instanceof String) {
stringValue += (String) current;
stringsAndInterpolations.add(current);
} else if (current instanceof Expr) {
stringValue += '${' + objToString(((Expr) current).accept(this)) + '}';
stringsAndInterpolations.add(((Expr) current).accept(this));
}
}
return stringValue;
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'StringLiteral',
'body' => new Map<String, Object> {
'value' => stringsAndInterpolations
}
};
return toPrint;
}

public Object visit(Expr.Variable variable) {
return 'Variable(' + variable.name.lexeme + ')';
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'Variable',
'body' => new Map<String, Object> {
'name' => variable.name.lexeme
}
};
return toPrint;
}

public Object visit(Expr.MergeField mergeField) {
return 'MergeField(' + mergeField.name.lexeme + ')';
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'MergeField',
'body' => new Map<String, Object> {
'name' => mergeField.name.lexeme
}
};
return toPrint;
}

public Object visit(Expr.Unary unary) {
return parenthesize(
unary.operator.lexeme,
new List<Expr>{
unary.right
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'Unary',
'body' => new Map<String, Object> {
'operator' => unary.operator.lexeme,
'right' => unary.right.accept(this)
}
);
};
return toPrint;
}

public Object visit(Expr.GetExpr getExpr) {
return parenthesize(
'GET:' + getExpr.field.lexeme,
new List<Expr>{
getExpr.objectExpr
List<Object> arguments = new List<Object>();
for (Expr expr : getExpr.arguments) {
arguments.add(expr.accept(this));
}
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'GetExpr',
'body' => new Map<String, Object> {
'object' => getExpr.objectExpr.accept(this),
'field' => getExpr.field.lexeme,
'arguments' => arguments
}
);
};
return toPrint;
}

public Object visit(Expr.FunctionCall function) {
return parenthesize(
'FN:' + function.functionName,
function.arguments
);
List<Object> arguments = new List<Object>();
for (Expr expr : function.arguments) {
arguments.add(expr.accept(this));
}
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'FunctionCall',
'body' => new Map<String, Object> {
'name' => function.functionName,
'arguments' => arguments
}
};
return toPrint;
}

public Object visit(Expr.ListLiteral listLiteral) {
StringBuilder builder = new StringBuilder();
builder.add('(').add('[');
List<Object> elements = new List<Object>();
for (Expr expr : listLiteral.elements) {
builder.add(' ');
builder.add(objToString(expr.accept(this)));
elements.add(expr.accept(this));
}
builder.add(']').add(')');
return builder.toString();
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'ListLiteral',
'body' => new Map<String, Object> {
'elements' => elements
}
};
return toPrint;
}

public Object visit(Expr.MapLiteral mapLiteral) {
StringBuilder builder = new StringBuilder();
builder.add('(').add('{');
for (Object element: mapLiteral.elements) {
builder.add(' ');
if (element instanceof Expr.Spread) {
builder.add(objToString(((Expr.Spread) element).accept(this)));
continue;
List<Object> elements = new List<Object>();
for (Object current : mapLiteral.elements) {
if (current instanceof Expr.KeyValue) {
Object key = ((Expr.KeyValue) current).key.accept(this);
Object value = ((Expr.KeyValue) current).value.accept(this);
Map<String, Object> keyValue = new Map<String, Object>{
'key' => key,
'value' => value
};
elements.add(keyValue);
} else if (current instanceof Expr.Spread) {
elements.add(((Expr.Spread) current).accept(this));
}

Expr.KeyValue keyValue = (Expr.KeyValue) element;
builder.add(objToString(keyValue.key.accept(this)));
builder.add(': ');
builder.add(objToString(keyValue.value.accept(this)));
}
builder.add('}').add(')');
return builder.toString();
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'MapLiteral',
'body' => new Map<String, Object> {
'elements' => elements
}
};
return toPrint;
}

public Object visit(Expr.Spread spread) {
return parenthesize(
'SPREAD',
new List<Expr>{
spread.expression
Map<String, Object> toPrint = new Map<String, Object>{
'type' => 'Spread',
'body' => new Map<String, Object> {
'expression' => spread.expression.accept(this)
}
);
}

private String parenthesize(String str, List<Expr> exprs) {
StringBuilder builder = new StringBuilder();
builder.add('(').add(str);
for (Expr expr : exprs) {
builder.add(' ');
builder.add(objToString(expr.accept(this)));
}
builder.add(')');
return builder.toString();
}

private static String objToString(Object obj) {
if (obj == null) {
return 'null';
}

if (obj instanceof Decimal) {
// There seems to be an Apex bug that throws a Javalang exception
// when trying to cast a generic Decimal object to a string, so if we encounter
// a Decimal, we cast it first to a Decimal, then to a string.
return String.valueOf((Decimal) obj);
}

return String.valueOf(obj.toString());
}

private with sharing class StringBuilder {
private String str;
private Integer index;

public StringBuilder() {
str = '';
index = 0;
}

public StringBuilder add(String s) {
str += s;
index += s.length();
return this;
}

public override String toString() {
return str;
}
};
return toPrint;
}
}
Loading