ANTLR источник для вывода

Я пытаюсь реализовать что-то вроде функции контрактов кода для JavaScript в качестве задания для одного из моих курсов.

У меня проблема в том, что я не могу найти способ вывести исходный файл прямо на консоль без изменения всей грамматики.

Кто-нибудь знает способ достичь этого?

Заранее спасибо.

Вот пример того, что я пытаюсь сделать:

function DoClear(num, arr, text){ 
  Contract.Requires<RangeError>(num > 0); 
  Contract.Requires(num < 1000); 
  Contract.Requires<TypeError>(arr instanceOf Array); 
  Contract.Requires<RangeError>(arr.length > 0 && arr.length <= 9); 
  Contract.Requires<ReferenceError>(text != null); 
  Contract.Ensures<RangeError>(text.length === 0); 

  // method body
  [...] 

  return text; 
}  

function DoClear(num, arr, text){ 
   if (!(num > 0)) 
     throw RangeError; 
   if (!(num < 1000)) 
     throw Error; 
   if (!(arr instanceOf Array)) 
     throw TypeError; 
   if (!(arr.length > 0 && arr.length <= 9)) 
     throw RangeError; 
   if (!(text != null)) 
     throw ReferenceError 

   // method body
   [...] 

   if (!(text.length === 0)) 
     throw RangeError 
   else 
     return text; 
} 

1 ответ

Решение

Есть несколько (незначительных) вещей, которые вы хотите рассмотреть:

  • игнорируйте строковые литералы, которые могут содержать ваш специальный синтаксис контракта;
  • игнорировать многострочные и однострочные комментарии, которые могут содержать ваши специальные Contract синтаксис;
  • игнорировать код как этот: var Requires = "Contract.Requires<RangeError>"; (т. е. обычный код JavaScript, "похожий" на ваш контракт-синтаксис);

Довольно просто принять во внимание вышеприведенные пункты, а также просто создать отдельные токены для всей строки контракта. Вы будете усложнять свою жизнь, если будете разбивать следующее на 4 разных токена. Contract.Requires<RangeError>(num > 0):

  • Contract
  • Requires
  • <RangeError>
  • (num > 0)

Поэтому проще всего создать из него один токен, а на этапе синтаксического анализа разделить токен на ".", "<" или же ">" с максимум 4 токенами (оставляя выражения, содержащие ".", "<" или же ">" как они есть).

Быстрая демонстрация того, что я описал выше, может выглядеть так:

grammar CCJS;

parse
  :  atom+ EOF
  ;

atom
  :  code_contract
  |  (Comment | String | Any) {System.out.print($text);}
  ;

code_contract
  :  Contract 
     {
       String[] tokens = $text.split("[.<>]", 4);
       System.out.print("if (!" + tokens[3] + ") throw " + tokens[2]);
     }
  ;

Contract
@init{
  boolean hasType = false;
}
@after{
  if(!hasType) {
    // inject a generic Error if this contract has no type
    setText(getText().replaceFirst("\\(", "<Error>("));
  }
}
  :  'Contract.' ('Requires' | 'Ensures') ('<' ('a'..'z' | 'A'..'Z')+ '>' {hasType=true;})? '(' ~';'+
  ;

Comment
  :  '//' ~('\r' | '\n')*
  |  '/*' .* '*/'
  ;

String
  :  '"' (~('\\' | '"' | '\r' | '\n') | '\\' . )* '"'
  ;

Any
  :  .
  ;

который вы можете проверить с помощью следующего класса:

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String src = 
        "/*                                                                  \n" +
        "   Contract.Requires to be ignored                                  \n" +
        "*/                                                                  \n" +
        "function DoClear(num, arr, text){                                   \n" +
        "  Contract.Requires<RangeError>(num > 0);                           \n" +
        "  Contract.Requires(num < 1000);                                    \n" +
        "  Contract.Requires<TypeError>(arr instanceOf Array);               \n" +
        "  Contract.Requires<RangeError>(arr.length > 0 && arr.length <= 9); \n" +
        "  Contract.Requires<ReferenceError>(text != null);                  \n" +
        "  Contract.Ensures<RangeError>(text.length === 0);                  \n" +
        "                                                                    \n" +
        "  // method body                                                    \n" +
        "  // and ignore single line comments, Contract.Ensures              \n" +
        "  var s = \"Contract.Requires\"; // also ignore strings             \n" +
        "                                                                    \n" +
        "  return text;                                                      \n" +
        "}                                                                   \n";

    CCJSLexer lexer = new CCJSLexer(new ANTLRStringStream(src));
    CCJSParser parser = new CCJSParser(new CommonTokenStream(lexer));
    parser.parse();
  }
}

Если вы запустите Main класс выше, на консоль будет выведено следующее:

/*
   Contract.Requires to be ignored
*/
function DoClear(num, arr, text){
  if (!(num > 0)) throw RangeError;
  if (!(num < 1000)) throw Error;
  if (!(arr instanceOf Array)) throw TypeError;
  if (!(arr.length > 0 && arr.length <= 9)) throw RangeError;
  if (!(text != null)) throw ReferenceError;
  if (!(text.length === 0)) throw RangeError;

  // method body
  // and ignore single line comments, Contract.Ensures
  var s = "Contract.Requires"; // also ignore strings

  return text;
}

НО...

... Я понимаю, что это не то, что вы ищете: RangeError не помещается в конец вашей функции. И это будет сложно: функция может иметь несколько return s, и, вероятно, будет иметь несколько блоков кода { ... } затрудняя узнать, где } это конец function, Таким образом, вы не знаете, где именно это ввести RangeError -проверять. По крайней мере, не с наивным подходом, как я продемонстрировал.

Единственный надежный способ реализовать такую ​​вещь - это получить приличную грамматику JavaScript, добавить к ней свои собственные правила контракта, переписать AST, который создает синтаксический анализатор, и, наконец, создать новый AST в дружественном формате: это не тривиальная задача по меньшей мере!

В ANTLR Wiki есть различные грамматики ECMA/JS, но действуйте осторожно: они являются пользовательскими грамматиками и могут содержать ошибки (возможно, в этом случае [1]!).

Если вы решили разместить RangeError там где это надо переписать, вот так:

function DoClear(num, arr, text){ 
  Contract.Requires<RangeError>(num > 0); 
  ...

  // method body
  ...

  Contract.Ensures<RangeError>(text.length === 0);

  return text; 
}  

что приведет к:

function DoClear(num, arr, text){ 
   if (!(num > 0)) throw RangeError; 
   ...

   // method body
   ...

   if (!(text.length === 0)) 
     throw RangeError 

   return text; 
} 

тогда вам не нужно анализировать все тело метода, и вы можете получить хак, как я и предлагал.

Удачи!

[1] в последний раз, когда я проверял эти грамматики скриптов ECMA/JS, ни один из них не обрабатывал литералы регулярных выражений, /pattern/ Правильно, делая их на мой взгляд подозрительными.

Другие вопросы по тегам