Development/KDevelop-PG-Qt Introduction: Difference between revisions
Neverendingo (talk | contribs) m (Text replace - "</code>" to "</syntaxhighlight>") |
(Standardisation of markup started - very incomplete) |
||
Line 1: | Line 1: | ||
{{Warning|1=''Note to Translators'' <br /> Please do not translate using the old-style language bar, as changes in the master page are not tracked by the old system}} | |||
{{Template:I18n/Language Navigation Bar|Development/KDevelop-PG-Qt Introduction}} | {{Template:I18n/Language Navigation Bar|Development/KDevelop-PG-Qt Introduction}} | ||
== Preface == | |||
'''KDevelop-PG-Qt''' is the parser-generator from ''KDevplatform''. It is used for some ''KDevelop-languagesupport-plugins'' (Ruby, PHP, Java...). | |||
It uses Qt classes internally. There's also the original '''KDevelop-PG parser''', which used types from the STL, but has since been superceeded by '''KDevelop-PG-Qt'''. Most of the features are the same, though it could be that the ...-Qt parser generator is more up to date and feature rich than the plain STL style generator. The ...-Qt version should be used to write parsers for KDevelop language plugins. | |||
This document is not supposed to be a full-fledged and in-depth resource for all parts of KDevelop-PG. Instead it is intended to be a short introduction and, more importantly, a reference for developers. | == In-Depth information == | ||
This document is not supposed to be a full-fledged and in-depth resource for all parts of '''KDevelop-PG'''. Instead it is intended to be a short introduction and, more importantly, a reference for developers. | |||
To get an in-depth introduction, read Jakob Petsovits' excellent Bachelor thesis. You find it in the Weblinks section at the bottom of this page. | To get an in-depth introduction, read Jakob Petsovits' excellent Bachelor thesis. You find it in the Weblinks section at the bottom of this page. | ||
= The Application = | == The Application == | ||
== Usage == | === Usage === | ||
You can find KDevelop-PG-Qt in [https://projects.kde.org/projects/extragear/kdevelop/utilities/kdevelop-pg-qt git]. Four example packages are also included in the sources.<br> | You can find '''KDevelop-PG-Qt''' in [https://projects.kde.org/projects/extragear/kdevelop/utilities/kdevelop-pg-qt git]. Four example packages are also included in the sources.<br /> | ||
To download it try: | To download it try: | ||
{{Input|1=git clone git://anongit.kde.org/kdevelop-pg-qt.git}} | |||
or: git clone kde:kdevelop-pg-qt (when having setup git with kde:-prefix) | or: | ||
{{Input|1=git clone kde:kdevelop-pg-qt}} (when having setup '''git''' with kde:-prefix) | |||
The program itself requests a .g file, a so called grammar, as input: | The program itself requests a .g file, a so called grammar, as input: | ||
{{Input|1=./kdev-pg-qt --output=''prefix'' syntax.g}} | |||
The value of the ''--output'' switch decides the prefix of the output files and additionally the namespace for the generated code. | The value of the ''--output'' switch decides the prefix of the output files and additionally the namespace for the generated code. | ||
Kate provides elementary highlighting for KDevelop-PG-Qt's grammar-files. | '''Kate''' provides elementary highlighting for '''KDevelop-PG-Qt's''' grammar-files. | ||
== Output Format == | === Output Format === | ||
While evaluating the grammar and generating its parser files, the application will output information about so called ''conflicts'' to STDOUT. As said above, the following files will actually be prefixed. | While evaluating the grammar and generating its parser files, the application will output information about so called ''conflicts'' to STDOUT. As said above, the following files will actually be prefixed. | ||
=== ast.h === | ==== ast.h ==== | ||
AST stands for [http://en.wikipedia.org/wiki/Abstract_syntax_tree | AST stands for [http://en.wikipedia.org/wiki/Abstract_syntax_tree Abstract Syntax Tree]. It defines the data structure in which the parse tree is saved. Each node is a struct with the postfix ''Ast'', which contains members that point to any possible sub elements. | ||
=== parser.h and parser.cpp === | ==== parser.h and parser.cpp ==== | ||
One important part of ''parser.h'' is the definition of the parser tokens, the ''TokenType'' enum. The TokenStream of your lexer should to use this. You have to write your own lexer or let one generate by Flex. See also the part about Tokenizers/Lexers below. | One important part of ''parser.h'' is the definition of the parser tokens, the ''TokenType'' enum. The TokenStream of your lexer should to use this. You have to write your own lexer or let one generate by Flex. See also the part about Tokenizers/Lexers below. | ||
Having the token stream available, you create your root item and call the parser on the parse method for the top-level AST item, e.g. DocumentAst* => parseDocument(&root). On success, root will contain the AST.<br> | Having the token stream available, you create your root item and call the parser on the parse method for the top-level AST item, e.g. DocumentAst* => parseDocument(&root). On success, root will contain the AST.<br /> | ||
The parser will have one parse method for each possible node of the AST. This is nice for e.g. an expression parser or parsers that should only parse a sub-element of a full document. | The parser will have one parse method for each possible node of the AST. This is nice for e.g. an expression parser or parsers that should only parse a sub-element of a full document. | ||
=== visitor.h and visitor.cpp === | ==== visitor.h and visitor.cpp ==== | ||
The Visitor class provides an abstract interface to walk the AST. Most of the time you don't need to use this directly, the DefaultVisitor takes some work off your shoulders. | The Visitor class provides an abstract interface to walk the AST. Most of the time you don't need to use this directly, the DefaultVisitor takes some work off your shoulders. | ||
=== defaultvisitor.h and defaultvisitor.cpp === | ==== defaultvisitor.h and defaultvisitor.cpp ==== | ||
The DefaultVisitor is an implementation of the abstract Visitor interface and automatically visits each node in the AST. Hence, this is probably the best candidate for a base class for your personal visitors. Most language plugins use these in their Builder classes to create the DUChain.<br> | The DefaultVisitor is an implementation of the abstract Visitor interface and automatically visits each node in the AST. Hence, this is probably the best candidate for a base class for your personal visitors. Most language plugins use these in their Builder classes to create the DUChain.<br /> | ||
== Command-Line-Options == | === Command-Line-Options === | ||
*--namespace=''namespace'' - sets the C++ namespace for the generated sources independently from the file prefix. When this option is set, you can also use / in the --ouput option<br> | * --namespace=''namespace'' - sets the C++ namespace for the generated sources independently from the file prefix. When this option is set, you can also use / in the --ouput option<br> | ||
*--no-ast - don't create the ast.h file, more to that below<br> | * --no-ast - don't create the ast.h file, more to that below<br> | ||
*--debug-visitor - generates a debug visitor that prints the AST<br> | * --debug-visitor - generates a debug visitor that prints the AST<br> | ||
*--serialize-visitor - generates code for serialization via a QIODevice | * --serialize-visitor - generates code for serialization via a QIODevice | ||
*--terminals - all tokens will be written into the file ''kdev-pg-terminals'' | * --terminals - all tokens will be written into the file ''kdev-pg-terminals'' | ||
*--symbols - all possible nodes from the AST (not the leafs) will be written into the file ''kdev-pg-symbols'' | * --symbols - all possible nodes from the AST (not the leafs) will be written into the file ''kdev-pg-symbols'' | ||
*--rules - all grammar rules with informationen about their syntactic correlations will be written into a file called ''kdev-pg-rules''. useful for debugging and solving conflicts | * --rules - all grammar rules with informationen about their syntactic correlations will be written into a file called ''kdev-pg-rules''. useful for debugging and solving conflicts | ||
*--token-text - generates a function to map token-numbers onto token-names | * --token-text - generates a function to map token-numbers onto token-names | ||
*--help - print usage information<br> | * --help - print usage information<br> | ||
= Tokenizers/Lexers | == Tokenizers/Lexers == | ||
As mentioned, KDevelop-PG-Qt requires a Tokenizer. You can either let KDevelop-PG-Qt generate one for you, write one per hand, as it has been done for C++ and PHP, or you can use external tools like Flex. | As mentioned, KDevelop-PG-Qt requires a Tokenizer. You can either let KDevelop-PG-Qt generate one for you, write one per hand, as it has been done for C++ and PHP, or you can use external tools like Flex. | ||
The tokenizer's job, in principle, boils down to: | The tokenizer's job, in principle, boils down to: | ||
*converting keywords and chars with special meanings to tokens<br> | * converting keywords and chars with special meanings to tokens<br> | ||
*converting literals and identifier to tokens<br> | * converting literals and identifier to tokens<br> | ||
*clean out anything that doesn't change the semantics, e.g. comments or whitespace (the latter of course not in Python)<br> | * clean out anything that doesn't change the semantics, e.g. comments or whitespace (the latter of course not in Python)<br> | ||
*while doing the above, handling character encoding (we recommend using UTF8 as much as possible)<br> | * while doing the above, handling character encoding (we recommend using UTF8 as much as possible)<br> | ||
The rest, e.g. actually building the tree and evaluating the semantics, is part of the parser and the AST visitors.<br> | The rest, e.g. actually building the tree and evaluating the semantics, is part of the parser and the AST visitors.<br> | ||
== Using KDevelop-PG-Qt == | === Using KDevelop-PG-Qt === | ||
KDevelop-PG-Qt can generate lexers being well integrated into its architecture (you do not have to create a token-stream-class invoking lex or something like that). See examples/foolisp in the code for a simplistic example. | KDevelop-PG-Qt can generate lexers being well integrated into its architecture (you do not have to create a token-stream-class invoking lex or something like that). See examples/foolisp in the code for a simplistic example. | ||
'''TODO''' | '''TODO''' | ||
== Using Flex == | === Using Flex === | ||
With the existing examples, it shouldn't be too hard to write such a lexer. Between most languages, especially those ''"inheriting"'' C, there are many common syntactic elements. Especially comments and literals can be handled just the same way over and over again. Adding a simple token is trivial: | With the existing examples, it shouldn't be too hard to write such a lexer. Between most languages, especially those ''"inheriting"'' C, there are many common syntactic elements. Especially comments and literals can be handled just the same way over and over again. Adding a simple token is trivial: | ||
{{Input|1="special-command" return Parser::Token_SPECIAL_COMMAND; }} | |||
That's pretty much it, take a look at eg. ''java.ll'' for an excellent example. | That's pretty much it, take a look at eg. ''java.ll'' for an excellent example. | ||
= How to write Grammar-Files = | == How to write Grammar-Files == | ||
== Chomsky Type-2 Grammars == | === Chomsky Type-2 Grammars === | ||
KDevelop-PG-Qt uses so called [http://en.wikipedia.org/wiki/Chomsky_hierarchy Type-2-grammars] use a concept of non-terminals (nodes) and terminals(tokens). While writing the grammar for the basic structure of your language, you should try to mimic the semantics of the language. Lets take a look at an example: | KDevelop-PG-Qt uses so called [http://en.wikipedia.org/wiki/Chomsky_hierarchy Type-2-grammars] use a concept of non-terminals (nodes) and terminals(tokens). While writing the grammar for the basic structure of your language, you should try to mimic the semantics of the language. Lets take a look at an example: | ||
Line 102: | Line 111: | ||
The ''member-declarations-list'' is of course not a part of any C++ description, it is just a ''helper'' to explain the structure of a given semantic part of your language. The grammar could then define how exactly such helper might look like. | The ''member-declarations-list'' is of course not a part of any C++ description, it is just a ''helper'' to explain the structure of a given semantic part of your language. The grammar could then define how exactly such helper might look like. | ||
== Basic Syntax == | === Basic Syntax === | ||
Now let us have a look at a basic example, a declaration in C++, as described in grammar syntax: | Now let us have a look at a basic example, a declaration in C++, as described in grammar syntax: | ||
{{Input|1= | |||
class_declaration | class_declaration | ||
| struct_declaration | | struct_declaration | ||
Line 115: | Line 124: | ||
| extern_declaration | | extern_declaration | ||
-> declaration ;; | -> declaration ;; | ||
}} | |||
This is called a ''rule'' definition. Every lower-case string in the grammar file references such a rule. Our case above defines what a ''declaration'' looks like. The ''|''-char stands for a logical ''or'', all rules have to end on two semicolons. | This is called a ''rule'' definition. Every lower-case string in the grammar file references such a rule. Our case above defines what a ''declaration'' looks like. The ''|''-char stands for a logical ''or'', all rules have to end on two semicolons. | ||
Line 121: | Line 130: | ||
In the example we reference other rules which also have to be defined. Here's for example the ''class_declaration'', note the tokens in all-upper-case: | In the example we reference other rules which also have to be defined. Here's for example the ''class_declaration'', note the tokens in all-upper-case: | ||
{{Input|1= | |||
CLASS IDENTIFIER SEMICOLON | CLASS IDENTIFIER SEMICOLON | ||
| CLASS IDENTIFIER LBRACE class_declaration* RBRACE SEMICOLON | | CLASS IDENTIFIER LBRACE class_declaration* RBRACE SEMICOLON | ||
-> class_declaration ;; | -> class_declaration ;; | ||
}} | |||
There is a new char in there: The asterisk has the same meaning as in regular expressions, i.e. that the previous rule can occur arbitrarily often or not at all. | There is a new char in there: The asterisk has the same meaning as in regular expressions, i.e. that the previous rule can occur arbitrarily often or not at all. | ||
Line 131: | Line 140: | ||
In a grammar ''0'' stands for an empty token. Using it in addition with parenthesizing and the logical ''or'' from above, you can express optional elements: | In a grammar ''0'' stands for an empty token. Using it in addition with parenthesizing and the logical ''or'' from above, you can express optional elements: | ||
{{Input|1= | |||
some_required_rule SOME_TOKEN | some_required_rule SOME_TOKEN | ||
( some_optional_stuff | some_other_stuff | 0 ) | ( some_optional_stuff | some_other_stuff | 0 ) | ||
-> my_rule ;; | -> my_rule ;; | ||
}} | |||
All symbols never occuring on the left side of a rule are start-symbols. You can use one of them to start parsing. | All symbols never occuring on the left side of a rule are start-symbols. You can use one of them to start parsing. | ||
== Making matched rules available to Visitors == | === Making matched rules available to Visitors === | ||
The simple rule above could be used to parse the token stream, yet no elements would be saved in the parsetree. This can be easily done though: | The simple rule above could be used to parse the token stream, yet no elements would be saved in the parsetree. This can be easily done though: | ||
{{Input|1= | |||
class_declaration=class_declaration | class_declaration=class_declaration | ||
| struct_declaration=struct_declaration | | struct_declaration=struct_declaration | ||
Line 152: | Line 161: | ||
| extern_declaration=extern_declaration | | extern_declaration=extern_declaration | ||
-> declaration ;; | -> declaration ;; | ||
}} | |||
The DeclarationAst struct now contains pointers to each of these elements. During the parse process the pointer for each found element gets set, all others become NULL. To store lists of elements, prepend the identifier with a hash (''#''): | The DeclarationAst struct now contains pointers to each of these elements. During the parse process the pointer for each found element gets set, all others become NULL. To store lists of elements, prepend the identifier with a hash (''#''): | ||
{{Input|1= | |||
CLASS IDENTIFIER SEMICOLON | CLASS IDENTIFIER SEMICOLON | ||
| CLASS IDENTIFIER LBRACE (#class_declaration=class_declaration)* RBRACE SEMICOLON | | CLASS IDENTIFIER LBRACE (#class_declaration=class_declaration)* RBRACE SEMICOLON | ||
-> class_declaration ;; | -> class_declaration ;; | ||
}} | |||
'''TODO: internal structure of the list, important for Visitors''' | '''TODO: internal structure of the list, important for Visitors''' | ||
Line 166: | Line 175: | ||
Identifier and targets can be used in more than one place: | Identifier and targets can be used in more than one place: | ||
{{Input|1= | |||
#one=one (#one=one)* | #one=one (#one=one)* | ||
-> one_or_more ;; | -> one_or_more ;; | ||
}} | |||
In the example above, all matches to the rule ''one'' will be stored in one and the same list ''one''. | In the example above, all matches to the rule ''one'' will be stored in one and the same list ''one''. | ||
== Defining available Tokens == | === Defining available Tokens === | ||
Somewhere in the grammar, you should probably put it near the head, you'll have to define a list of available Tokens. From this list, the ''TokenType'' enum in ''parser.h'' will be created. Additionally to the enum value names you should define an explanation name which will e.g. be used in error messages. Note that the representation of a Token inside the source code is not required for the grammar/parser as it operates on a TokenStream, see Lexer/Tokenizer section above. | Somewhere in the grammar, you should probably put it near the head, you'll have to define a list of available Tokens. From this list, the ''TokenType'' enum in ''parser.h'' will be created. Additionally to the enum value names you should define an explanation name which will e.g. be used in error messages. Note that the representation of a Token inside the source code is not required for the grammar/parser as it operates on a TokenStream, see Lexer/Tokenizer section above. | ||
{{Input|1= | |||
%token T1 ("T1-Name"), T2 ("T2-Name"), COMMA (";"), SEMICOLON (";") ;; | %token T1 ("T1-Name"), T2 ("T2-Name"), COMMA (";"), SEMICOLON (";") ;; | ||
}} | |||
It is possible to use ''%token'' multiple times to group tokens in the grammar. Though all tokens will still be put into the same ''TokenType'' enum. | It is possible to use ''%token'' multiple times to group tokens in the grammar. Though all tokens will still be put into the same ''TokenType'' enum. | ||
Line 185: | Line 194: | ||
'''TODO: explain process of writing Lexer/Tokenizer and using the parser Tokens''' | '''TODO: explain process of writing Lexer/Tokenizer and using the parser Tokens''' | ||
== Special Syntax... == | === Special Syntax... === | ||
=== ...to use inside Rules === | ==== ...to use inside Rules ==== | ||
==== list of one or more elements ==== | ===== list of one or more elements ===== | ||
Alternatively to the asterisk (''*'') you can use a plus sign (''+'') to mark lists of one-or-more elements: | Alternatively to the asterisk (''*'') you can use a plus sign (''+'') to mark lists of one-or-more elements: | ||
{{Input|1= | |||
(#one=one)+ | (#one=one)+ | ||
-> one_or_more ;; | -> one_or_more ;; | ||
}} | |||
==== | ==== Separated lists ==== | ||
Using the ''#rule @ TOKEN'' syntax you can mark a list of ''rule'', separated by ''TOKEN'': | Using the ''#rule @ TOKEN'' syntax you can mark a list of ''rule'', separated by ''TOKEN'': | ||
{{Input|1= | |||
#item=item @ COMMA | #item=item @ COMMA | ||
-> comma_separated_list ;; | -> comma_separated_list ;; | ||
}} | |||
==== Optional items ==== | |||
Alternatively to the above mentioned ''(item=item | 0)'' syntax you can use the following to mark optional items: | Alternatively to the above mentioned ''(item=item | 0)'' syntax you can use the following to mark optional items: | ||
{{Input|1= | |||
?item=item | ?item=item | ||
-> optional_item ;; | -> optional_item ;; | ||
}} | |||
Attention: ''(0|x)'' is equivalent to ''0''. | |||
'''Attention:''' ''(0|x)'' is equivalent to ''0''. | |||
==== local variables for the parse-process ==== | ==== local variables for the parse-process ==== |
Revision as of 19:05, 5 December 2011
Please do not translate using the old-style language bar, as changes in the master page are not tracked by the old system
Development/KDevelop-PG-Qt Introduction
Languages: عربي | Asturianu | Català | Česky | Kaszëbsczi | Dansk | Deutsch | English | Esperanto | Español | Eesti | فارسی | Suomi | Français | Galego | Italiano | 日本語 | 한국어 | Norwegian | Polski | Português Brasileiro | Română | Русский | Svenska | Slovenčina | Slovenščina | српски | Türkçe | Tiếng Việt | Українська | 简体中文 | 繁體中文
Preface
KDevelop-PG-Qt is the parser-generator from KDevplatform. It is used for some KDevelop-languagesupport-plugins (Ruby, PHP, Java...).
It uses Qt classes internally. There's also the original KDevelop-PG parser, which used types from the STL, but has since been superceeded by KDevelop-PG-Qt. Most of the features are the same, though it could be that the ...-Qt parser generator is more up to date and feature rich than the plain STL style generator. The ...-Qt version should be used to write parsers for KDevelop language plugins.
In-Depth information
This document is not supposed to be a full-fledged and in-depth resource for all parts of KDevelop-PG. Instead it is intended to be a short introduction and, more importantly, a reference for developers.
To get an in-depth introduction, read Jakob Petsovits' excellent Bachelor thesis. You find it in the Weblinks section at the bottom of this page.
The Application
Usage
You can find KDevelop-PG-Qt in git. Four example packages are also included in the sources.
To download it try:
git clone git://anongit.kde.org/kdevelop-pg-qt.git
or:
git clone kde:kdevelop-pg-qt
(when having setup git with kde:-prefix)
The program itself requests a .g file, a so called grammar, as input:
./kdev-pg-qt --output=prefix syntax.g
The value of the --output switch decides the prefix of the output files and additionally the namespace for the generated code. Kate provides elementary highlighting for KDevelop-PG-Qt's grammar-files.
Output Format
While evaluating the grammar and generating its parser files, the application will output information about so called conflicts to STDOUT. As said above, the following files will actually be prefixed.
ast.h
AST stands for Abstract Syntax Tree. It defines the data structure in which the parse tree is saved. Each node is a struct with the postfix Ast, which contains members that point to any possible sub elements.
parser.h and parser.cpp
One important part of parser.h is the definition of the parser tokens, the TokenType enum. The TokenStream of your lexer should to use this. You have to write your own lexer or let one generate by Flex. See also the part about Tokenizers/Lexers below.
Having the token stream available, you create your root item and call the parser on the parse method for the top-level AST item, e.g. DocumentAst* => parseDocument(&root). On success, root will contain the AST.
The parser will have one parse method for each possible node of the AST. This is nice for e.g. an expression parser or parsers that should only parse a sub-element of a full document.
visitor.h and visitor.cpp
The Visitor class provides an abstract interface to walk the AST. Most of the time you don't need to use this directly, the DefaultVisitor takes some work off your shoulders.
defaultvisitor.h and defaultvisitor.cpp
The DefaultVisitor is an implementation of the abstract Visitor interface and automatically visits each node in the AST. Hence, this is probably the best candidate for a base class for your personal visitors. Most language plugins use these in their Builder classes to create the DUChain.
Command-Line-Options
- --namespace=namespace - sets the C++ namespace for the generated sources independently from the file prefix. When this option is set, you can also use / in the --ouput option
- --no-ast - don't create the ast.h file, more to that below
- --debug-visitor - generates a debug visitor that prints the AST
- --serialize-visitor - generates code for serialization via a QIODevice
- --terminals - all tokens will be written into the file kdev-pg-terminals
- --symbols - all possible nodes from the AST (not the leafs) will be written into the file kdev-pg-symbols
- --rules - all grammar rules with informationen about their syntactic correlations will be written into a file called kdev-pg-rules. useful for debugging and solving conflicts
- --token-text - generates a function to map token-numbers onto token-names
- --help - print usage information
Tokenizers/Lexers
As mentioned, KDevelop-PG-Qt requires a Tokenizer. You can either let KDevelop-PG-Qt generate one for you, write one per hand, as it has been done for C++ and PHP, or you can use external tools like Flex.
The tokenizer's job, in principle, boils down to:
- converting keywords and chars with special meanings to tokens
- converting literals and identifier to tokens
- clean out anything that doesn't change the semantics, e.g. comments or whitespace (the latter of course not in Python)
- while doing the above, handling character encoding (we recommend using UTF8 as much as possible)
The rest, e.g. actually building the tree and evaluating the semantics, is part of the parser and the AST visitors.
Using KDevelop-PG-Qt
KDevelop-PG-Qt can generate lexers being well integrated into its architecture (you do not have to create a token-stream-class invoking lex or something like that). See examples/foolisp in the code for a simplistic example.
TODO
Using Flex
With the existing examples, it shouldn't be too hard to write such a lexer. Between most languages, especially those "inheriting" C, there are many common syntactic elements. Especially comments and literals can be handled just the same way over and over again. Adding a simple token is trivial:
"special-command" return Parser::Token_SPECIAL_COMMAND;
That's pretty much it, take a look at eg. java.ll for an excellent example.
How to write Grammar-Files
Chomsky Type-2 Grammars
KDevelop-PG-Qt uses so called Type-2-grammars use a concept of non-terminals (nodes) and terminals(tokens). While writing the grammar for the basic structure of your language, you should try to mimic the semantics of the language. Lets take a look at an example:
C++-document consists of lots of declarations and definitions, a class definition could be handled e.g. in the following way:
- CLASS-token
- a identifier
- the {-token
- a member-declarations-list
- the }-token
- and finally the ;-token
The member-declarations-list is of course not a part of any C++ description, it is just a helper to explain the structure of a given semantic part of your language. The grammar could then define how exactly such helper might look like.
Basic Syntax
Now let us have a look at a basic example, a declaration in C++, as described in grammar syntax:
struct_declaration
This is called a rule definition. Every lower-case string in the grammar file references such a rule. Our case above defines what a declaration looks like. The |-char stands for a logical or, all rules have to end on two semicolons.
In the example we reference other rules which also have to be defined. Here's for example the class_declaration, note the tokens in all-upper-case:
CLASS IDENTIFIER LBRACE class_declaration* RBRACE SEMICOLON -> class_declaration ;;
There is a new char in there: The asterisk has the same meaning as in regular expressions, i.e. that the previous rule can occur arbitrarily often or not at all.
In a grammar 0 stands for an empty token. Using it in addition with parenthesizing and the logical or from above, you can express optional elements:
some_other_stuff
All symbols never occuring on the left side of a rule are start-symbols. You can use one of them to start parsing.
Making matched rules available to Visitors
The simple rule above could be used to parse the token stream, yet no elements would be saved in the parsetree. This can be easily done though:
class_declaration=class_declaration
The DeclarationAst struct now contains pointers to each of these elements. During the parse process the pointer for each found element gets set, all others become NULL. To store lists of elements, prepend the identifier with a hash (#):
CLASS IDENTIFIER SEMICOLON
TODO: internal structure of the list, important for Visitors
Identifier and targets can be used in more than one place:
#one=one (#one=one)* -> one_or_more ;;
In the example above, all matches to the rule one will be stored in one and the same list one.
Defining available Tokens
Somewhere in the grammar, you should probably put it near the head, you'll have to define a list of available Tokens. From this list, the TokenType enum in parser.h will be created. Additionally to the enum value names you should define an explanation name which will e.g. be used in error messages. Note that the representation of a Token inside the source code is not required for the grammar/parser as it operates on a TokenStream, see Lexer/Tokenizer section above.
%token T1 ("T1-Name"), T2 ("T2-Name"), COMMA (";"), SEMICOLON (";") ;;
It is possible to use %token multiple times to group tokens in the grammar. Though all tokens will still be put into the same TokenType enum.
TODO: explain process of writing Lexer/Tokenizer and using the parser Tokens
Special Syntax...
...to use inside Rules
list of one or more elements
Alternatively to the asterisk (*) you can use a plus sign (+) to mark lists of one-or-more elements:
(#one=one)+ -> one_or_more ;;
Separated lists
Using the #rule @ TOKEN syntax you can mark a list of rule, separated by TOKEN:
#item=item @ COMMA -> comma_separated_list ;;
Optional items
Alternatively to the above mentioned (item=item | 0) syntax you can use the following to mark optional items:
?item=item -> optional_item ;;
Attention: (0|x) is equivalent to 0.
local variables for the parse-process
Using a colon (:) instead of the equal sign (=) you can store the sub-AST in a local variable that will only be available during parsing, and only in the current rule.
TODO: need example
Inlining
Instead of a name you can also place a dot (.) before the equal sign (=). Then the AST will inherit all class-members from the sub-AST and the parsing-method for the sub-AST will be merged into the parent-AST. An example:
op=PLUS | op=MUL | op=MOD -> operator ;; val1=number .=op val2=number -> simpleArithmetics ;;
In SimpleArithmeticsAst there will be the fields val1, val2 (storing the operands) and op (storing the operator-token). parseSimpleArithmetics will not have to call parseOperator. Obviously recursive inlining is not allowed.
...to add Hand-Written Code
Sometimes it is required to integrate hand-written code into the generated parser. Instead of editing the end-result (**never** do that!) you should put this code into the grammar at the correct places. Here are a few examples when you'd need this:
- custom error handling / error recovery, i.e. to prevent the parser to stop at the first error
- creating tokens, if you don't want to do that externally
- setting custom variables, especially for state tracking. e.g. in C++ you could save whether you are inside a private, protected oder public section. then you could save this information inside each node of the class elements.
- additional verifications, lookaheads etc.
General Syntax
[: // here be dragons^W code ;-) :]
The code will be put into the generated parser.cpp file. If you use it inside a grammar rule, it will be put into the correct position during the parse process. You can access the current node via the variable yynode, it will have the type 'XYZAst**'.
Global Code
In KDevelop language plugins, you'll see that most grammars start with something like:
[: #include <QtCore/QString> #include <kdebug.h> #include <tokenstream.h> #include <language/interfaces/iproblem.h> #include "phplexer.h" namespace KDevelop { class DUContext; } :]
This is a code section, that will be put at the beginning of ast.h, i.e. into the global context.
But there are also some newer statements available to include header-files:
%ast_header "header.h" %parser_declaration_header "header.h" %parser_bits_header "header.h"
The include-statement will be inserted into ast.h, parser.h respectively parser.cpp.
Namespace Code
Also it's very common to define a set of enumerations e.g. for operators, modifiers, etc. pp. Here's an stripped example from PHP, note that the code will again be put into the generated parser.h file:
%namespace [: enum ModifierFlags { ModifierPrivate = 1, ModifierPublic = 1 << 1, ModifierProtected = 1 << 2, ModifierStatic = 1 << 3, ModifierFinal = 1 << 4, ModifierAbstract = 1 << 5 }; ... enum OperationType { OperationPlus = 1, OperationMinus, OperationConcat, OperationMul, OperationDiv, OperationMod, OperationAnd, OperationOr, OperationXor, OperationSl, OperationSr }; :]
When you write code at the end of the grammar-file simply between [: and :], it will be added to parser.cpp.
Additional AST member
To add additional members to _every_ AST variable, use the following syntax:
%ast_extra_members [: KDevelop::DUContext* ducontext; :]
You can also specify the base-class for the nodes:
%ast_base symbol_name "BaseClass"
BaseClass has to inherit from AstNode.
Additional parser class members
Instead of polluting the global context with state tracker variables, and hence destroying the whole advantages of OOP, you can add additional members to the parser class. It's also very convenient to define functions for error reporting etc. pp. Again a stripped example from PHP:
%parserclass (public declaration) [: enum ProblemType { Error, Warning, Info }; void reportProblem( Parser::ProblemType type, const QString& message ); QList<KDevelop::ProblemPointer> problems() { return m_problems; } ... enum InitialLexerState { HtmlState = 0, DefaultState = 1 }; :]
Note, that we used %parserclass (public declaration), we could instead have used private or protected declaration.
%parserclass ( [private|protected|public] declaration) [: // Code :]
There is also a statement to specify a base-class:
%parser_base "ClassName"
Initializing additional parser class members
When you add member variables to the class, you'll have to initialize and or destroy them as well. Here's how (either use ctor or dtor, of course):
%parserclass ( [constructor|desctructor] ) [: // Code :]
Boolean Checks
?[: // some bool expression :]
The following rule will only apply if the boolean expression evaluates to true. Here's an advanced example, which also shows that you can use the pipe symbol ('|') as logical or, i.e. essentially this is a if... else...' conditional:
?[: someCondition :] SOMETOKEN ifrule=myVar | elserule=myVar
This is especially convenient together with lookaheads (see below).
defining local variables inside rules
You can setup the grammar to define local variables whenever a rule gets applied:
... -> class_member [: enum { A_PUBLIC, A_PROTECTED, A_PRIVATE } access; :];;
This variable is local to the rule class_member.
defining additional variables for the parse tree
Similar to the syntax above, you can define members whenever a rule gets applied:
... -> class_member [ [member|temporary] variable yourName: yourType ]
For example:
... -> class_member [ member variable access : AccessType; ];;
Of course AccessType has to be defined somewhere else, see e.g. the Additional parser class members section above.
Using temporary or member is equivalent.
Conflicts
First time your write a grammar, you'll potentially fail: Since KDevelop-PG-Qt is a LL(1) parser generator, conflicts can occur and have to be solved by hand. Here's an example for a FIRST/FIRST conflict:
CLASS IDENTIFIER SEMICOLON -> class_declaration ;; CLASS IDENTIFIER LBRACE class_content RBRACE SEMICOLON -> class_definition ;; class_declaration | class_definition -> class_expression ;;
KDevelop-PG-Qt will output:
** WARNING found FIRST/FIRST conflict in "class_expression"
Sometime's it's these warnings can be ignored, but most of the time it will lead to problems in parsing. In our example the class_expression rule won't be evaluated properly: When you try to parse a class_definition, the parser will see that the first part (CLASS) of class_declaration matches, and jumps think's that this rule is to be applied. Since the next part of the rule does not match, an error will be reported. It does _not_ try to evaluate class_definition automatically, but there are different ways to solve this problem:
Backtracking
In theory such behavior might be unexpected: In BNF e.g. the above syntax would be enough and the parser would automatically jump back and retry with the next rule, here class_definition. But in practice such behaviour is - most of the time - not necessary and would slow down the parser. If however such behavior is explicitly what you want, you can use an explicit backtracking syntax in your grammar:
try/rollback(class_declaration) catch(class_definition) -> class_expression ;;
In theory, every FIRST/FIRST could be solved this way, but keep in mind that the more you use this feature, the slower your parser gets. If you use it, sort the rules in order of likeliness. Also, there could be cases where the sort order could make the difference between correct and wrong parsing, especially with rules that "extend" others.
Look ahead
KDevelop-PG-Qt has an alternative to the - potentially slow - backtracking mechanism: Look ahead. You can use the LA(qint64) function in embedded C++ code (see sections above). LA(1) will return the current token, you most probably never need to use that. Accordingly, LA(2) returns the next and LA(0) the previous token. (If you wonder where these somewhat uncommon indexes come from: Read the thesis, or make yourself acquainted with LA(k) parser theory.)
(?[: LA(2) == Token_LBRACE :] class_definition) | class_declaration -> class_expression
Note: The conflict will still be outputted, but it is manually resolved. You should always document this somewhere with a comment, for future contributors to know that this is a false-positive.
Elegant Solutions
Often you can solve the conflict all-together with an elegant solution. Like in our example:
LBRACE class_content RBRACE -> class_definition ;; CLASS IDENTIFIER ?class_definition SEMICOLON -> class_expression ;;
No conflicts, fast: everybody is happy!
FIRST/FOLLOW-Conflicts
A FIRST/FOLLOW conflict says, that it is undefined where a symbol ends and the parent symbol continues. A pretty stupid example is:
item* -> item_list ;; item_list item* -> items ;;
You probably see the glaring error. try/rollback helps with serious problems (e.g. parser doesn't work), though often you can ignore these conflicts. But if you do so, be aware that the parser is greedy: In our example item_list will always contain all item elements, and items will never have an item member. If this is an issue that leads to later conflicts, only try/rollback, custom manual code to check which rule should be used, or a refactoring of your grammar structure.
Changing the Greedy Behaviour
Alternatively it is sometimes helpful/required to change the greedy behaviour. In lists you can use manual code to force a break at a given position. Here's an example that shows this on a declaration of an array with fixed size:
typed_identifier=typed_identifier LBRACKET UNSIGNED_INTEGER [: count = static_cast<MyTokenStream*>(token)->strForCurrentToken().toUInt(); :] RBRACKET EQUAL LBRACE (#expression=expression [: if(--count == 0) break; :] @ COMMA) ?[: count == 0 :] RBRACE SEMICOLON -> initialized_fixed_array [ temporary variable count: uint; ];;
TODO: return true/false or break??
Via return false you can enforce a premature stop (== error) of the evaluation of the current rule. Using return true you stop the evaluation prematurely, but signalize that the parse process was successful.
try/recover
try/recover(expression)
-> symbol ;;
This is approximately the same as:
[: ParserState *state = copyCurrentState(); :]
try/rollback(expression)
catch( [: restoreState(state); :] )
-> symbol ;;
Hence you have to implement the member-functions copyCurrentState and restoreState and yaou have to define a type called ParseState. You do not have to write the declaration of those functions in the header-file, it is generated automatically if you use try/recover. This concept seems to be useful if there are additional states used while parsing. The Java-parser takes usage from it very often. But I do not know a lot about this feature and it seems unimportant for me. (I guess, it is not) I would be happy when somebody could explain it to me.
Operator Expression Parsing
TODO
Weblinks
- [1] - The KDevelop-PG-Qt-Grammar (it is a Bison/Yacc-grammar-file)
- [2] - Project at projects.kde.org
- [3] - Some blog-posts by “The User” about KDevelop-PG-Qt (informative for recently added features)
- [4] - Jakob Petsovits' bachelor thesis about using KDevelop-PG for Java Java-Parsers - It is a good in-depth introduction to everything you might want to know for writing your own grammar. Keep in mind that it is partly outdated. In doubt, refer to this page for updated syntax. Also some of the shortcomings of KDevelop-PG layed out in the thesis have been fixed in the meantime.