#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, я предполагаю, что ваш комментарий является последующим вопросом? Если это так, не стесняйтесь создавать новый вопрос по этому поводу.