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/
Правильно, делая их на мой взгляд подозрительными.