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