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

#antlr #grammar #lexer

#antlr #грамматика #лексер

Вопрос:

Я пытаюсь реализовать что-то вроде функции Code Contracts для 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 amp;amp; 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 amp;amp; arr.length <= 9)) 
     throw RangeError; 
   if (!(text != null)) 
     throw ReferenceError 

   // method body
   [...] 

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

Комментарии:

1. @sarnold, принцип может быть применим, но опубликованная вами ссылка относится к ANTLR v2: ее синтаксис сильно отличается от сегодняшней версии ANTLR, поэтому, скорее всего, это не поможет OP (если он / она не использует v2 … :)).

2. Как выглядит ваша грамматика и вызывающее программирование в настоящее время?

3. @Bart, спасибо за уловку; URL выглядел настолько чистым, что я полностью пропустил 2 в домене.

4. @sarnold спасибо за вашу помощь, но, как сказал Барт, я использую версию v3. Следовало бы упомянуть в вопросе.

5. @MateusBR, я должен был уделить больше внимания URL, который я нашел — я предположил, что это был v3. 🙂 Моя ошибка. хе-хе.

Ответ №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 amp;amp; 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 amp;amp; 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/ должным образом, что, на мой взгляд, делает их подозрительными.

Комментарии:

1. я смог разработать механизм возврата. я контролирую область действия с помощью стека и списка контрактных действий, при каждом возврате я распечатываю контракты. единственная проблема, с которой я сталкиваюсь, заключается в том, что анализатор выдает ошибки каждый раз, когда находит что-то вроде ‘re *’, это потому, что я определил правило для ‘return’.

2. @MateusBR, я предполагаю, что ваш комментарий является последующим вопросом? Если это так, не стесняйтесь создавать новый вопрос по этому поводу.