使用 RASCAL 拆分声明和初始化

Splitting declaration and initialization using RASCAL

我是 Rascal 的新手,正在尝试其 transformation/term 重写能力。

我想编写一个脚本来拆分如下声明:

整数 x = 5;

变成declaration/initializations喜欢:

int x; x = 5;

我该怎么做?假设我要转换的语言是 Java.

感谢您的帮助。

解决方案草图

好问题。有几种方法可以做到这一点,我将展示最简单的一种。请注意,您的示例不是最简单的示例,因为它需要从单个语句转换为语句列表(即,它不是类型保留)。

这里不多说一个完整的例子,解释如下。

module Decl

import IO;
import ParseTree;

// Decl, a trivial language, to demo simple program trafo

lexical Id  = [a-z][a-z0-9]* !>> [a-z0-9] \ Reserved;
lexical Natural = [0-9]+ ;
lexical String = "\"" ![\"]*  "\"";

layout Layout = WhitespaceAndComment* !>> [\ \t\n];

lexical WhitespaceAndComment 
   = [\ \t\n\r]
   ;

keyword Reserved = "int" | "str" | "if" | "then" | "else" | "fi" | "while" | "do" | "od";

start syntax Program 
   = {Statement  ";"}* body
   ;

syntax Type 
   = "int" 
   | "str"
   ;

syntax Statement 
   = Type tp Id var
   | Type tp Id var ":=" Expression exp
   | Id var ":=" Expression val                                                                      
   | "if" Expression cond "then" {Statement ";"}*  thenPart "else" {Statement ";"}* elsePart "fi"   
   | "while" Expression cond "do" {Statement ";"}* body "od"                                   
   ;  

syntax Expression 
   = Id name                                    
   | String string                          
   | Natural natcon                         
   | bracket "(" Expression e ")"                   
   > left ( Expression lhs "+" Expression rhs                                          
          | Expression lhs "-" Expression rhs  
          )
   ;

str trafo1 () {
    p = parse(#start[Program], "int x := 1").top;
    newBody = "";
    for(stat <- p.body){
        if((Statement) `<Type tp> <Id var> := <Expression exp>` := stat){
            newBody += "<tp> <var>; <var> := <exp>";
        } else {
            newBody += "<stat>";
        }
    }
    return newBody;
}

主要部分是简单语言的完整语法。实际转换由 trafo1 完成,它执行以下操作:

  1. 解析示例。
  2. 引入并初始化newBody(用于构建结果)。
  3. 迭代给定正文中的语句。
  4. 测试每个语句是否符合要求的形式。 如果为真,则附加转换后的语句。请注意,此处使用 字符串模板 来构建转换后的语句。 如果为假,则附加原始语句。
  5. Return 结果字符串。

讨论

解决方案的风格主要取决于您的目标。这里我们只是构建一个字符串。如果需要,您可以 return 解析后的字符串作为结果。

备选方案:

  1. 转换为具体的解析树(不是那么容易,因为仍然缺乏一些功能,但我们的目标是使其成为首选解决方案)。
  2. 首先转换为抽象语法树(AST),并在AST上进行转换。

希望这可以帮助您入门。

我这里还有一些代码示例,它们使用具体的语法匹配和替换来达到您想要的效果:

module JavaMatch

import lang::java::\syntax::Java15;

// this just replaces exactly these specific kinds of declarations, as the only statement in a block:
CompilationUnit splitInitializersSimple(CompilationUnit u) = visit(u) {
    case (Block) `{ int i = 0; }` => (Block) `{int i; i = 0;}`
};

// the next generalizes over any type, variable name or expression, but still one statement in a block:
CompilationUnit splitInitializersSingle(CompilationUnit u) = visit(u) {
    case (Block) `{ <Type t> <Id i> = <Expr e>; }` 
      => (Block) `{<Type t> <Id i>; <Id i> = <Expr e>;}`
};

// Now we allow more statements around the declaration, and we simply leave them where they are
CompilationUnit splitInitializersInContext(CompilationUnit u) = visit(u) {
    case (Block) `{ <BlockStm* pre> 
                 '  <Type t> <Id i> = <Expr e>; 
                 '  <BlockStm* post> 
                 '}` 
      => (Block) `{ <BlockStm* pre> 
                 '  <Type t> <Id i>; 
                 '  <Id i> = <Expr e>;
                 '  <BlockStm* post>
                 '}`
};

// But there could be more initializers in the same decl as well, as in int i, j = 0, k; :
CompilationUnit splitInitializersInContext2(CompilationUnit u) = visit(u) {
case (Block) `{ <BlockStm* pre> 
             '  <Type t> <{VarDec ","}+ a>, <Id i>= <Expr e>, <{VarDec ","}+ b>; 
             '  <BlockStm* post> 
             '}` 
  => (Block) `{ <BlockStm* pre> 
             '  <Type t> <{VarDec ","}+ a>, <Id i>, <{VarDec ","}+ b>; 
             '  <Id i> = <Expr e>;
             '  <BlockStm* post>
             '}`
};

// and now we add `innermost` such that not only the first but all occurrences are replaced:
CompilationUnit splitInitializersInContext2(CompilationUnit u) = innermost visit(u) {
case (Block) `{ <BlockStm* pre> 
             '  <Type t> <{VarDec ","}+ a>, <Id i>= <Expr e>, <{VarDec ","}+ b>; 
             '  <BlockStm* post> 
             '}` 
  => (Block) `{ <BlockStm* pre> 
             '  <Type t> <{VarDec ","}+ a>, <Id i>, <{VarDec ","}+ b>; 
             '  <Id i> = <Expr e>;
             '  <BlockStm* post>
             '}`
};

void doIt(loc file) {
  start[CompilationUnit] unit = parse(#start[CompilationUnit], file);
  unit.top = splitInitializersInContext(unit.top);
  writeFile(file, "<unit>");
}

最后的评论,因为这仍然不完全笼统:

  • 注意修饰符和数组类型;这只会添加更多变量以匹配并携带到右侧
  • 初始化器现在将以与语句相反的顺序出现,如果它们之间存在数据依赖性,这将中断
  • 请注意,规则的形状很大程度上取决于 Java 语法,因为我们在这里使用具体的语法匹配。它有助于在您创建此类代码时浏览语法。
  • 此代码在许多地方保留注释,但不是全部,特别是在重写的声明之间和在重写的 vardec 之间,使用此代码将丢失注释。