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

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

1 голос
/ 07 ноября 2011

Я пытаюсь реализовать что-то вроде функции Кодовые контракты для 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 Ответ

0 голосов
/ 07 ноября 2011

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

  • игнорировать строковые литералы, которые могут содержать ваш специальный синтаксис контракта;
  • игнорировать мульти- и одиночныестроковые комментарии, которые могут содержать ваш специальный 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 с и, вероятно, иметь несколько кодовых блоков { ... }, что затрудняет определение того, где находится }, заканчивающийся 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/, правильно, что делает их, по моему мнению,подозреваемый.

...