Мы используем ANTLR для синтаксического анализа C, и большая часть нашего кода имеет точечную нотацию для структур. Прошло некоторое время с тех пор, как я написал C, но насколько я помню, эти два утверждения синонимичны:
void hello() {
this->hello = "hello";
this.hello = "hello";
}
ANTLR может анализировать greeting->hello
без каких-либо проблем, однако точечная нотация вызывает следующую ошибку:
line 3:4 mismatched input 'this.hello' expecting '}'
Если мы поменяем операторы следующим образом:
void hello() {
this.hello = "hello";
this->hello = "hello";
}
Ошибки:
line 2:4 mismatched input 'this.hello' expecting {'__extension__', '__builtin_va_arg', '__builtin_offsetof', '__m128', '__m128d', '__m128i', '__typeof__', '__inline__', '__stdcall', '__declspec', '__asm', '__attribute__', '__asm__', 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while', '_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex', '_Generic', '_Noreturn', '_Static_assert', '_Thread_local', '(', '{', '}', '+', '++', '-', '--', '*', '&', '&&', '!', '~', ';', Identifier, Constant, DigitSequence, StringLiteral}
line 3:8 no viable alternative at input 'this->'
line 4:0 extraneous input '}' expecting <EOF>
Мы используем Грамматика C из Репозиторий ANTLR Grammars. При этом мы настроили его для обработки операторов #include
, и его можно увидеть здесь. Мы добавили эти два парсера и эти два лексера:
includeExpression
: IncludeDirective includedLibExpression '"'
| IncludeDirective includedLibExpression '>'
;
includedLibExpression
: IncludedHeaderDirective
;
IncludeDirective
: '#' Whitespace? 'include' Whitespace '"'
| '#' Whitespace? 'include' Whitespace '<'
;
IncludedHeaderDirective
: ('a'..'z' | 'A'..'Z' | '.' | '_' | '/')+
;
Затем, чтобы использовать новые парсеры, мы добавили в translationUnit
следующее. Чтобы еще больше запутать ситуацию, если строка с includeExpression
в translationUnit
закомментирована, мы все равно получим ошибки.
translationUnit
: externalDeclaration
| translationUnit externalDeclaration
| includeExpression+?
;
Конкретный парсер, который должен это уловить, таков:
postfixExpression
: primaryExpression
| postfixExpression '[' expression ']'
| postfixExpression '(' argumentExpressionList? ')'
| postfixExpression '.' Identifier
| postfixExpression '->' Identifier
| postfixExpression '++'
| postfixExpression '--'
| '(' typeName ')' '{' initializerList '}'
| '(' typeName ')' '{' initializerList ',' '}'
| '__extension__' '(' typeName ')' '{' initializerList '}'
| '__extension__' '(' typeName ')' '{' initializerList ',' '}'
;
Что меня действительно озадачивает, так это то, что точечное обозначение и обозначение стрелок идут друг за другом, но распознаются только обозначения стрелок.
Rup прав (я собирался сказать то же самое), но это не должно вызывать ошибку разбирать. Замечу, что ваше сообщение об ошибке не соответствует вашему коду: this.hello
вместо greeting.hello
. Вы уверены, что получили тот результат, который вы указали?
Ааа сегодня узнал. Это очень похожий код, который я просто написал в файле на своей машине, а здесь написал немного по-другому. Я обновлю ОП.
Вы изменили что-нибудь в грамматике или используете связанную грамматику как есть? Каким типом токена распознается this.hello
?
Где в грамматике обрабатывается точка с запятой? Я не вижу правила для этого
действительно, можете ли вы подтвердить, что если вы отмените утверждения, что ошибка происходит с .
, все еще
Вы, ребята, определенно что-то поняли ... Я забыл, что мы адаптировали грамматику для обработки операторов include для нашего варианта использования. Я добавил ту грамматику, которую мы изменили, до сути. Что касается отмены заявлений, я обновлю OP.
Вы добавили в грамматику следующее правило лексера:
IncludedHeaderDirective
: ('a'..'z' | 'A'..'Z' | '.' | '_' | '/')+
;
Этот шаблон соответствует строке this.hello
. Поэтому, когда лексер достигает строки 2 вашего ввода, он может либо применить правило Identifier
для сопоставления с this
, либо правило IncludeHeaderDirective
для сопоставления с this.hello
. Поскольку последний вариант является более длинным, он выбирается в соответствии с правилом максимального пережевывания.
Поскольку IncludedHeaderDirective
не является допустимым выражением, вы получите ошибку, которую делаете. Чтобы соответствовать правилу postfixExpression '.' Identifier
, this.hello
должен был быть токенизирован как Identifier, '.', Identifier
, но существование правила IncludedHeaderDirective
препятствует этому.
Я понимаю, в этом есть смысл. Как лучше всего настроить этот лексер, чтобы он по-прежнему находил .
в операторе #include
, но не затрагивал точечную нотацию?
@skylerl Препроцессор действительно должен быть отдельной фазой. Вы можете решить вашу текущую проблему, используя лексические режимы, но это все равно не приведет вас к стандартной реализации препроцессора. Обратите внимание, например, что что-то вроде int x = \n#include "file-that-only-contains-the-number-42"\n;
(где \n
должны представлять фактические символы новой строки) является допустимым C. Другими словами: файлы C не должны быть синтаксически действительными до окончания предварительной обработки, и вы не можете реализовать это в одной грамматике. как это.
Если я правильно помню и, как я уже сказал, мой C в настоящее время очень туманный, но препроцессор эффективно комбинирует связанный файл заголовка и файл C, который вы пишете, верно? Наш вариант использования - просто найти ссылки между FileA.c и FileB.h, если FileA.c имеет #include "FileB.h"
. Итак, вы говорите, что у нас должна быть грамматика для препроцессора и грамматика для правильного языка C?
@skylerl: Это частично зависит от сложности (зверства) кода, который вы анализируете. Полноценный компилятор C должен будет распознать все злоупотребления, которые может нанести ему непослушный программист. Ваш код может быть в порядке, будучи более слабым. (Сегодня утром я работал над самодельным синтаксическим анализатором C - кто-то в моем проекте создал его много лет назад для C90; его нужно немного обновить для C99, C11, C18! Это больно!) Синтаксис необработанного C не обязательно имеет большое отношение к предварительно обработанным C. #define IF if (
- #define THEN ) {
- #define ELSE } else {
- #define ENDIF }
.
@skylerl Препроцессор эффективно скопировал и вставил содержимое включенного файла в позицию директивы #include
, да. Настоящий синтаксический анализ затем выполняется после этого копирования и вставки (и других заданий препроцессора, таких как раскрытие макросов). Итак, да, для правильной реализации этого процесса потребуется одна грамматика для препроцессора и другая для предварительно обработанного кода. Затем препроцессор мог бы создать поток токенов, который мог бы обработать настоящий синтаксический анализатор C (или он мог бы просто записать предварительно обработанный C в файл, который затем анализируется реальным ...
... синтаксический анализатор C). Действительно ли вам все это нужно, зависит от того, что именно вы делаете, и, как уже указывал Джонатан Леффер, от того, нужно ли вашему инструменту обрабатывать все возможные файлы C. Если все, что вам нужно сделать, это выяснить, какой файл включает какие другие файлы, вам даже не нужно вообще разделять C-часть - только директиву препроцессора. Итак, все, что вам нужно, это грамматика для препроцессора.
«эти два утверждения синонимичны» - это не так, но это зависит от типа
greeting
, поэтому не должно иметь значения для грамматики. Вы используете точку, когда приветствие является структурой (или в C++ ссылкой на структуру), а стрелку, когда приветствие является указателем на структуру.