- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在尝试为类似Java的语言编写多种语言的翻译器。
现在,我面临两个问题:
首先是按照一系列基本操作分解复杂的表达式,然后将其翻译为目标语言。
例如,我的起始语言可以是:
var a = (ln(b) + avg(c))*2
var x1 = log_N(b);
var x2 = average(c);
var x3 = sum(x1, x2);
var a = multiply(x3, 2);
var a = (ln(b) + avg(c))*2
log_N(x1, b);
average(x2, c);
sum(x3, x1, x2);
multiply(a, x3, 2);
最佳答案
我认为我必须使用Tree解析器,但是我不确定如何将其与StringTemplate集成。
将StringTemplate集成到树解析器中基本上与将其集成到令牌解析器中相同:将output
选项定义为template
,然后相应地编写规则生成。
以下是使用模板进行输出的小树语法。请注意,此语法与the one I described in a previous answer之间唯一有意义的区别是,此语法在树节点而不是令牌上运行。模板的工作原理相同。
tree grammar AstToTemplateParser;
options {
output = template;
tokenVocab = JavaLikeToAst;
ASTLabelType = CommonTree;
}
program
: ^(PROGRAM decls+=decl+) -> write(text={$decls})
;
decl
: ^(DECL ID ^(op args+=arg*)) -> assign(name={$ID.text}, op={$op.st}, args={$args})
| ^(DECL ID ^(CALL method args+=arg*)) -> assign(name={$ID.text}, op={$method.st}, args={$args})
;
arg
: ID -> write(text={$ID.text})
| INT -> write(text={$INT.text})
;
method
: ID -> op(name={$ID.text})
;
op : STAR -> op(name={$STAR.text})
| DIV -> op(name={$DIV.text})
| PLUS -> op(name={$PLUS.text})
| MINUS -> op(name={$MINUS.text})
;
x1
,
x2
和
x3
。
var a = (ln(b) + avg(c))*2
var var0 = log_N(b);
var var1 = average(c);
var var2 = add(var0, var1);
var a = multiply(var2, 2);
log_N(var0, b);
average(var1, c);
add(var2, var0, var1);
multiply(a, var2, 2);
grammar JavaLikeToAst;
options {
output = AST;
}
tokens {
PROGRAM; DECL; CALL;
}
compilationUnit : statement* EOF -> ^(PROGRAM statement*);
statement : decl;
decl : VAR ID EQ expr -> ^(DECL ID expr);
expr : add_expr;
add_expr : mul_expr ((PLUS|MINUS)^ mul_expr)*;
mul_expr : call_expr ((STAR|DIV)^ call_expr)*;
call_expr : ID LPAR arglist? RPAR -> ^(CALL ID arglist?)
| primary_expr;
arglist : expr (COMMA! expr)*;
primary_expr : ID | INT | LPAR! expr RPAR!;
VAR : 'var';
ID : ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;
INT : ('0'..'9')+;
COMMA : ',';
SEMI : ';';
LCUR : '{';
RCUR : '}';
LPAR : '(';
RPAR : ')';
EQ : '=';
PLUS : '+';
MINUS : '-';
STAR : '*';
DIV : '/';
WS : (' '|'\t'|'\f'|'\r'|'\n'){skip();};
tree grammar AstToFlatAstParser;
options {
output = AST;
tokenVocab = JavaLikeToAst;
ASTLabelType = CommonTree;
filter = true;
}
@header {
import java.util.HashMap;
}
@members {
private int currentId = 0;
private HashMap<Integer, Object> exprs = new HashMap<Integer, Object>();
private boolean newDecls = false;
private int nextId() {
return currentId++;
}
private Object generateId(int id) {
return adaptor.create(ID, "var" + id);
}
private void saveExpr(int id, Object expr){
newDecls = true;
exprs.put(id, expr);
}
private Object buildNewDecls() {
Object newDecls = adaptor.nil();
for (int i = 0; i < currentId; ++i){
if (!exprs.containsKey(i)){
continue; //This id was generated but not used.
}
Object expr = exprs.get(i);
Object decl = adaptor.create(DECL, tokenNames[DECL]);
adaptor.addChild(decl, adaptor.create(ID, "var" + i));
adaptor.addChild(decl, expr);
adaptor.addChild(newDecls, decl);
}
exprs.clear();
return newDecls;
}
}
bottomup
: exit_program
| exit_op
;
exit_op
@init {
int myId = nextId();
}
: ^(binary_op reduced reduced)
{$start.parent != null && $start.parent.getType() != DECL}?
{saveExpr(myId, $start);}
-> {generateId(myId)}
| ^(CALL ID .*)
{$start.parent != null && $start.parent.getType() != DECL}?
{saveExpr(myId, $start);}
-> {generateId(myId)}
;
binary_op : STAR | DIV | PLUS | MINUS;
reduced : ID | INT;
exit_program
//Only rebuild PROGRAM if a new declaration is going to be built, that is, when "newDecls" is true.
//Otherwise PROGRAM is considered changed when it isn't and the processing never ends.
: {newDecls}? ^(PROGRAM old+=.*) {newDecls = false;}
-> ^(PROGRAM {buildNewDecls()} $old*)
;
filter
树语法,因此规则
bottomup
和
topdown
是入口点。在这种情况下,仅需要
bottomup
,因此未指定
topdown
。重复规则
bottomup
直到输出树不变,这对我们来说意味着没有更多的声明要产生并且树已完全展平。
exit_program
是新声明被写到AST的地方。我正在使用语义谓词(
{newDecls}?
)以确保仅在添加新声明时才修改
PROGRAM
。还记得我说过
bottomup
叫什么直到没有更多更改吗?没有此语义谓词,
exit_program
将始终修改
PROGRAM
,并且树解析将永远不会停止处理
bottomup
。对于这种特殊情况,这是一种粗略的解决方法,但是它可以工作。新的声明会插入
PROGRAM
的开头,以确保它们在被引用之前就出现了。在预期的十行后定义
x1
不好。
exit_op
用声明(例如
ln(b)
)替换了表达式(例如
var0
)。如果满足以下条件之一,则替换表达式:
DECL
节点的子代。
var a = 1 + 2
不会更改,因为
1 + 2
是声明的子级。之所以更改
var b = a + (2 + c)
是因为
(2 + c)
具有两个“精简”操作数,并且不是
DECL
节点的子代(它是
+
中
a + ...
的子代)。
CALL
,它不是
DECL
节点的子级。
var a = ln(b)
未被触动,但是
var a = ln(b) + 3
被更改,因为
ln(b)
是
+
的子级。
exprs
中,然后将其替换为ID。当规则调用
exit_program
时,它将在
buildNewDecls
规则中重构。
buildNewDecls
仅使用解析器的内置
TreeAdaptor
成员(名为
adaptor
)来生成出现在扁平化AST中的
DECL
节点。适配器方法的Javadoc可以很好地解释调用的作用,因此我将不赘述。
variable = method(arg0, arg1)
或
method(variable, arg0, arg1)
之类的文本。关键是要确保输入是平坦的,并且模板可以接收与声明有关的所有内容。
public class JavaLikeToAstTest {
public static void main(String[] args) throws Exception {
final String code = "var a = (ln(b) + avg(c))*2";
CharStream input = new ANTLRStringStream(code);
JavaLikeToAstLexer lexer = new JavaLikeToAstLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
JavaLikeToAstParser parser = new JavaLikeToAstParser(tokens);
JavaLikeToAstParser.compilationUnit_return result = parser
.compilationUnit();
if (lexer.getNumberOfSyntaxErrors() > 0 || parser.getNumberOfSyntaxErrors() > 0) {
throw new Exception("Syntax Errors encountered!");
}
CommonTree tree = (CommonTree) result.tree;
System.out.printf("Baseline AST: %s%n%n", tree.toStringTree());
tree = flatten(tree);
System.out.printf("Flattened AST: %s%n%n", tree.toStringTree());
translate(tree, "AstToPlsql.stg");
translate(tree, "AstToGlobal.stg");
}
private static CommonTree flatten(CommonTree tree) {
AstToFlatAstParser parser = new AstToFlatAstParser(
new CommonTreeNodeStream(tree));
return (CommonTree) parser.downup(tree, true);
}
private static void translate(CommonTree tree, String templateResourceName)
throws Exception {
AstToTemplateParser parser = new AstToTemplateParser(
new CommonTreeNodeStream(tree));
InputStream stream = JavaLikeToTemplateTest.class
.getResourceAsStream(templateResourceName);
Reader reader = new InputStreamReader(stream);
parser.setTemplateLib(new StringTemplateGroup(reader));
reader.close();
stream.close();
System.out.printf("Result for %s%n%n%s%n%n", templateResourceName,
parser.program().st.toString());
}
group AstToGlobal;
methods ::= ["*":"multiply", "/":"divide", "+":"add", "-":"subtract", "avg":"average", "ln":"log_N", default:key]
assign(name, op, args) ::= <<var <name> = <op>(<args;separator=", ">) >>
op(name) ::= "<methods.(name)>"
write(text) ::= << <text;separator="\n"> >>
group AstToPlsql;
methods ::= ["*":"multiply", "/":"divide", "+":"add", "-":"subtract", "avg":"average", "ln":"log_N", default:key]
assign(name, op, args) ::=<< <op>(<name>, <args;separator=", ">) >>
op(name) ::= "<methods.(name)>"
write(text) ::= << <text;separator="\n"> >>
Baseline AST: (PROGRAM (DECL a (* (+ (CALL ln b) (CALL avg c)) 2)))
(CALL ln b) -> var0
(CALL avg c) -> var1
(+ var0 var1) -> var2
(PROGRAM (DECL a (* var2 2))) -> (PROGRAM (DECL var0 (CALL ln b)) (DECL var1 (CALL avg c)) (DECL var2 (+ var0 var1)) (DECL a (* var2 2)))
Flattened AST: (PROGRAM (DECL var0 (CALL ln b)) (DECL var1 (CALL avg c)) (DECL var2 (+ var0 var1)) (DECL a (* var2 2)))
Result for AstToPlsql.stg
log_N(var0, b )
average(var1, c )
add(var2, var0 , var1 )
multiply(a, var2 , 2 )
Result for AstToGlobal.stg
var var0 = log_N(b )
var var1 = average(c )
var var2 = add(var0 , var1 )
var a = multiply(var2 , 2 )
AstToTemplate.g
或模板中没有代码可以处理像
var a = 3
这样的简单任务,但是我认为使用op / method任务作为指导来添加代码来处理它很容易。
关于translation - 在基本操作中分解表达式:ANTLR + StringTemplate,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13817046/
我正在用 yacc/bison 编写一个简单的计算器。 表达式的语法看起来有点像这样: expr : NUM | expr '+' expr { $$ = $1 + $3; } | expr '-'
我开始学习 lambda 表达式,并在以下情况下遇到了以下语句: interface MyNumber { double getValue(); } MyNumber number; nu
这两个 Linq 查询有什么区别: var result = ResultLists().Where( c=> c.code == "abc").FirstOrDefault(); // vs. va
如果我们查看 draft C++ standard 5.1.2 Lambda 表达式 段 2 说(强调我的 future ): The evaluation of a lambda-expressio
我使用的是 Mule 4.2.2 运行时、studio 7.5.1 和 Oracle JDK 1.8.0_251。 我在 java 代码中使用 Lambda 表达式,该表达式由 java Invoke
我是 XPath 的新手。我有网页的html源 http://london.craigslist.co.uk/com/1233708939.html 现在我想从上面的页面中提取以下数据 完整日期 电子
已关闭。这个问题是 off-topic 。目前不接受答案。 想要改进这个问题吗? Update the question所以它是on-topic用于堆栈溢出。 已关闭10 年前。 Improve th
我将如何编写一个 Cron 表达式以在每天上午 8 点和下午 3:30 触发?我了解如何创建每天触发一次的表达式,而不是在多个设定时间触发。提前致谢 最佳答案 你应该只使用两行。 0 8 * * *
这个问题已经有答案了: What do 3 dots next to a parameter type mean in Java? (9 个回答) varargs and the '...' argu
我是 python 新手,在阅读 BeautifulSoup 教程时,我不明白这个表达式“[x for x in titles if x.findChildren()][:-1]”我不明白?你能解释一
(?:) 这是一个有效的 ruby 正则表达式,谁能告诉我它是什么意思? 谢谢 最佳答案 正如其他人所说,它被用作正则表达式的非捕获语法,但是,它也是正则表达式之外的有效 ruby 语法。 在
这个问题在这里已经有了答案: Why does ++[[]][+[]]+[+[]] return the string "10"? (10 个答案) 关闭 8 年前。 谁能帮我处理这个 JavaSc
这个问题在这里已经有了答案: What is the "-->" operator in C++? (29 个答案) Java: Prefix/postfix of increment/decrem
这个问题在这里已经有了答案: List comprehension vs. lambda + filter (16 个答案) 关闭 10 个月前。 我不确定我是否需要 lambda 或其他东西。但是,
C 中的 assert() 函数工作原理对我来说就像一片黑暗的森林。根据这里的答案https://stackoverflow.com/a/1571360 ,您可以使用以下构造将自定义消息输出到您的断言
在this页,John Barnes 写道: If the conditional expression is the argument of a type conversion then effec
我必须创建一个调度程序,它必须每周从第一天上午 9 点到第二天晚上 11 点 59 分运行 2 天(星期四和星期五)。为此,我需要提供一个 cron 表达式。 0-0 0-0 9-23 ? * THU
我正在尝试编写一个 Linq 表达式来检查派生类中的属性,但该列表由来自基类的成员组成。下面的示例代码。以“var list”开头的 Process 方法的第二行无法编译,但我不确定应该使用什么语法来
此 sed 表达式将输入字符串转换为两行输出字符串。两条输出行中的每一行都由输入的子串组成。第一行需要转换成大写: s:random_stuff\(choice1\|choice2\){\([^}]*
我正在使用 Quartz.Net 在我的应用程序中安排我的工作。我只是想知道是否可以为以下场景构建 CRON 表达式: Every second between 2:15AM and 5:20AM 最
我是一名优秀的程序员,十分优秀!