82 Commits
Author SHA1 Message Date
josh 243ee0f19f v3.0.0 2026-02-11 00:21:55 -05:00
josh 8d2efcc19b Update gems 2026-02-11 00:21:37 -05:00
josh 9c787f0e89 Update CHANGELOG.md and UPGRADING.md for v3.0.0 2026-02-09 22:08:20 -05:00
josh ef6a0f9552 Update README and CHANGELOG overview features list 2026-02-09 22:00:39 -05:00
josh 77ec7c9de4 Rename AST generation mode to tree generation mode 2026-02-09 21:59:00 -05:00
josh 6a87bb2d56 Add support for multiple starting rules - close #38 2026-02-09 21:21:49 -05:00
josh cb426b4be1 Add free_token_node grammar statement 2026-02-08 20:33:00 -05:00
josh addf27d837 Turn on valgrind for C, C++ target specs 2026-02-08 19:24:11 -05:00
josh 9c03d20083 Add p_free_ast() to free AST node memory for C targets 2026-02-08 00:02:24 -05:00
josh e223d03d7c Use more concise RSpec output 2026-02-07 19:18:36 -05:00
josh be6a9ca2c1 Rename Rakefile -> Rakefile.rb 2026-02-07 19:18:25 -05:00
josh 072af73b1e Support C++ output language 2026-02-07 18:59:01 -05:00
josh ac8ed4bf5a Update gems 2026-01-15 20:34:31 -05:00
josh 8a393f554a Document p_lex and p_token_info_t in user guide - fix #37 2026-01-15 20:22:54 -05:00
josh 66f95cb6d8 v2.3.0 2025-07-28 20:40:24 -04:00
josh dbe0bf8ad0 Add github workflow to run unit tests 2025-07-28 20:35:06 -04:00
josh 75fb627602 Update CHANGELOG 2025-07-28 18:44:00 -04:00
josh 7ccb4c8730 Parallelize parser table generation on Linux hosts 2025-07-28 18:43:12 -04:00
josh 962b7125ec Build lookahead reduce actions for item sets before building reduce table 2025-07-27 17:24:35 -04:00
josh 2df27b04fe Update CHANGELOG 2025-07-27 14:56:09 -04:00
josh 17f1454a4f Speedup: avoid checking the same rule set more than once for lookahead tokens 2025-07-27 14:50:18 -04:00
josh b371f4b404 Speedup: calculate Item#hash once 2025-07-27 13:48:52 -04:00
josh 91f476187b Update CHANGELOG 2025-07-27 10:32:57 -04:00
josh 54a0629e60 Add spec for \s matching newline 2025-07-26 21:36:05 -04:00
josh 69aa3097c3 Update user guide for shorthand character classes 2025-07-26 21:34:22 -04:00
josh 5486e5f138 Add \D, \S, \w, \W special character classes 2025-07-26 21:21:29 -04:00
josh 5b243507cf Show grammar line numbers for regex errors 2025-07-25 21:27:20 -04:00
josh 25d6e3bc34 Fix user guide regular expression example 2025-07-25 16:24:05 -04:00
josh 035bb2fc60 Fix matching lexer patterns containing a negated character class which includes a backslash-escaped sequence that matches more than one character 2025-07-25 16:14:35 -04:00
josh 125c149750 Drop argument to CharacterRangeUnit constructor when possible 2025-07-22 21:53:18 -04:00
josh 9ef80e61d4 Update gems 2025-07-22 21:22:27 -04:00
josh 5b94b03b04 Fix user guide double backslashes in examples 2025-07-22 21:17:18 -04:00
josh 9d686989ec Fix C issue not fully initializing pvalues
This issue manifested when multiple ptypes are present with different sizes.
2025-07-22 21:05:32 -04:00
josh 87d892d0a3 Fix a couple clang warnings 2025-07-21 23:26:37 -04:00
josh 4ec57fa48d Fix vim syntax highlighting for a regex ending with an escaped backslash 2025-07-20 17:16:59 -04:00
josh 8b38ea4261 v2.2.1 2025-04-05 09:53:27 -04:00
josh 92da10e483 Fix GC issue for D backend with AST enabled - close #36 2025-04-05 09:51:01 -04:00
josh 839174a635 Generate comments to annotate reduce table entries 2025-04-05 09:50:18 -04:00
josh 659de44c31 Update gems 2025-03-29 18:31:46 -04:00
josh 207201d589 v2.2.0 2025-03-13 19:25:30 -04:00
josh 89bc52fd80 Update CHANGELOG for v2.2.0 2025-03-13 19:25:09 -04:00
josh eb9d9026fc Allow multiple lexer modes to be specified for a lexer pattern - close #35 2025-03-11 20:40:10 -04:00
josh 54bb3307cd Document p_decode_code_point() API function - close #34 2025-03-10 12:19:42 -04:00
josh 5ebcbb2d6d v2.1.1 2024-12-28 23:15:45 -05:00
josh 61ebbb4f19 Update CHANGELOG for v2.1.1 2024-12-28 23:13:46 -05:00
josh 1b4ca59158 Field aliases for AST node fields could alias incorrect field when multiple rule alternatives present for one rule set - close #33 2024-12-28 23:08:53 -05:00
josh b02c9205c0 Update CHANGELOG 2024-12-28 10:01:33 -05:00
josh 7344554b5f Version 2.1.0 2024-12-28 09:56:29 -05:00
josh 77571a3449 Update gems 2024-12-28 09:54:54 -05:00
josh e098b7e445 Report rule name and line number for conflicting AST node field positions errors - close #32 2024-12-28 09:51:12 -05:00
josh 3ea344a520 v2.0.0 2024-09-25 16:09:27 -04:00
josh 530878a796 Update CHANGELOG for v2.0.0 2024-09-25 16:09:20 -04:00
josh 1d468b6d3c Use 1-based row and column values for position values - close #30 2024-09-25 15:58:03 -04:00
josh 98e10d3d14 Fix named optional rules - close #29 2024-09-24 20:28:19 -04:00
josh 36c74e439e Log conflicting rules on reduce/reduce conflict - close #31 2024-09-24 20:05:39 -04:00
josh c24f323ff0 v1.5.1 2024-07-26 22:30:48 -04:00
josh fec2c28693 Only calculate lookahead tokens when needed - #28
Lookahead tokens are only need if either:
(1) There is more than one rule that could be reduced in a given parser
state, or
(2) There are shift actions for a state and at least one rule that could
be reduced in the same state (to warn about shift/reduce conflicts).
2024-07-26 22:08:25 -04:00
josh 61339aeae9 Avoid recalculating reduce_rules - #28 2024-07-26 21:36:41 -04:00
josh 95b3dc6550 Cache ItemSet#next_symbols - #28 2024-07-25 20:33:15 -04:00
josh 74d94fef72 Do not build ItemSet follow sets - #28 2024-07-25 20:02:00 -04:00
josh 588c5e21c7 Cache ItemSet#leading_item_sets return values - #28 2024-07-25 10:42:43 -04:00
josh 5f1c306273 Update CLI usage in README 2024-07-22 21:35:32 -04:00
josh 343e8a7f9e v1.5.0 2024-07-22 21:23:38 -04:00
josh b3a134bf8d Update vim syntax to highlight "?" and field alias names 2024-07-22 20:39:59 -04:00
josh 4a71dc74fb Update CHANGELOG for v1.5.0 2024-07-22 20:26:04 -04:00
josh a7348be95d Add rule field aliases - #24 2024-07-22 20:16:52 -04:00
josh 9746b3f2bf Document position tracking fields in user guide - #27 2024-07-21 14:04:51 -04:00
josh c5b8fc28bd Move INVALID_POSITION from header to C source - #27 2024-07-21 13:39:34 -04:00
josh 092fce61eb Test position validity for empty matching rules - #27 2024-07-21 13:39:30 -04:00
josh e647248e34 Track start and end position of rules in AST nodes - #27 2024-07-19 15:37:37 -04:00
josh f4ae1b8601 Add position fields to AST nodes (not populated yet) - #27 2024-07-19 14:34:50 -04:00
josh eae2e17f41 Test tracking token end positions when the token spans a newline - #27 2024-07-18 12:09:26 -04:00
josh 87d6d29d60 Store token end position - #27 2024-07-18 12:03:44 -04:00
josh 3aced70356 Show line numbers of rules upon conflict - close #23 2024-07-14 20:52:52 -04:00
josh 2dd89445fc Add command line switch to output warnings to stderr - close #26 2024-07-14 15:36:07 -04:00
josh 4ae5ab79b3 Warn on shift/reduce conflicts 2024-07-13 21:35:53 -04:00
josh 69cc8fa67d Always compute lookahead tokens for reduce rules
Even if they won't be needed for the generated parser, they'll be useful
to detect shift/reduce conflicts.
2024-07-13 21:01:44 -04:00
josh 7f3eb8f315 Calculate follow token set for an ItemSet 2024-07-13 20:48:28 -04:00
josh d76e12fea1 Rename "following" to "next" - #25
The term "following" could potentially imply an association with the
"follow set", however it was used in a non-closed manner.
2024-07-08 10:14:09 -04:00
josh 911e9505b7 Track token position in AST Token node 2024-05-27 22:10:05 -04:00
josh aaeb0c4db1 Remove leftover TODO from earlier restructuring 2024-05-27 20:44:42 -04:00
josh fd89c5c6b3 Add Vim syntax highlighting files for Propane 2024-05-26 14:49:30 -04:00
74 changed files with 4194 additions and 644 deletions

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
name: Run Propane Tests
on:
push:
branches:
- master
pull_request:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
ruby-version: ['3.4']
steps:
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y gcc gdc ldc
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: brew install gcc ldc
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
- name: Install dependencies
run: bundle install
- name: Run tests
run: rake all
-1
View File
@@ -1,3 +1,2 @@
--format documentation
--color --color
--require spec_helper --require spec_helper
+97
View File
@@ -1,3 +1,100 @@
## v3.0.0
### New Features
- Add support for multiple starting rules (#38)
- Add `p_free_tree()` functions to reclaim generated tree memory
- Add `free_token_node` grammar statement to reclaim user-allocated memory stored in a Token tree node `pvalue` field
- Add valgrind memory leak tests to unit tests
- Fix build issues for C++ to officially support C++ target output
### Improvements
- Document `p_lex()` and `p_token_info_t` in user guide (#37)
### Breaking changes
- Rename AST generation mode to tree generation mode (see [UPGRADING.md]())
## v2.3.0
### New Features
- Add \D, \S, \w, \W special character classes
### Improvements
- Include line numbers for pattern errors
- Improve performance in a few places
- Parallelize parser table generation on Linux hosts
- Add github workflow to run unit tests
### Fixes
- Fix a couple clang warnings for C backend
- Fix C backend not fully initializing pvalues when multiple ptypes are used with different sizes.
- Fix some user guide examples
## v2.2.1
### Fixes
- Fix GC issue for D backend when AST is enabled (#36)
## v2.2.0
### Improvements
- Allow multiple lexer modes to be specified for a lexer pattern (#35)
- Document p_decode_code_point() API function (#34)
## v2.1.1
### Fixes
- Field aliases for AST node fields could alias incorrect field when multiple rule alternatives present for one rule set (#33)
## v2.1.0
### Improvements
- Report rule name and line number for conflicting AST node field positions errors (#32)
## v2.0.0
### Improvements
- Log conflicting rules on reduce/reduce conflict (#31)
- Use 1-based row and column values for position values (#30)
### Fixes
- Fix named optional rules (#29)
### Upgrading
- Adjust all uses of p_position_t row and col values to expect 1-based instead
of 0-based values.
## v1.5.1
### Improvements
- Improve performance (#28)
## v1.5.0
### New Features
- Track start and end text positions for tokens and rules in AST node structures (#27)
- Add warnings for shift/reduce conflicts to log file (#25)
- Add -w command line switch to treat warnings as errors and output to stderr (#26)
- Add rule field aliases (#24)
### Improvements
- Show line numbers of rules on conflict (#23)
## v1.4.0 ## v1.4.0
### New Features ### New Features
+1
View File
@@ -1,5 +1,6 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "base64"
gem "rake" gem "rake"
gem "rspec" gem "rspec"
gem "rdoc" gem "rdoc"
+27 -19
View File
@@ -1,40 +1,48 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
diff-lcs (1.5.0) base64 (0.3.0)
docile (1.4.0) date (3.5.1)
psych (5.1.0) diff-lcs (1.6.2)
docile (1.4.1)
erb (6.0.1)
psych (5.3.1)
date
stringio stringio
rake (13.0.6) rake (13.3.1)
rdoc (6.5.0) rdoc (7.2.0)
erb
psych (>= 4.0.0) psych (>= 4.0.0)
redcarpet (3.6.0) tsort
rspec (3.12.0) redcarpet (3.6.1)
rspec-core (~> 3.12.0) rspec (3.13.2)
rspec-expectations (~> 3.12.0) rspec-core (~> 3.13.0)
rspec-mocks (~> 3.12.0) rspec-expectations (~> 3.13.0)
rspec-core (3.12.2) rspec-mocks (~> 3.13.0)
rspec-support (~> 3.12.0) rspec-core (3.13.6)
rspec-expectations (3.12.3) rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.13.0)
rspec-mocks (3.12.6) rspec-mocks (3.13.7)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.13.0)
rspec-support (3.12.1) rspec-support (3.13.7)
simplecov (0.22.0) simplecov (0.22.0)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1) simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3) simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
stringio (3.0.7) stringio (3.2.0)
syntax (1.2.2) syntax (1.2.2)
tsort (0.2.0)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
base64
rake rake
rdoc rdoc
redcarpet redcarpet
+12 -5
View File
@@ -6,8 +6,10 @@ Propane is a LALR Parser Generator (LPG) which:
* generates a built-in lexer to tokenize input * generates a built-in lexer to tokenize input
* supports UTF-8 lexer inputs * supports UTF-8 lexer inputs
* generates a table-driven shift/reduce parser to parse input in linear time * generates a table-driven shift/reduce parser to parse input in linear time
* targets C or D language outputs * targets C, C++, or D language outputs
* optionally supports automatic full AST generation * optionally supports automatic full parse tree generation
* supports starting parsing from multiple start rules
* tracks input text start and end positions for all matched tokens/rules
* is MIT-licensed * is MIT-licensed
* is distributable as a standalone Ruby script * is distributable as a standalone Ruby script
@@ -31,9 +33,14 @@ Propane is typically invoked from the command-line as `./propane`.
Usage: ./propane [options] <input-file> <output-file> Usage: ./propane [options] <input-file> <output-file>
Options: Options:
--log LOG Write log file -h, --help Show this usage and exit.
--version Show program version and exit --log LOG Write log file. This will show all parser states and their
-h, --help Show this usage and exit associated shifts and reduces. It can be helpful when
debugging a grammar.
--version Show program version and exit.
-w Treat warnings as errors. This option will treat shift/reduce
conflicts as fatal errors and will print them to stderr in
addition to the log file.
The user must specify the path to a Propane input grammar file and a path to an The user must specify the path to a Propane input grammar file and a path to an
output file. output file.
+14
View File
@@ -1,5 +1,7 @@
require "rake/clean" require "rake/clean"
require "rspec/core/rake_task" require "rspec/core/rake_task"
require "simplecov"
require "stringio"
CLEAN.include %w[spec/run gen .yardoc yard coverage dist] CLEAN.include %w[spec/run gen .yardoc yard coverage dist]
@@ -12,6 +14,18 @@ RSpec::Core::RakeTask.new(:spec, :example_pattern) do |task, args|
task.rspec_opts = %W[-e "#{args.example_pattern}" -f documentation] task.rspec_opts = %W[-e "#{args.example_pattern}" -f documentation]
end end
end end
task :spec do |task, args|
unless ENV["dist_specs"]
original_stdout = $stdout
sio = StringIO.new
$stdout = sio
SimpleCov.collate Dir["coverage/.resultset.json"]
$stdout = original_stdout
sio.string.lines.each do |line|
$stdout.write(line) unless line =~ /Coverage report generated for/
end
end
end
# dspec task is useful to test the distributable release script, but is not # dspec task is useful to test the distributable release script, but is not
# useful for coverage information. # useful for coverage information.
+7
View File
@@ -0,0 +1,7 @@
## v3.0.0
### Grammar changes
- Rename `ast;` statement to `tree;`.
- Rename `ast_prefix;` statement to `tree_prefix;`.
- Rename `ast_suffix;` statement to `tree_suffix;`.
+187 -49
View File
@@ -55,11 +55,14 @@ const char * <%= @grammar.prefix %>token_names[] = {
void <%= @grammar.prefix %>context_init(<%= @grammar.prefix %>context_t * context, uint8_t const * input, size_t input_length) void <%= @grammar.prefix %>context_init(<%= @grammar.prefix %>context_t * context, uint8_t const * input, size_t input_length)
{ {
/* New default-initialized context structure. */ /* New default-initialized context structure. */
<%= @grammar.prefix %>context_t newcontext = {0}; <%= @grammar.prefix %>context_t newcontext;
memset(&newcontext, 0, sizeof(newcontext));
/* Lexer initialization. */ /* Lexer initialization. */
newcontext.input = input; newcontext.input = input;
newcontext.input_length = input_length; newcontext.input_length = input_length;
newcontext.text_position.row = 1u;
newcontext.text_position.col = 1u;
newcontext.mode = <%= @lexer.mode_id("default") %>; newcontext.mode = <%= @lexer.mode_id("default") %>;
/* Copy to the user's context structure. */ /* Copy to the user's context structure. */
@@ -226,7 +229,10 @@ typedef struct
/** Number of bytes of input text used to match. */ /** Number of bytes of input text used to match. */
size_t length; size_t length;
/** Input text position delta. */ /** Input text position delta to end of token. */
<%= @grammar.prefix %>position_t end_delta_position;
/** Input text position delta to next code point after token end. */
<%= @grammar.prefix %>position_t delta_position; <%= @grammar.prefix %>position_t delta_position;
/** Accepting lexer state from the match. */ /** Accepting lexer state from the match. */
@@ -339,8 +345,10 @@ static lexer_state_id_t check_lexer_transition(uint32_t current_state, uint32_t
static size_t find_longest_match(<%= @grammar.prefix %>context_t * context, static size_t find_longest_match(<%= @grammar.prefix %>context_t * context,
lexer_match_info_t * out_match_info, size_t * out_unexpected_input_length) lexer_match_info_t * out_match_info, size_t * out_unexpected_input_length)
{ {
lexer_match_info_t longest_match = {0}; lexer_match_info_t longest_match;
lexer_match_info_t attempt_match = {0}; memset(&longest_match, 0, sizeof(longest_match));
lexer_match_info_t attempt_match;
memset(&attempt_match, 0, sizeof(attempt_match));
*out_match_info = longest_match; *out_match_info = longest_match;
uint32_t current_state = lexer_mode_table[context->mode].state_table_offset; uint32_t current_state = lexer_mode_table[context->mode].state_table_offset;
for (;;) for (;;)
@@ -354,14 +362,16 @@ static size_t find_longest_match(<%= @grammar.prefix %>context_t * context,
switch (result) switch (result)
{ {
case P_SUCCESS: case P_SUCCESS:
{
lexer_state_id_t transition_state = check_lexer_transition(current_state, code_point); lexer_state_id_t transition_state = check_lexer_transition(current_state, code_point);
if (transition_state != INVALID_LEXER_STATE_ID) if (transition_state != INVALID_LEXER_STATE_ID)
{ {
attempt_match.length += code_point_length; attempt_match.length += code_point_length;
attempt_match.end_delta_position = attempt_match.delta_position;
if (code_point == '\n') if (code_point == '\n')
{ {
attempt_match.delta_position.row++; attempt_match.delta_position.row++;
attempt_match.delta_position.col = 0u; attempt_match.delta_position.col = 1u;
} }
else else
{ {
@@ -384,6 +394,7 @@ static size_t find_longest_match(<%= @grammar.prefix %>context_t * context,
*out_unexpected_input_length = attempt_match.length + code_point_length; *out_unexpected_input_length = attempt_match.length + code_point_length;
return P_UNEXPECTED_INPUT; return P_UNEXPECTED_INPUT;
} }
}
break; break;
case P_EOF: case P_EOF:
@@ -441,16 +452,17 @@ static size_t find_longest_match(<%= @grammar.prefix %>context_t * context,
*/ */
static size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>token_info_t * out_token_info) static size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>token_info_t * out_token_info)
{ {
<%= @grammar.prefix %>token_info_t token_info = {0}; <%= @grammar.prefix %>token_info_t token_info;
memset(&token_info, 0, sizeof(token_info));
token_info.position = context->text_position; token_info.position = context->text_position;
token_info.token = INVALID_TOKEN_ID; token_info.token = INVALID_TOKEN_ID;
*out_token_info = token_info; // TODO: remove
lexer_match_info_t match_info; lexer_match_info_t match_info;
size_t unexpected_input_length; size_t unexpected_input_length;
size_t result = find_longest_match(context, &match_info, &unexpected_input_length); size_t result = find_longest_match(context, &match_info, &unexpected_input_length);
switch (result) switch (result)
{ {
case P_SUCCESS: case P_SUCCESS:
{
<%= @grammar.prefix %>token_t token_to_accept = match_info.accepting_state->token; <%= @grammar.prefix %>token_t token_to_accept = match_info.accepting_state->token;
if (match_info.accepting_state->code_id != INVALID_USER_CODE_ID) if (match_info.accepting_state->code_id != INVALID_USER_CODE_ID)
{ {
@@ -491,11 +503,23 @@ static size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%= @
} }
token_info.token = token_to_accept; token_info.token = token_to_accept;
token_info.length = match_info.length; token_info.length = match_info.length;
if (match_info.end_delta_position.row != 0u)
{
token_info.end_position.row = token_info.position.row + match_info.end_delta_position.row;
token_info.end_position.col = match_info.end_delta_position.col;
}
else
{
token_info.end_position.row = token_info.position.row;
token_info.end_position.col = token_info.position.col + match_info.end_delta_position.col;
}
*out_token_info = token_info; *out_token_info = token_info;
}
return P_SUCCESS; return P_SUCCESS;
case P_EOF: case P_EOF:
token_info.token = TOKEN___EOF; token_info.token = TOKEN___EOF;
token_info.end_position = token_info.position;
*out_token_info = token_info; *out_token_info = token_info;
return P_SUCCESS; return P_SUCCESS;
@@ -552,6 +576,9 @@ size_t <%= @grammar.prefix %>lex(<%= @grammar.prefix %>context_t * context, <%=
* Parser * Parser
*************************************************************************/ *************************************************************************/
/** Invalid position value. */
#define INVALID_POSITION (<%= @grammar.prefix %>position_t){0u, 0u}
/** Reduce ID type. */ /** Reduce ID type. */
typedef <%= get_type_for(@parser.reduce_table.size) %> reduce_id_t; typedef <%= get_type_for(@parser.reduce_table.size) %> reduce_id_t;
@@ -611,7 +638,7 @@ typedef struct
* reduce action. * reduce action.
*/ */
parser_state_id_t n_states; parser_state_id_t n_states;
<% if @grammar.ast %> <% if @grammar.tree %>
/** /**
* Map of rule components to rule set child fields. * Map of rule components to rule set child fields.
@@ -619,7 +646,7 @@ typedef struct
uint16_t const * rule_set_node_field_index_map; uint16_t const * rule_set_node_field_index_map;
/** /**
* Number of rule set AST node fields. * Number of rule set tree node fields.
*/ */
uint16_t rule_set_node_field_array_size; uint16_t rule_set_node_field_array_size;
@@ -661,20 +688,30 @@ typedef struct
/** Parser value from this state. */ /** Parser value from this state. */
<%= @grammar.prefix %>value_t pvalue; <%= @grammar.prefix %>value_t pvalue;
<% if @grammar.ast %> <% if @grammar.tree %>
/** AST node. */ /** tree node. */
void * ast_node; void * tree_node;
<% end %> <% end %>
} state_value_t; } state_value_t;
/** Common tree node structure. */
typedef struct TreeNode_s
{
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
uint16_t n_fields;
uint8_t is_token;
struct TreeNode_s * fields[];
} TreeNode;
/** Parser shift table. */ /** Parser shift table. */
static const shift_t parser_shift_table[] = { static const shift_t parser_shift_table[] = {
<% @parser.shift_table.each do |shift| %> <% @parser.shift_table.each do |shift| %>
{<%= shift[:symbol_id] %>u, <%= shift[:state_id] %>u}, {<%= shift[:symbol].id %>u, <%= shift[:state_id] %>u},
<% end %> <% end %>
}; };
<% if @grammar.ast %> <% if @grammar.tree %>
<% @grammar.rules.each do |rule| %> <% @grammar.rules.each do |rule| %>
<% unless rule.flat_rule_set_node_field_index_map? %> <% unless rule.flat_rule_set_node_field_index_map? %>
const uint16_t r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_map[<%= rule.rule_set_node_field_index_map.size %>] = {<%= rule.rule_set_node_field_index_map.map {|v| v.to_s}.join(", ") %>}; const uint16_t r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_map[<%= rule.rule_set_node_field_index_map.size %>] = {<%= rule.rule_set_node_field_index_map.map {|v| v.to_s}.join(", ") %>};
@@ -685,17 +722,22 @@ const uint16_t r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_
/** Parser reduce table. */ /** Parser reduce table. */
static const reduce_t parser_reduce_table[] = { static const reduce_t parser_reduce_table[] = {
<% @parser.reduce_table.each do |reduce| %> <% @parser.reduce_table.each do |reduce| %>
{<%= reduce[:token_id] %>u, <%= reduce[:rule_id] %>u, <%= reduce[:rule_set_id] %>u, <%= reduce[:n_states] %>u {
<% if @grammar.ast %> <%= reduce[:token_id] %>u, /* Token: <%= reduce[:token] ? reduce[:token].name : "(any)" %> */
<%= reduce[:rule_id] %>u, /* Rule ID */
<%= reduce[:rule_set_id] %>u, /* Rule set ID (<%= reduce[:rule].rule_set.name %>) */
<% if @grammar.tree %>
<%= reduce[:n_states] %>u, /* Number of states */
<% if reduce[:rule].flat_rule_set_node_field_index_map? %> <% if reduce[:rule].flat_rule_set_node_field_index_map? %>
, NULL NULL, /* No rule set node field index map (flat map) */
<% else %> <% else %>
, &r_<%= reduce[:rule].name.gsub("$", "_") %><%= reduce[:rule].id %>_node_field_index_map[0] &r_<%= reduce[:rule].name.gsub("$", "_") %><%= reduce[:rule].id %>_node_field_index_map[0], /* Rule set node field index map */
<% end %> <% end %>
, <%= reduce[:rule].rule_set.ast_fields.size %> <%= reduce[:rule].rule_set.tree_fields.size %>, /* Number of tree fields */
, <%= reduce[:propagate_optional_target] %> <%= reduce[:propagate_optional_target] %>}, /* Propagate optional target? */
<% else %>
<%= reduce[:n_states] %>u},
<% end %> <% end %>
},
<% end %> <% end %>
}; };
@@ -765,7 +807,7 @@ static void state_values_stack_push(state_values_stack_t * stack)
if (current_length >= current_capacity) if (current_length >= current_capacity)
{ {
size_t const new_capacity = current_capacity * 2u; size_t const new_capacity = current_capacity * 2u;
state_value_t * new_entries = malloc(new_capacity * sizeof(state_value_t)); state_value_t * new_entries = (state_value_t *)malloc(new_capacity * sizeof(state_value_t));
memcpy(new_entries, stack->entries, current_length * sizeof(state_value_t)); memcpy(new_entries, stack->entries, current_length * sizeof(state_value_t));
free(stack->entries); free(stack->entries);
stack->capacity = new_capacity; stack->capacity = new_capacity;
@@ -799,7 +841,7 @@ static void state_values_stack_free(state_values_stack_t * stack)
free(stack->entries); free(stack->entries);
} }
<% unless @grammar.ast %> <% unless @grammar.tree %>
/** /**
* Execute user code associated with a parser rule. * Execute user code associated with a parser rule.
* *
@@ -882,6 +924,8 @@ static size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token)
* *
* @param context * @param context
* Lexer/parser context structure. * Lexer/parser context structure.
* @start_state_id
* ID of the state in which to start.
* *
* @retval P_SUCCESS * @retval P_SUCCESS
* The parser successfully matched the input text. The parse result value * The parser successfully matched the input text. The parse result value
@@ -894,19 +938,20 @@ static size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token)
* @reval P_UNEXPECTED_INPUT * @reval P_UNEXPECTED_INPUT
* Input text does not match any lexer pattern. * Input text does not match any lexer pattern.
*/ */
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context) static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start_state_id)
{ {
<%= @grammar.prefix %>token_info_t token_info; <%= @grammar.prefix %>token_info_t token_info;
<%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID; <%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID;
state_values_stack_t statevalues; state_values_stack_t statevalues;
size_t reduced_rule_set = INVALID_ID; size_t reduced_rule_set = INVALID_ID;
<% if @grammar.ast %> <% if @grammar.tree %>
void * reduced_parser_node; void * reduced_parser_node;
<% else %> <% else %>
<%= @grammar.prefix %>value_t reduced_parser_value; <%= @grammar.prefix %>value_t reduced_parser_value;
<% end %> <% end %>
state_values_stack_init(&statevalues); state_values_stack_init(&statevalues);
state_values_stack_push(&statevalues); state_values_stack_push(&statevalues);
state_values_stack_index(&statevalues, -1)->state_id = start_state_id;
size_t result; size_t result;
for (;;) for (;;)
{ {
@@ -931,8 +976,8 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
if ((shift_state != INVALID_ID) && (token == TOKEN___EOF)) if ((shift_state != INVALID_ID) && (token == TOKEN___EOF))
{ {
/* Successful parse. */ /* Successful parse. */
<% if @grammar.ast %> <% if @grammar.tree %>
context->parse_result = (<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> *)state_values_stack_index(&statevalues, -1)->ast_node; context->parse_result = state_values_stack_index(&statevalues, -1)->tree_node;
<% else %> <% else %>
context->parse_result = state_values_stack_index(&statevalues, -1)->pvalue; context->parse_result = state_values_stack_index(&statevalues, -1)->pvalue;
<% end %> <% end %>
@@ -948,11 +993,15 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
if (reduced_rule_set == INVALID_ID) if (reduced_rule_set == INVALID_ID)
{ {
/* We shifted a token, mark it consumed. */ /* We shifted a token, mark it consumed. */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> * token_ast_node = malloc(sizeof(<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>)); <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> * token_tree_node = (<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> *)malloc(sizeof(<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>));
token_ast_node->token = token; token_tree_node->position = token_info.position;
token_ast_node->pvalue = token_info.pvalue; token_tree_node->end_position = token_info.end_position;
state_values_stack_index(&statevalues, -1)->ast_node = token_ast_node; token_tree_node->n_fields = 0u;
token_tree_node->is_token = 1u;
token_tree_node->token = token;
token_tree_node->pvalue = token_info.pvalue;
state_values_stack_index(&statevalues, -1)->tree_node = token_tree_node;
<% else %> <% else %>
state_values_stack_index(&statevalues, -1)->pvalue = token_info.pvalue; state_values_stack_index(&statevalues, -1)->pvalue = token_info.pvalue;
<% end %> <% end %>
@@ -961,11 +1010,12 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
else else
{ {
/* We shifted a RuleSet. */ /* We shifted a RuleSet. */
<% if @grammar.ast %> <% if @grammar.tree %>
state_values_stack_index(&statevalues, -1)->ast_node = reduced_parser_node; state_values_stack_index(&statevalues, -1)->tree_node = reduced_parser_node;
<% else %> <% else %>
state_values_stack_index(&statevalues, -1)->pvalue = reduced_parser_value; state_values_stack_index(&statevalues, -1)->pvalue = reduced_parser_value;
<%= @grammar.prefix %>value_t new_parse_result = {0}; <%= @grammar.prefix %>value_t new_parse_result;
memset(&new_parse_result, 0, sizeof(new_parse_result));
reduced_parser_value = new_parse_result; reduced_parser_value = new_parse_result;
<% end %> <% end %>
reduced_rule_set = INVALID_ID; reduced_rule_set = INVALID_ID;
@@ -977,38 +1027,60 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
if (reduce_index != INVALID_ID) if (reduce_index != INVALID_ID)
{ {
/* We have something to reduce. */ /* We have something to reduce. */
<% if @grammar.ast %> <% if @grammar.tree %>
if (parser_reduce_table[reduce_index].propagate_optional_target) if (parser_reduce_table[reduce_index].propagate_optional_target)
{ {
reduced_parser_node = state_values_stack_index(&statevalues, -1)->ast_node; reduced_parser_node = state_values_stack_index(&statevalues, -1)->tree_node;
} }
else if (parser_reduce_table[reduce_index].n_states > 0) else if (parser_reduce_table[reduce_index].n_states > 0)
{ {
void ** node_fields = calloc(parser_reduce_table[reduce_index].rule_set_node_field_array_size, sizeof(void *)); size_t n_fields = parser_reduce_table[reduce_index].rule_set_node_field_array_size;
size_t bytes = sizeof(TreeNode) + n_fields * sizeof(void *);
TreeNode * node = (TreeNode *)malloc(bytes);
memset(node, 0, bytes);
node->position = INVALID_POSITION;
node->end_position = INVALID_POSITION;
node->n_fields = n_fields;
if (parser_reduce_table[reduce_index].rule_set_node_field_index_map == NULL) if (parser_reduce_table[reduce_index].rule_set_node_field_index_map == NULL)
{ {
for (size_t i = 0; i < parser_reduce_table[reduce_index].n_states; i++) for (size_t i = 0; i < parser_reduce_table[reduce_index].n_states; i++)
{ {
node_fields[i] = state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node; node->fields[i] = (TreeNode *)state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->tree_node;
} }
} }
else else
{ {
for (size_t i = 0; i < parser_reduce_table[reduce_index].n_states; i++) for (size_t i = 0; i < parser_reduce_table[reduce_index].n_states; i++)
{ {
node_fields[parser_reduce_table[reduce_index].rule_set_node_field_index_map[i]] = state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node; node->fields[parser_reduce_table[reduce_index].rule_set_node_field_index_map[i]] = (TreeNode *)state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->tree_node;
} }
} }
reduced_parser_node = node_fields; bool position_found = false;
for (size_t i = 0; i < n_fields; i++)
{
TreeNode * child = node->fields[i];
if ((child != NULL) && <%= @grammar.prefix %>position_valid(child->position))
{
if (!position_found)
{
node->position = child->position;
position_found = true;
}
node->end_position = child->end_position;
}
}
reduced_parser_node = node;
} }
else else
{ {
reduced_parser_node = NULL; reduced_parser_node = NULL;
} }
<% else %> <% else %>
<%= @grammar.prefix %>value_t reduced_parser_value2 = {0}; <%= @grammar.prefix %>value_t reduced_parser_value2;
memset(&reduced_parser_value2, 0, sizeof(reduced_parser_value2));
if (parser_user_code(&reduced_parser_value2, parser_reduce_table[reduce_index].rule, &statevalues, parser_reduce_table[reduce_index].n_states, context) == P_USER_TERMINATED) if (parser_user_code(&reduced_parser_value2, parser_reduce_table[reduce_index].rule, &statevalues, parser_reduce_table[reduce_index].n_states, context) == P_USER_TERMINATED)
{ {
state_values_stack_free(&statevalues);
return P_USER_TERMINATED; return P_USER_TERMINATED;
} }
reduced_parser_value = reduced_parser_value2; reduced_parser_value = reduced_parser_value2;
@@ -1032,6 +1104,19 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
return result; return result;
} }
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
{
return parse_from(context, 0u);
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return parse_from(context, <%= i %>u);
}
<% end %>
/** /**
* Get the parse result value. * Get the parse result value.
* *
@@ -1040,18 +1125,29 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
* *
* @return Parse result value. * @return Parse result value.
*/ */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) <%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
{
return (<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> *) context->parse_result;
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return (<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> *) context->parse_result;
}
<% end %>
<% else %> <% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% end %>
{ {
<% if @grammar.ast %>
return context->parse_result;
<% else %>
return context->parse_result.v_<%= start_rule_type[0] %>; return context->parse_result.v_<%= start_rule_type[0] %>;
<% end %>
} }
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
<%= start_rule_type(i)[1] %> <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return context->parse_result.v_<%= start_rule_type(i)[0] %>;
}
<% end %>
<% end %>
/** /**
* Get the current text input position. * Get the current text input position.
@@ -1088,3 +1184,45 @@ size_t <%= @grammar.prefix %>user_terminate_code(<%= @grammar.prefix %>context_t
{ {
return context->token; return context->token;
} }
<% if @grammar.tree %>
static void free_tree_node(TreeNode * node)
{
if (node->is_token)
{
<% if @grammar.free_token_node %>
<%= @grammar.free_token_node %>((<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> *) node);
<% end %>
/* TODO: free value_t */
}
else if (node->n_fields > 0u)
{
for (size_t i = 0u; i < node->n_fields; i++)
{
if (node->fields[i] != NULL)
{
free_tree_node(node->fields[i]);
}
}
}
free(node);
}
/**
* Free all tree node memory.
*/
void <%= @grammar.prefix %>free_tree(<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * tree)
{
free_tree_node((TreeNode *)tree);
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
/**
* Free all tree node memory.
*/
void <%= @grammar.prefix %>free_tree_<%= start_rule %>(<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * tree)
{
free_tree_node((TreeNode *)tree);
}
<% end %>
<% end %>
+160 -65
View File
@@ -8,6 +8,9 @@
module <%= @grammar.modulename %>; module <%= @grammar.modulename %>;
<% end %> <% end %>
import core.memory;
import core.stdc.stdlib : malloc;
/************************************************************************** /**************************************************************************
* User code blocks * User code blocks
*************************************************************************/ *************************************************************************/
@@ -49,7 +52,30 @@ public enum : <%= @grammar.prefix %>token_t
/** Code point type. */ /** Code point type. */
public alias <%= @grammar.prefix %>code_point_t = uint; public alias <%= @grammar.prefix %>code_point_t = uint;
<% if @grammar.ast %> /**
* A structure to keep track of input position.
*
* This is useful for reporting errors, etc...
*/
public struct <%= @grammar.prefix %>position_t
{
/** Input text row (0-based). */
uint row;
/** Input text column (0-based). */
uint col;
/** Invalid position value. */
enum INVALID = <%= @grammar.prefix %>position_t(0u, 0u);
/** Return whether the position is valid. */
public @property bool valid()
{
return row != 0u;
}
}
<% if @grammar.tree %>
/** Parser values type. */ /** Parser values type. */
public alias <%= @grammar.prefix %>value_t = <%= @grammar.ptype %>; public alias <%= @grammar.prefix %>value_t = <%= @grammar.ptype %>;
<% else %> <% else %>
@@ -62,10 +88,21 @@ public union <%= @grammar.prefix %>value_t
} }
<% end %> <% end %>
<% if @grammar.ast %> <% if @grammar.tree %>
/** AST node types. @{ */ /** Common tree node structure. */
public struct <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> private struct TreeNode
{ {
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
void *[0] fields;
}
/** Tree node types. @{ */
public struct <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>
{
/* TreeNode fields must be present in the same order here. */
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
<%= @grammar.prefix %>token_t token; <%= @grammar.prefix %>token_t token;
<%= @grammar.prefix %>value_t pvalue; <%= @grammar.prefix %>value_t pvalue;
} }
@@ -73,9 +110,11 @@ public struct <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>
<% @parser.rule_sets.each do |name, rule_set| %> <% @parser.rule_sets.each do |name, rule_set| %>
<% next if name.start_with?("$") %> <% next if name.start_with?("$") %>
<% next if rule_set.optional? %> <% next if rule_set.optional? %>
public struct <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %> public struct <%= @grammar.tree_prefix %><%= name %><%= @grammar.tree_suffix %>
{ {
<% rule_set.ast_fields.each do |fields| %> <%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
<% rule_set.tree_fields.each do |fields| %>
union union
{ {
<% fields.each do |field_name, type| %> <% fields.each do |field_name, type| %>
@@ -89,26 +128,15 @@ public struct <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %>
/** @} */ /** @} */
<% end %> <% end %>
/**
* A structure to keep track of parser position.
*
* This is useful for reporting errors, etc...
*/
public struct <%= @grammar.prefix %>position_t
{
/** Input text row (0-based). */
uint row;
/** Input text column (0-based). */
uint col;
}
/** Lexed token information. */ /** Lexed token information. */
public struct <%= @grammar.prefix %>token_info_t public struct <%= @grammar.prefix %>token_info_t
{ {
/** Text position where the token was found. */ /** Text position of first code point in token. */
<%= @grammar.prefix %>position_t position; <%= @grammar.prefix %>position_t position;
/** Text position of last code point in token. */
<%= @grammar.prefix %>position_t end_position;
/** Number of input bytes used by the token. */ /** Number of input bytes used by the token. */
size_t length; size_t length;
@@ -144,8 +172,8 @@ public struct <%= @grammar.prefix %>context_t
/* Parser context data. */ /* Parser context data. */
/** Parse result value. */ /** Parse result value. */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * parse_result; void * parse_result;
<% else %> <% else %>
<%= @grammar.prefix %>value_t parse_result; <%= @grammar.prefix %>value_t parse_result;
<% end %> <% end %>
@@ -208,6 +236,8 @@ public void <%= @grammar.prefix %>context_init(<%= @grammar.prefix %>context_t *
/* Lexer initialization. */ /* Lexer initialization. */
newcontext.input = input; newcontext.input = input;
newcontext.text_position.row = 1u;
newcontext.text_position.col = 1u;
newcontext.mode = <%= @lexer.mode_id("default") %>; newcontext.mode = <%= @lexer.mode_id("default") %>;
/* Copy to the user's context structure. */ /* Copy to the user's context structure. */
@@ -372,7 +402,10 @@ private struct lexer_match_info_t
/** Number of bytes of input text used to match. */ /** Number of bytes of input text used to match. */
size_t length; size_t length;
/** Input text position delta. */ /** Input text position delta to end of token. */
<%= @grammar.prefix %>position_t end_delta_position;
/** Input text position delta to next code point after token end. */
<%= @grammar.prefix %>position_t delta_position; <%= @grammar.prefix %>position_t delta_position;
/** Accepting lexer state from the match. */ /** Accepting lexer state from the match. */
@@ -500,10 +533,11 @@ private size_t find_longest_match(<%= @grammar.prefix %>context_t * context,
if (transition_state != INVALID_LEXER_STATE_ID) if (transition_state != INVALID_LEXER_STATE_ID)
{ {
attempt_match.length += code_point_length; attempt_match.length += code_point_length;
attempt_match.end_delta_position = attempt_match.delta_position;
if (code_point == '\n') if (code_point == '\n')
{ {
attempt_match.delta_position.row++; attempt_match.delta_position.row++;
attempt_match.delta_position.col = 0u; attempt_match.delta_position.col = 1u;
} }
else else
{ {
@@ -586,7 +620,6 @@ private size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%=
<%= @grammar.prefix %>token_info_t token_info; <%= @grammar.prefix %>token_info_t token_info;
token_info.position = context.text_position; token_info.position = context.text_position;
token_info.token = INVALID_TOKEN_ID; token_info.token = INVALID_TOKEN_ID;
*out_token_info = token_info; // TODO: remove
lexer_match_info_t match_info; lexer_match_info_t match_info;
size_t unexpected_input_length; size_t unexpected_input_length;
size_t result = find_longest_match(context, &match_info, &unexpected_input_length); size_t result = find_longest_match(context, &match_info, &unexpected_input_length);
@@ -633,11 +666,22 @@ private size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%=
} }
token_info.token = token_to_accept; token_info.token = token_to_accept;
token_info.length = match_info.length; token_info.length = match_info.length;
if (match_info.end_delta_position.row != 0u)
{
token_info.end_position.row = token_info.position.row + match_info.end_delta_position.row;
token_info.end_position.col = match_info.end_delta_position.col;
}
else
{
token_info.end_position.row = token_info.position.row;
token_info.end_position.col = token_info.position.col + match_info.end_delta_position.col;
}
*out_token_info = token_info; *out_token_info = token_info;
return P_SUCCESS; return P_SUCCESS;
case P_EOF: case P_EOF:
token_info.token = TOKEN___EOF; token_info.token = TOKEN___EOF;
token_info.end_position = token_info.position;
*out_token_info = token_info; *out_token_info = token_info;
return P_SUCCESS; return P_SUCCESS;
@@ -753,7 +797,7 @@ private struct reduce_t
* reduce action. * reduce action.
*/ */
parser_state_id_t n_states; parser_state_id_t n_states;
<% if @grammar.ast %> <% if @grammar.tree %>
/** /**
* Map of rule components to rule set child fields. * Map of rule components to rule set child fields.
@@ -761,7 +805,7 @@ private struct reduce_t
immutable(ushort) * rule_set_node_field_index_map; immutable(ushort) * rule_set_node_field_index_map;
/** /**
* Number of rule set AST node fields. * Number of rule set tree node fields.
*/ */
ushort rule_set_node_field_array_size; ushort rule_set_node_field_array_size;
@@ -803,9 +847,9 @@ private struct state_value_t
/** Parser value from this state. */ /** Parser value from this state. */
<%= @grammar.prefix %>value_t pvalue; <%= @grammar.prefix %>value_t pvalue;
<% if @grammar.ast %> <% if @grammar.tree %>
/** AST node. */ /** Tree node. */
void * ast_node; void * tree_node;
<% end %> <% end %>
this(size_t state_id) this(size_t state_id)
@@ -817,11 +861,11 @@ private struct state_value_t
/** Parser shift table. */ /** Parser shift table. */
private immutable shift_t[] parser_shift_table = [ private immutable shift_t[] parser_shift_table = [
<% @parser.shift_table.each do |shift| %> <% @parser.shift_table.each do |shift| %>
shift_t(<%= shift[:symbol_id] %>u, <%= shift[:state_id] %>u), shift_t(<%= shift[:symbol].id %>u, <%= shift[:state_id] %>u),
<% end %> <% end %>
]; ];
<% if @grammar.ast %> <% if @grammar.tree %>
<% @grammar.rules.each do |rule| %> <% @grammar.rules.each do |rule| %>
<% unless rule.flat_rule_set_node_field_index_map? %> <% unless rule.flat_rule_set_node_field_index_map? %>
immutable ushort[<%= rule.rule_set_node_field_index_map.size %>] r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_map = [<%= rule.rule_set_node_field_index_map.map {|v| v.to_s}.join(", ") %>]; immutable ushort[<%= rule.rule_set_node_field_index_map.size %>] r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_map = [<%= rule.rule_set_node_field_index_map.map {|v| v.to_s}.join(", ") %>];
@@ -832,17 +876,22 @@ immutable ushort[<%= rule.rule_set_node_field_index_map.size %>] r_<%= rule.name
/** Parser reduce table. */ /** Parser reduce table. */
private immutable reduce_t[] parser_reduce_table = [ private immutable reduce_t[] parser_reduce_table = [
<% @parser.reduce_table.each do |reduce| %> <% @parser.reduce_table.each do |reduce| %>
reduce_t(<%= reduce[:token_id] %>u, <%= reduce[:rule_id] %>u, <%= reduce[:rule_set_id] %>u, <%= reduce[:n_states] %>u reduce_t(
<% if @grammar.ast %> <%= reduce[:token_id] %>u, /* Token: <%= reduce[:token] ? reduce[:token].name : "(any)" %> */
<%= reduce[:rule_id] %>u, /* Rule ID */
<%= reduce[:rule_set_id] %>u, /* Rule set ID (<%= reduce[:rule].rule_set.name %>) */
<% if @grammar.tree %>
<%= reduce[:n_states] %>u, /* Number of states */
<% if reduce[:rule].flat_rule_set_node_field_index_map? %> <% if reduce[:rule].flat_rule_set_node_field_index_map? %>
, null null, /* No rule set node field index map (flat map) */
<% else %> <% else %>
, &r_<%= reduce[:rule].name.gsub("$", "_") %><%= reduce[:rule].id %>_node_field_index_map[0] &r_<%= reduce[:rule].name.gsub("$", "_") %><%= reduce[:rule].id %>_node_field_index_map[0], /* Rule set node field index map */
<% end %> <% end %>
, <%= reduce[:rule].rule_set.ast_fields.size %> <%= reduce[:rule].rule_set.tree_fields.size %>, /* Number of tree fields */
, <%= reduce[:propagate_optional_target] %> <%= reduce[:propagate_optional_target] %>), /* Propagate optional target? */
<% else %>
<%= reduce[:n_states] %>u), /* Number of states */
<% end %> <% end %>
),
<% end %> <% end %>
]; ];
@@ -853,7 +902,7 @@ private immutable parser_state_t[] parser_state_table = [
<% end %> <% end %>
]; ];
<% unless @grammar.ast %> <% unless @grammar.tree %>
/** /**
* Execute user code associated with a parser rule. * Execute user code associated with a parser rule.
* *
@@ -936,6 +985,8 @@ private size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token
* *
* @param context * @param context
* Lexer/parser context structure. * Lexer/parser context structure.
* @start_state_id
* ID of the state in which to start.
* *
* @retval P_SUCCESS * @retval P_SUCCESS
* The parser successfully matched the input text. The parse result value * The parser successfully matched the input text. The parse result value
@@ -948,13 +999,14 @@ private size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token
* @reval P_UNEXPECTED_INPUT * @reval P_UNEXPECTED_INPUT
* Input text does not match any lexer pattern. * Input text does not match any lexer pattern.
*/ */
public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context) private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start_state_id)
{ {
<%= @grammar.prefix %>token_info_t token_info; <%= @grammar.prefix %>token_info_t token_info;
<%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID; <%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID;
state_value_t[] statevalues = new state_value_t[](1); state_value_t[] statevalues = new state_value_t[](1);
statevalues[0].state_id = start_state_id;
size_t reduced_rule_set = INVALID_ID; size_t reduced_rule_set = INVALID_ID;
<% if @grammar.ast %> <% if @grammar.tree %>
void * reduced_parser_node; void * reduced_parser_node;
<% else %> <% else %>
<%= @grammar.prefix %>value_t reduced_parser_value; <%= @grammar.prefix %>value_t reduced_parser_value;
@@ -981,8 +1033,8 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
if ((shift_state != INVALID_ID) && (token == TOKEN___EOF)) if ((shift_state != INVALID_ID) && (token == TOKEN___EOF))
{ {
/* Successful parse. */ /* Successful parse. */
<% if @grammar.ast %> <% if @grammar.tree %>
context.parse_result = cast(<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> *)statevalues[$-1].ast_node; context.parse_result = statevalues[$-1].tree_node;
<% else %> <% else %>
context.parse_result = statevalues[$-1].pvalue; context.parse_result = statevalues[$-1].pvalue;
<% end %> <% end %>
@@ -996,9 +1048,9 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
if (reduced_rule_set == INVALID_ID) if (reduced_rule_set == INVALID_ID)
{ {
/* We shifted a token, mark it consumed. */ /* We shifted a token, mark it consumed. */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> * token_ast_node = new <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>(token, token_info.pvalue); <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> * token_tree_node = new <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>(token_info.position, token_info.end_position, token, token_info.pvalue);
statevalues[$-1].ast_node = token_ast_node; statevalues[$-1].tree_node = token_tree_node;
<% else %> <% else %>
statevalues[$-1].pvalue = token_info.pvalue; statevalues[$-1].pvalue = token_info.pvalue;
<% end %> <% end %>
@@ -1007,8 +1059,8 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
else else
{ {
/* We shifted a RuleSet. */ /* We shifted a RuleSet. */
<% if @grammar.ast %> <% if @grammar.tree %>
statevalues[$-1].ast_node = reduced_parser_node; statevalues[$-1].tree_node = reduced_parser_node;
<% else %> <% else %>
statevalues[$-1].pvalue = reduced_parser_value; statevalues[$-1].pvalue = reduced_parser_value;
<%= @grammar.prefix %>value_t new_parse_result; <%= @grammar.prefix %>value_t new_parse_result;
@@ -1023,33 +1075,52 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
if (reduce_index != INVALID_ID) if (reduce_index != INVALID_ID)
{ {
/* We have something to reduce. */ /* We have something to reduce. */
<% if @grammar.ast %> <% if @grammar.tree %>
if (parser_reduce_table[reduce_index].propagate_optional_target) if (parser_reduce_table[reduce_index].propagate_optional_target)
{ {
reduced_parser_node = statevalues[$ - 1].ast_node; reduced_parser_node = statevalues[$ - 1].tree_node;
} }
else if (parser_reduce_table[reduce_index].n_states > 0) else if (parser_reduce_table[reduce_index].n_states > 0)
{ {
void *[] node_fields = new void *[parser_reduce_table[reduce_index].rule_set_node_field_array_size]; size_t n_fields = parser_reduce_table[reduce_index].rule_set_node_field_array_size;
foreach (i; 0..parser_reduce_table[reduce_index].rule_set_node_field_array_size) size_t node_size = TreeNode.sizeof + n_fields * (void *).sizeof;
TreeNode * node = cast(TreeNode *)malloc(node_size);
GC.addRange(node, node_size);
node.position = <%= @grammar.prefix %>position_t.INVALID;
node.end_position = <%= @grammar.prefix %>position_t.INVALID;
foreach (i; 0..n_fields)
{ {
node_fields[i] = null; node.fields[i] = null;
} }
if (parser_reduce_table[reduce_index].rule_set_node_field_index_map is null) if (parser_reduce_table[reduce_index].rule_set_node_field_index_map is null)
{ {
foreach (i; 0..parser_reduce_table[reduce_index].n_states) foreach (i; 0..parser_reduce_table[reduce_index].n_states)
{ {
node_fields[i] = statevalues[$ - parser_reduce_table[reduce_index].n_states + i].ast_node; node.fields[i] = statevalues[$ - parser_reduce_table[reduce_index].n_states + i].tree_node;
} }
} }
else else
{ {
foreach (i; 0..parser_reduce_table[reduce_index].n_states) foreach (i; 0..parser_reduce_table[reduce_index].n_states)
{ {
node_fields[parser_reduce_table[reduce_index].rule_set_node_field_index_map[i]] = statevalues[$ - parser_reduce_table[reduce_index].n_states + i].ast_node; node.fields[parser_reduce_table[reduce_index].rule_set_node_field_index_map[i]] = statevalues[$ - parser_reduce_table[reduce_index].n_states + i].tree_node;
} }
} }
reduced_parser_node = node_fields.ptr; bool position_found = false;
foreach (i; 0..n_fields)
{
TreeNode * child = cast(TreeNode *)node.fields[i];
if (child && child.position.valid)
{
if (!position_found)
{
node.position = child.position;
position_found = true;
}
node.end_position = child.end_position;
}
}
reduced_parser_node = node;
} }
else else
{ {
@@ -1079,6 +1150,19 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
} }
} }
public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
{
return parse_from(context, 0u);
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
public size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return parse_from(context, <%= i %>u);
}
<% end %>
/** /**
* Get the parse result value. * Get the parse result value.
* *
@@ -1087,18 +1171,29 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
* *
* @return Parse result value. * @return Parse result value.
*/ */
<% if @grammar.ast %> <% if @grammar.tree %>
public <%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) public <%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
{
return cast(<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> *)context.parse_result;
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
public <%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return cast(<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> *)context.parse_result;
}
<% end %>
<% else %> <% else %>
public <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) public <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% end %>
{ {
<% if @grammar.ast %>
return context.parse_result;
<% else %>
return context.parse_result.v_<%= start_rule_type[0] %>; return context.parse_result.v_<%= start_rule_type[0] %>;
<% end %>
} }
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
public <%= start_rule_type(i)[1] %> <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return context.parse_result.v_<%= start_rule_type(i)[0] %>;
}
<% end %>
<% end %>
/** /**
* Get the current text input position. * Get the current text input position.
+87 -55
View File
@@ -38,57 +38,8 @@ typedef <%= get_type_for(@grammar.terminate_token_id) %> <%= @grammar.prefix %>t
/** Code point type. */ /** Code point type. */
typedef uint32_t <%= @grammar.prefix %>code_point_t; typedef uint32_t <%= @grammar.prefix %>code_point_t;
/** User header code blocks. */
<%= @grammar.code_blocks.fetch("header", "") %>
<% if @grammar.ast %>
/** Parser values type. */
typedef <%= @grammar.ptype %> <%= @grammar.prefix %>value_t;
<% else %>
/** Parser values type(s). */
typedef union
{
<% @grammar.ptypes.each do |name, typestring| %>
<%= typestring %> v_<%= name %>;
<% end %>
} <%= @grammar.prefix %>value_t;
<% end %>
<% if @grammar.ast %>
/** AST node types. @{ */
typedef struct <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>
{
<%= @grammar.prefix %>token_t token;
<%= @grammar.prefix %>value_t pvalue;
} <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>;
<% @parser.rule_sets.each do |name, rule_set| %>
<% next if name.start_with?("$") %>
<% next if rule_set.optional? %>
struct <%= name %>;
<% end %>
<% @parser.rule_sets.each do |name, rule_set| %>
<% next if name.start_with?("$") %>
<% next if rule_set.optional? %>
typedef struct <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %>
{
<% rule_set.ast_fields.each do |fields| %>
union
{
<% fields.each do |field_name, type| %>
struct <%= type %> * <%= field_name %>;
<% end %>
};
<% end %>
} <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %>;
<% end %>
/** @} */
<% end %>
/** /**
* A structure to keep track of parser position. * A structure to keep track of input position.
* *
* This is useful for reporting errors, etc... * This is useful for reporting errors, etc...
*/ */
@@ -101,12 +52,77 @@ typedef struct
uint32_t col; uint32_t col;
} <%= @grammar.prefix %>position_t; } <%= @grammar.prefix %>position_t;
/** Return whether the position is valid. */
#define <%= @grammar.prefix %>position_valid(p) ((p).row != 0u)
/** User header code blocks. */
<%= @grammar.code_blocks.fetch("header", "") %>
<% if @grammar.tree %>
/** Parser values type. */
typedef <%= @grammar.ptype %> <%= @grammar.prefix %>value_t;
<% else %>
/** Parser values type(s). */
typedef union
{
<% @grammar.ptypes.each do |name, typestring| %>
<%= typestring %> v_<%= name %>;
<% end %>
} <%= @grammar.prefix %>value_t;
<% end %>
<% if @grammar.tree %>
/** Tree node types. @{ */
typedef struct <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>
{
<% # TreeNode fields must be present in the same order here. # %>
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
uint16_t n_fields;
uint8_t is_token;
<%= @grammar.prefix %>token_t token;
<%= @grammar.prefix %>value_t pvalue;
} <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>;
<% @parser.rule_sets.each do |name, rule_set| %>
<% next if name.start_with?("$") %>
<% next if rule_set.optional? %>
struct <%= name %>;
<% end %>
<% @parser.rule_sets.each do |name, rule_set| %>
<% next if name.start_with?("$") %>
<% next if rule_set.optional? %>
typedef struct <%= @grammar.tree_prefix %><%= name %><%= @grammar.tree_suffix %>
{
<% # TreeNode fields must be present in the same order here. # %>
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
uint16_t n_fields;
uint8_t is_token;
<% rule_set.tree_fields.each do |fields| %>
union
{
<% fields.each do |field_name, type| %>
struct <%= type %> * <%= field_name %>;
<% end %>
};
<% end %>
} <%= @grammar.tree_prefix %><%= name %><%= @grammar.tree_suffix %>;
<% end %>
/** @} */
<% end %>
/** Lexed token information. */ /** Lexed token information. */
typedef struct typedef struct
{ {
/** Text position where the token was found. */ /** Text position of first code point in token. */
<%= @grammar.prefix %>position_t position; <%= @grammar.prefix %>position_t position;
/** Text position of last code point in token. */
<%= @grammar.prefix %>position_t end_position;
/** Number of input bytes used by the token. */ /** Number of input bytes used by the token. */
size_t length; size_t length;
@@ -145,8 +161,8 @@ typedef struct
/* Parser context data. */ /* Parser context data. */
/** Parse result value. */ /** Parse result value. */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * parse_result; void * parse_result;
<% else %> <% else %>
<%= @grammar.prefix %>value_t parse_result; <%= @grammar.prefix %>value_t parse_result;
<% end %> <% end %>
@@ -173,11 +189,27 @@ size_t <%= @grammar.prefix %>decode_code_point(uint8_t const * input, size_t inp
size_t <%= @grammar.prefix %>lex(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>token_info_t * out_token_info); size_t <%= @grammar.prefix %>lex(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>token_info_t * out_token_info);
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context); size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context);
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context);
<% end %>
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context); <%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context);
<% end %>
<% else %> <% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context); <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
<%= start_rule_type(i)[1] %> <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context);
<% end %>
<% end %>
<% if @grammar.tree %>
void <%= @grammar.prefix %>free_tree(<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * tree);
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
void <%= @grammar.prefix %>free_tree_<%= start_rule %>(<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * tree);
<% end %>
<% end %> <% end %>
<%= @grammar.prefix %>position_t <%= @grammar.prefix %>position(<%= @grammar.prefix %>context_t * context); <%= @grammar.prefix %>position_t <%= @grammar.prefix %>position(<%= @grammar.prefix %>context_t * context);
+345 -79
View File
@@ -13,8 +13,10 @@ Propane is a LALR Parser Generator (LPG) which:
* generates a built-in lexer to tokenize input * generates a built-in lexer to tokenize input
* supports UTF-8 lexer inputs * supports UTF-8 lexer inputs
* generates a table-driven shift/reduce parser to parse input in linear time * generates a table-driven shift/reduce parser to parse input in linear time
* targets C or D language outputs * targets C, C++, or D language outputs
* optionally supports automatic full AST generation * optionally supports automatic full parse tree generation
* supports starting parsing from multiple start rules
* tracks input text start and end positions for all matched tokens/rules
* is MIT-licensed * is MIT-licensed
* is distributable as a standalone Ruby script * is distributable as a standalone Ruby script
@@ -35,9 +37,14 @@ Propane is typically invoked from the command-line as `./propane`.
Usage: ./propane [options] <input-file> <output-file> Usage: ./propane [options] <input-file> <output-file>
Options: Options:
--log LOG Write log file -h, --help Show this usage and exit.
--version Show program version and exit --log LOG Write log file. This will show all parser states and their
-h, --help Show this usage and exit associated shifts and reduces. It can be helpful when
debugging a grammar.
--version Show program version and exit.
-w Treat warnings as errors. This option will treat shift/reduce
conflicts as fatal errors and will print them to stderr in
addition to the log file.
The user must specify the path to a Propane input grammar file and a path to an The user must specify the path to a Propane input grammar file and a path to an
output file. output file.
@@ -61,10 +68,10 @@ import std.math;
ptype ulong; ptype ulong;
# A few basic arithmetic operators. # A few basic arithmetic operators.
token plus /\\+/; token plus /\+/;
token times /\\*/; token times /\*/;
token power /\\*\\*/; token power /\*\*/;
token integer /\\d+/ << token integer /\d+/ <<
ulong v; ulong v;
foreach (c; match) foreach (c; match)
{ {
@@ -73,10 +80,10 @@ token integer /\\d+/ <<
} }
$$ = v; $$ = v;
>> >>
token lparen /\\(/; token lparen /\(/;
token rparen /\\)/; token rparen /\)/;
# Drop whitespace. # Drop whitespace.
drop /\\s+/; drop /\s+/;
Start -> E1 << $$ = $1; >> Start -> E1 << $$ = $1; >>
E1 -> E2 << $$ = $1; >> E1 -> E2 << $$ = $1; >>
@@ -149,7 +156,7 @@ Example:
``` ```
ptype ulong; ptype ulong;
token integer /\\d+/ << token integer /\d+/ <<
ulong v; ulong v;
foreach (c; match) foreach (c; match)
{ {
@@ -183,21 +190,21 @@ rule.
Parser values for the rules or tokens in the rule pattern can be accessed Parser values for the rules or tokens in the rule pattern can be accessed
positionally with tokens `$1`, `$2`, `$3`, etc... positionally with tokens `$1`, `$2`, `$3`, etc...
Parser rule code blocks are not available in AST generation mode. Parser rule code blocks are not available in tree generation mode.
In AST generation mode, a full parse tree is automatically constructed in In tree generation mode, a full parse tree is automatically constructed in
memory for user code to traverse after parsing is complete. memory for user code to traverse after parsing is complete.
##> AST generation mode - the `ast` statement ##> Tree generation mode - the `tree` statement
To activate AST generation mode, place the `ast` statement in your grammar file: To activate tree generation mode, place the `tree` statement in your grammar file:
``` ```
ast; tree;
``` ```
It is recommended to place this statement early in the grammar. It is recommended to place this statement early in the grammar.
In AST generation mode various aspects of propane's behavior are changed: In tree generation mode various aspects of propane's behavior are changed:
* Only one `ptype` is allowed. * Only one `ptype` is allowed.
* Parser user code blocks are not supported. * Parser user code blocks are not supported.
@@ -208,10 +215,10 @@ In AST generation mode various aspects of propane's behavior are changed:
with the `start` grammar statement, the name of the start struct will be with the `start` grammar statement, the name of the start struct will be
given by the user-specified start rule instead of `Start`. given by the user-specified start rule instead of `Start`.
Example AST generation grammar: Example tree generation grammar:
``` ```
ast; tree;
ptype int; ptype int;
@@ -222,21 +229,21 @@ token two /2/;
token comma /,/ << token comma /,/ <<
$$ = 42; $$ = 42;
>> >>
token lparen /\\(/; token lparen /\(/;
token rparen /\\)/; token rparen /\)/;
drop /\\s+/; drop /\s+/;
Start -> Items; Start -> Items;
Items -> Item ItemsMore; Items -> Item:item ItemsMore;
Items -> ; Items -> ;
ItemsMore -> comma Item ItemsMore; ItemsMore -> comma Item:item ItemsMore;
ItemsMore -> ; ItemsMore -> ;
Item -> a; Item -> a;
Item -> b; Item -> b;
Item -> lparen Item rparen; Item -> lparen Item:item rparen;
Item -> Dual; Item -> Dual;
Dual -> One Two; Dual -> One Two;
@@ -257,44 +264,44 @@ Start * start = p_result(&context);
assert(start.pItems1 !is null); assert(start.pItems1 !is null);
assert(start.pItems !is null); assert(start.pItems !is null);
Items * items = start.pItems; Items * items = start.pItems;
assert(items.pItem !is null); assert(items.item !is null);
assert(items.pItem.pToken1 !is null); assert(items.item.pToken1 !is null);
assert_eq(TOKEN_a, items.pItem.pToken1.token); assert_eq(TOKEN_a, items.item.pToken1.token);
assert_eq(11, items.pItem.pToken1.pvalue); assert_eq(11, items.item.pToken1.pvalue);
assert(items.pItemsMore !is null); assert(items.pItemsMore !is null);
ItemsMore * itemsmore = items.pItemsMore; ItemsMore * itemsmore = items.pItemsMore;
assert(itemsmore.pItem !is null); assert(itemsmore.item !is null);
assert(itemsmore.pItem.pItem !is null); assert(itemsmore.item.item !is null);
assert(itemsmore.pItem.pItem.pItem !is null); assert(itemsmore.item.item.item !is null);
assert(itemsmore.pItem.pItem.pItem.pToken1 !is null); assert(itemsmore.item.item.item.pToken1 !is null);
assert_eq(TOKEN_b, itemsmore.pItem.pItem.pItem.pToken1.token); assert_eq(TOKEN_b, itemsmore.item.item.item.pToken1.token);
assert_eq(22, itemsmore.pItem.pItem.pItem.pToken1.pvalue); assert_eq(22, itemsmore.item.item.item.pToken1.pvalue);
assert(itemsmore.pItemsMore !is null); assert(itemsmore.pItemsMore !is null);
itemsmore = itemsmore.pItemsMore; itemsmore = itemsmore.pItemsMore;
assert(itemsmore.pItem !is null); assert(itemsmore.item !is null);
assert(itemsmore.pItem.pToken1 !is null); assert(itemsmore.item.pToken1 !is null);
assert_eq(TOKEN_b, itemsmore.pItem.pToken1.token); assert_eq(TOKEN_b, itemsmore.item.pToken1.token);
assert_eq(22, itemsmore.pItem.pToken1.pvalue); assert_eq(22, itemsmore.item.pToken1.pvalue);
assert(itemsmore.pItemsMore is null); assert(itemsmore.pItemsMore is null);
``` ```
## `ast_prefix` and `ast_suffix` statements ## `tree_prefix` and `tree_suffix` statements
In AST generation mode, structure types are defined and named based on the In tree generation mode, structure types are defined and named based on the
rules in the grammar. rules in the grammar.
Additionally, a structure type called `Token` is generated to hold parsed Additionally, a structure type called `Token` is generated to hold parsed
token information. token information.
These structure names can be modified by using the `ast_prefix` or `ast_suffix` These structure names can be modified by using the `tree_prefix` or `tree_suffix`
statements in the grammar file. statements in the grammar file.
The field names that point to instances of the structures are not affected by The field names that point to instances of the structures are not affected by
the `ast_prefix` or `ast_suffix` values. the `tree_prefix` or `tree_suffix` values.
For example, if the following two lines were added to the example above: For example, if the following two lines were added to the example above:
``` ```
ast_prefix ABC; tree_prefix ABC;
ast_suffix XYZ; tree_suffix XYZ;
``` ```
Then the types would be used as such instead: Then the types would be used as such instead:
@@ -320,6 +327,36 @@ assert(itemsmore.pItem.pItem.pItem !is null);
assert(itemsmore.pItem.pItem.pItem.pToken1 !is null); assert(itemsmore.pItem.pItem.pItem.pToken1 !is null);
``` ```
## Freeing user-allocated memory in token node `pvalue`: the `free_token_node` statement
If user lexer code block allocates memory to store in a token node's `pvalue`,
the `free_token_node` grammar statement can be used to specify the name of a
function which will be called during the `p_free_tree()` call to free the memory
associated with a token node.
Example:
```
<<
static void free_token(Token * token)
{
free(token->pvalue);
}
>>
tree;
free_token_node free_token;
ptype int *;
token a <<
$$ = (int *)malloc(sizeof(int));
*$$ = 1;
>>
token b <<
$$ = (int *)malloc(sizeof(int));
*$$ = 2;
>>
Start -> a:a b:b;
```
##> Specifying tokens - the `token` statement ##> Specifying tokens - the `token` statement
The `token` statement allows defining a lexer token and a pattern to match that The `token` statement allows defining a lexer token and a pattern to match that
@@ -426,10 +463,10 @@ A regular expression begins and ends with a `/` character.
Example: Example:
``` ```
/#.*$/ /#.*/
``` ```
Regular expressions can include many special characters: Regular expressions can include many special characters/sequences:
* The `.` character matches any input character other than a newline. * The `.` character matches any input character other than a newline.
* The `*` character matches any number of the previous regex element. * The `*` character matches any number of the previous regex element.
@@ -441,14 +478,17 @@ Regular expressions can include many special characters:
* The `\` character escapes the following character and changes its meaning: * The `\` character escapes the following character and changes its meaning:
* The `\a` sequence matches an ASCII bell character (0x07). * The `\a` sequence matches an ASCII bell character (0x07).
* The `\b` sequence matches an ASCII backspace character (0x08). * The `\b` sequence matches an ASCII backspace character (0x08).
* The `\d` sequence matches any character `0` through `9`. * The `\d` sequence is shorthand for the `[0-9]` character class.
* The `\D` sequence matches every code point not matched by `\d`.
* The `\f` sequence matches an ASCII form feed character (0x0C). * The `\f` sequence matches an ASCII form feed character (0x0C).
* The `\n` sequence matches an ASCII new line character (0x0A). * The `\n` sequence matches an ASCII new line character (0x0A).
* The `\r` sequence matches an ASCII carriage return character (0x0D). * The `\r` sequence matches an ASCII carriage return character (0x0D).
* The `\s` sequence matches a space, horizontal tab `\t`, carriage return * The `\s` sequence is shorthand for the `[ \t\r\n\f\v]` character class.
`\r`, a form feed `\f`, or a vertical tab `\v` character. * The `\S` sequence matches every code point not matched by `\s`.
* The `\t` sequence matches an ASCII tab character (0x09). * The `\t` sequence matches an ASCII tab character (0x09).
* The `\v` sequence matches an ASCII vertical tab character (0x0B). * The `\v` sequence matches an ASCII vertical tab character (0x0B).
* The `\w` sequence is shorthand for the `[a-zA-Z0-9_]` character class.
* The `\W` sequence matches every code point not matched by `\w`.
* Any other character matches itself. * Any other character matches itself.
* The `|` character creates an alternate match. * The `|` character creates an alternate match.
@@ -502,7 +542,7 @@ tokenid str;
mystringvalue = ""; mystringvalue = "";
$mode(string); $mode(string);
>> >>
string: /[^"]+/ << mystringvalue += match; >> string: /[^"]+/ << mystringvalue ~= match; >>
string: /"/ << string: /"/ <<
$mode(default); $mode(default);
return $token(str); return $token(str);
@@ -530,6 +570,28 @@ It also returns the `str` token now that the token is complete.
Note that the token name `str` above could have been `string` instead - the Note that the token name `str` above could have been `string` instead - the
namespace for token names is distinct from the namespace for lexer modes. namespace for token names is distinct from the namespace for lexer modes.
Multiple modes can be specified for a token or pattern or drop statement.
For example, if the grammar wanted to only recognize an identifier following
a `.` token and not other keywords, it could switch to an `identonly` mode
when matching a `.`
The `ident` token pattern will be matched in either the `default` or
`identonly` mode.
```
ptype char;
token abc;
token def;
default, identonly: token ident /[a-z]+/ <<
$$ = match[0];
$mode(default);
return $token(ident);
>>
token dot /\./ <<
$mode(identonly);
>>
default, identonly: drop /\s+/;
```
##> Specifying parser value types - the `ptype` statement ##> Specifying parser value types - the `ptype` statement
The `ptype` statement is used to define parser value type(s). The `ptype` statement is used to define parser value type(s).
@@ -580,7 +642,7 @@ In this example:
* a reduced `Values`'s parser value has a type of `Value[]`. * a reduced `Values`'s parser value has a type of `Value[]`.
* a reduced `KeyValue`'s parser value has a type of `Value[string]`. * a reduced `KeyValue`'s parser value has a type of `Value[string]`.
When AST generation mode is active, the `ptype` functionality works differently. When tree generation mode is active, the `ptype` functionality works differently.
In this mode, only one `ptype` is used by the parser. In this mode, only one `ptype` is used by the parser.
Lexer user code blocks may assign a parse value to the generated `Token` node Lexer user code blocks may assign a parse value to the generated `Token` node
by assigning to `$$` within a lexer code block. by assigning to `$$` within a lexer code block.
@@ -601,6 +663,10 @@ This can be changed with the `start` statement.
The grammar file must define a rule with the name of the start rule name which The grammar file must define a rule with the name of the start rule name which
will be used as the top-level starting rule that the parser attempts to reduce. will be used as the top-level starting rule that the parser attempts to reduce.
Rule statements are composed of the name of the rule, a `->` token, the fields
defining the rule pattern that must be matched, and a terminating semicolon or
user code block.
Example: Example:
``` ```
@@ -629,11 +695,16 @@ E4 -> lparen E1 rparen << $$ = $2; >>
This example uses the default start rule name of `Start`. This example uses the default start rule name of `Start`.
A parser rule has zero or more terms on the right side of its definition. A parser rule has zero or more fields on the right side of its definition.
Each of these terms is either a token name or a rule name. Each of these fields is either a token name or a rule name.
A term can be immediately followed by a `?` character to signify that it is A field can be immediately followed by a `?` character to signify that it is
optional. optional.
Another example: A field can optionally be followed by a `:` and then a field alias name.
If present, the field alias name is used to refer to the field value in user
code blocks, or if tree generation mode is active, the field alias name is used
as the field name in the generated tree node structure.
An optional and named field must use the format `field?:name`.
Example:
``` ```
token public; token public;
@@ -641,19 +712,21 @@ token private;
token int; token int;
token ident /[a-zA-Z_][a-zA-Z_0-9]*/; token ident /[a-zA-Z_][a-zA-Z_0-9]*/;
token semicolon /;/; token semicolon /;/;
IntegerDeclaration -> Visibility? int ident semicolon; IntegerDeclaration -> Visibility?:visibility int ident:name semicolon;
Visibility -> public; Visibility -> public;
Visibility -> private; Visibility -> private;
``` ```
In a parser rule code block, parser values for the right side terms are In a parser rule code block, parser values for the right side fields are
accessible as `$1` for the first term's parser value, `$2` for the second accessible as `$1` for the first field's parser value, `$2` for the second
term's parser value, etc... field's parser value, etc...
For the `IntegerDeclaration` rule, the first field value can also be referred to as `${visibility}` and the third field value can also be referred
to as `${name}`.
The `$$` symbol accesses the output parser value for this rule. The `$$` symbol accesses the output parser value for this rule.
The above examples demonstrate how the parser values for the rule components The above examples demonstrate how the parser values for the rule components
can be used to produce the parser value for the accepted rule. can be used to produce the parser value for the accepted rule.
Parser rule code blocks are not allowed and not used when AST generation mode Parser rule code blocks are not allowed and not used when tree generation mode
is active. is active.
##> Specifying the parser start rule name - the `start` statement ##> Specifying the parser start rule name - the `start` statement
@@ -666,6 +739,22 @@ Example:
start MyStartRule; start MyStartRule;
``` ```
Multiple start rules can be specified, either with multiple `start` statements
or one `start` statement listing multiple start rules.
Example:
```
start Module ModuleItem Statement Expression;
```
When multiple start rules are specified, multiple `p_parse_*()` functions,
`p_result_*()`, and `p_free_tree_*()` functions (in tree mode) are generated.
A default `p_parse()`, `p_result()`, `p_free_tree()` are generated corresponding
to the first start rule.
Additionally, each start rule causes the generation of another version of each
of these functions, for example `p_parse_Statement()`, `p_result_Statement()`,
and `p_free_tree_Statement()`.
##> Specifying the parser module name - the `module` statement ##> Specifying the parser module name - the `module` statement
The `module` statement can be used to specify the module name for a generated The `module` statement can be used to specify the module name for a generated
@@ -709,7 +798,7 @@ Some example uses of this functionality could be to:
* Detect integer overflow when lexing an integer literal constant. * Detect integer overflow when lexing an integer literal constant.
* Detect and report an error as soon as possible during parsing before continuing to parse any more of the input. * Detect and report an error as soon as possible during parsing before continuing to parse any more of the input.
* Determine whether parsing should stop and instead be performed using a different parser version. * Determine whether parsing should stop and instead be retried using a different parser version.
To terminate parsing from a lexer or parser user code block, use the To terminate parsing from a lexer or parser user code block, use the
`$terminate(code)` function, passing an integer expression argument. `$terminate(code)` function, passing an integer expression argument.
@@ -745,10 +834,16 @@ Propane generates the following result code constants:
* `P_EOF`: The lexer reached the end of the input string. * `P_EOF`: The lexer reached the end of the input string.
* `P_USER_TERMINATED`: A parser user code block has requested to terminate the parser. * `P_USER_TERMINATED`: A parser user code block has requested to terminate the parser.
Result codes are returned by the functions `p_decode_input()`, `p_lex()`, and `p_parse()`. Result codes are returned by the API functions `p_decode_code_point()`, `p_lex()`, and `p_parse()`.
##> Types ##> Types
### `p_code_point_t`
The `p_code_point_t` type is aliased to a 32-bit unsigned integer.
It is used to store decoded code points from the input text and perform
lexing based on the grammar's lexer patterns.
### `p_context_t` ### `p_context_t`
Propane defines a `p_context_t` structure type. Propane defines a `p_context_t` structure type.
@@ -759,26 +854,56 @@ A pointer to this instance is passed to the generated functions.
### `p_position_t` ### `p_position_t`
The `p_position_t` structure contains two fields `row` and `col`. The `p_position_t` structure contains two fields: `row` and `col`.
These fields contain the 0-based row and column describing a parser position. These fields contain the 1-based row and column describing a parser position.
### AST Node Types For D targets, the `p_position_t` structure can be checked for validity by
querying the `valid` property.
If AST generation mode is enabled, a structure type for each rule will be For C targets, the `p_position_t` structure can be checked for validity by
calling `p_position_valid(pos)` where `pos` is a `p_position_t` structure
instance.
### `p_token_info_t`
The `p_token_info_t` structure contains the following fields:
* `position` (`p_position_t`) holds the text position of the first code point in the token.
* `end_position` (`p_position_t`) holds the text position of the last code point in the token.
* `length` (`size_t`) holds the number of input bytes used by the token.
* `token` (`p_token_t`) holds the token ID of the lexed token
* `pvalue` (`p_value_t`) holds the parser value associated with the token.
### Tree Node Types
If tree generation mode is enabled, a structure type for each rule will be
generated. generated.
The name of the structure type is given by the name of the rule. The name of the structure type is given by the name of the rule.
Additionally a structure type called `Token` is generated to represent an Additionally a structure type called `Token` is generated to represent a
AST node which refers to a raw parser token rather than a composite rule. tree node which refers to a raw parser token rather than a composite rule.
#### AST Node Fields #### Tree Node Fields
A `Token` node has two fields: All tree nodes have a `position` field specifying the text position of the
beginning of the matched token or rule, and an `end_position` field specifying
the text position of the end of the matched token or rule.
Each of these fields are instances of the `p_position_t` structure.
A `Token` node will always have a valid `position` and `end_position`.
A rule node may not have valid positions if the rule allows for an empty match.
In this case the `position` structure should be checked for validity before
using it.
For C targets this can be accomplished with
`if (p_position_valid(node->position))` and for D targets this can be
accomplished with `if (node.position.valid)`.
A `Token` node has the following additional fields:
* `token` which specifies which token was parsed (one of `TOKEN_*`) * `token` which specifies which token was parsed (one of `TOKEN_*`)
* `pvalue` which specifies the parser value for the token. If a lexer user * `pvalue` which specifies the parser value for the token. If a lexer user
code block assigned to `$$`, the assigned value will be stored here. code block assigned to `$$`, the assigned value will be stored here.
The other generated AST node structures have fields generated based on the Tree node structures for rules contain generated fields based on the
right hand side components specified for all rules of a given name. right hand side components specified for all rules of a given name.
In this example: In this example:
@@ -801,10 +926,10 @@ The `Items` structure will have fields:
* `pItemsMore` and `pItemsMore2` which point to the parsed `ItemsMore` structure. * `pItemsMore` and `pItemsMore2` which point to the parsed `ItemsMore` structure.
If a rule can be empty (for example in the second `Items` rule above), then If a rule can be empty (for example in the second `Items` rule above), then
an instance of a pointer to that rule's generated AST node will be null if the an instance of a pointer to that rule's generated tree node will be null if the
parser matches the empty rule definition. parser matches the empty rule pattern.
The non-positional AST node field pointer will not be generated if there are The non-positional tree node field pointer will not be generated if there are
multiple positions in which an instance of the node it points to could be multiple positions in which an instance of the node it points to could be
present. present.
For example, in the below rules: For example, in the below rules:
@@ -823,6 +948,19 @@ If the first rule is matched, then `pOne1` and `pTwo2` will be non-null while
`pTwo1` and `pOne2` will be null. `pTwo1` and `pOne2` will be null.
If the second rule is matched instead, then the opposite would be the case. If the second rule is matched instead, then the opposite would be the case.
If a field alias is present in a rule definition, an additional field will be
generated in the tree node with the field alias name.
For example:
```
Exp -> Exp:left plus ExpB:right;
```
In the generated `Exp` structure, the fields `pExp`, `pExp1`, and `left` will
all point to the same child node (an instance of the `Exp` structure), and the
fields `pExpB`, `pExpB3`, and `right` will all point to the same child node
(an instance of the `ExpB` structure).
##> Functions ##> Functions
### `p_context_init` ### `p_context_init`
@@ -846,6 +984,44 @@ p_context_t context;
p_context_init(&context, input); p_context_init(&context, input);
``` ```
### `p_lex`
The `p_lex()` function is the main entry point to the lexer.
It is normally called automatically by the generated parser to retrieve the
next input token for the parser and does not need to be called by the user.
However, the user may initialize a context and call `p_lex()` to use the
generated lexer in a standalone mode.
Example:
```
p_context_t context;
p_context_init(&context, input, input_length);
p_token_info_t token_info;
size_t result = p_lex(&context, &token_info);
switch (result)
{
case P_DECODE_ERROR:
/* UTF-8 decode error */
break;
case P_UNEXPECTED_INPUT:
/* Input text does not match any lexer pattern. */
break;
case P_USER_TERMINATED:
/* Lexer user code block requested to terminate the lexer. */
break;
case P_SUCCESS:
/*
* token_info.position holds the text position of the first code point in the token.
* token_info.end_position holds the text position of the last code point in the token.
* token_info.length holds the number of input bytes used by the token.
* token_info.token holds the token ID of the lexed token
* token_info.pvalue holds the parser value associated with the token.
*/
break;
}
```
### `p_parse` ### `p_parse`
The `p_parse()` function is the main entry point to the parser. The `p_parse()` function is the main entry point to the parser.
@@ -859,6 +1035,34 @@ p_context_init(&context, input, input_length);
size_t result = p_parse(&context); size_t result = p_parse(&context);
``` ```
When multiple start rules are specified, a separate parse function is generated
for each which starts parsing at the given rule.
For example, if `Statement` is specified as a start rule:
```
size_t result = p_parse_Statement(&context);
```
In this case, the parser will start parsing with the `Statement` rule.
### `p_position_valid`
The `p_position_valid()` function is only generated for C targets.
it is used to determine whether or not a `p_position_t` structure is valid.
Example:
```
if (p_position_valid(node->position))
{
....
}
```
For D targets, rather than using `p_position_valid()`, the `valid` property
function of the `p_position_t` structure can be queried
(e.g. `if (node.position.valid)`).
### `p_result` ### `p_result`
The `p_result()` function can be used to retrieve the final parse value after The `p_result()` function can be used to retrieve the final parse value after
@@ -876,8 +1080,25 @@ if (p_parse(&context) == P_SUCCESS)
} }
``` ```
If AST generation mode is active, then the `p_result()` function returns a If tree generation mode is active, then the `p_result()` function returns a
`Start *` pointing to the `Start` AST structure. `Start *` pointing to the `Start` tree node structure.
When multiple start rules are specified, a separate result function is generated
for each which returns the parse result for the corresponding rule.
For example, if `Statement` is specified as a start rule:
```
p_context_t context;
p_context_init(&context, input, input_length);
size_t result = p_parse(&context);
if (p_parse_Statement(&context) == P_SUCCESS)
{
result = p_result_Statement(&context);
}
```
In this case, the parser will start parsing with the `Statement` rule and the
parse result from the `Statement` rule will be returned.
### `p_position` ### `p_position`
@@ -931,6 +1152,51 @@ if (p_parse(&context) == P_UNEXPECTED_TOKEN)
} }
``` ```
### `p_decode_code_point`
The `p_decode_code_point()` function can be used to decode code points from a
UTF-8 string.
It does not require a lexer/parser context structure and can be used as a
standalone UTF-8 decoder or from within a lexer or parser user code block.
D Example:
```
size_t result;
p_code_point_t code_point;
ubyte code_point_length;
result = p_decode_code_point("\xf0\x9f\xa7\xa1", &code_point, &code_point_length);
assert(result == P_SUCCESS);
assert(code_point == 0x1F9E1u);
assert(code_point_length == 4u);
```
### `p_free_tree`
The `p_free_tree()` function can be used to free the memory used by the tree.
It should be passed the same value that is returned by `p_result()`.
The `p_free_tree()` function is only available for C/C++ output targets.
Note that if any lexer user code block allocates memory to store in a token's
`pvalue`, in order to properly free this memory a `free_token_node` function
should be specified in the grammar file.
If specified, the `free_token_node` function will be called during the
`p_free_tree()` process to allow user code to free any memory associated with
a token node's `pvalue`.
When multiple start rules are specified, a separate `p_free_tree` function is
generated for each which frees the tree resulting from parsing the given rule.
For example, if `Statement` is specified as a start rule:
```
p_free_tree_Statement(statement_tree);
```
In this case, Propane will free a `Statement` tree structure returned by the
`p_parse_Statement(&context)` function.
##> Data ##> Data
### `p_token_names` ### `p_token_names`
+1
View File
@@ -0,0 +1 @@
au BufNewFile,BufRead *.propane set filetype=propane
+33
View File
@@ -0,0 +1,33 @@
" Vim syntax file for Propane
" Language: propane
" Maintainer: Josh Holtrop
" URL: https://github.com/holtrop/propane
if exists("b:current_syntax")
finish
endif
if !exists("b:propane_subtype")
let b:propane_subtype = "d"
endif
exe "syn include @propaneTarget syntax/".b:propane_subtype.".vim"
syn region propaneTarget matchgroup=propaneDelimiter start="<<" end=">>$" contains=@propaneTarget keepend
syn match propaneComment "#.*"
syn match propaneOperator "->"
syn match propaneFieldAlias ":[a-zA-Z0-9_]\+" contains=propaneFieldOperator
syn match propaneFieldOperator ":" contained
syn match propaneOperator "?"
syn keyword propaneKeyword drop free_token_node module prefix ptype start token tokenid tree tree_prefix tree_suffix
syn region propaneRegex start="/" end="/" skip="\v\\\\|\\/"
hi def link propaneComment Comment
hi def link propaneKeyword Keyword
hi def link propaneRegex String
hi def link propaneOperator Operator
hi def link propaneFieldOperator Operator
hi def link propaneDelimiter Delimiter
hi def link propaneFieldAlias Identifier
+2 -2
View File
@@ -31,10 +31,10 @@ class Propane
class << self class << self
def run(input_file, output_file, log_file) def run(input_file, output_file, log_file, options)
begin begin
grammar = Grammar.new(File.read(input_file)) grammar = Grammar.new(File.read(input_file))
generator = Generator.new(grammar, output_file, log_file) generator = Generator.new(grammar, output_file, log_file, options)
generator.generate generator.generate
rescue Error => e rescue Error => e
$stderr.puts e.message $stderr.puts e.message
+12 -4
View File
@@ -4,15 +4,21 @@ class Propane
USAGE = <<EOF USAGE = <<EOF
Usage: #{$0} [options] <input-file> <output-file> Usage: #{$0} [options] <input-file> <output-file>
Options: Options:
--log LOG Write log file -h, --help Show this usage and exit.
--version Show program version and exit --log LOG Write log file. This will show all parser states and their
-h, --help Show this usage and exit associated shifts and reduces. It can be helpful when
debugging a grammar.
--version Show program version and exit.
-w Treat warnings as errors. This option will treat shift/reduce
conflicts as fatal errors and will print them to stderr in
addition to the log file.
EOF EOF
class << self class << self
def run(args) def run(args)
params = [] params = []
options = {}
log_file = nil log_file = nil
i = 0 i = 0
while i < args.size while i < args.size
@@ -29,6 +35,8 @@ EOF
when "-h", "--help" when "-h", "--help"
puts USAGE puts USAGE
return 0 return 0
when "-w"
options[:warnings_as_errors] = true
when /^-/ when /^-/
$stderr.puts "Error: unknown option #{arg}" $stderr.puts "Error: unknown option #{arg}"
return 1 return 1
@@ -45,7 +53,7 @@ EOF
$stderr.puts "Error: cannot read #{params[0]}" $stderr.puts "Error: cannot read #{params[0]}"
return 2 return 2
end end
Propane.run(*params, log_file) Propane.run(*params, log_file, options)
end end
end end
+39 -18
View File
@@ -2,7 +2,7 @@ class Propane
class Generator class Generator
def initialize(grammar, output_file, log_file) def initialize(grammar, output_file, log_file, options)
@grammar = grammar @grammar = grammar
@output_file = output_file @output_file = output_file
if log_file if log_file
@@ -11,23 +11,28 @@ class Propane
@log = StringIO.new @log = StringIO.new
end end
@language = @language =
if output_file =~ /\.([a-z]+)$/ if output_file.end_with?(".d")
$1
else
"d" "d"
else
"c"
end end
@options = options
process_grammar! process_grammar!
end end
def generate def generate
extensions = [@language] extensions = [nil]
if @language == "c" if @language == "c"
extensions += %w[h] extensions += %w[h]
end end
extensions.each do |extension| extensions.each do |extension|
template = Assets.get("parser.#{extension}.erb") template = Assets.get("parser.#{extension || @language}.erb")
if extension
output_file = @output_file.sub(%r{\.[a-z]+$}, ".#{extension}")
else
output_file = @output_file
end
erb = ERB.new(template, trim_mode: "<>") erb = ERB.new(template, trim_mode: "<>")
output_file = @output_file.sub(%r{\.[a-z]+$}, ".#{extension}")
result = erb.result(binding.clone) result = erb.result(binding.clone)
File.open(output_file, "wb") do |fh| File.open(output_file, "wb") do |fh|
fh.write(result) fh.write(result)
@@ -42,8 +47,8 @@ class Propane
# Assign default pattern mode to patterns without a mode assigned. # Assign default pattern mode to patterns without a mode assigned.
found_default = false found_default = false
@grammar.patterns.each do |pattern| @grammar.patterns.each do |pattern|
if pattern.mode.nil? if pattern.modes.empty?
pattern.mode = "default" pattern.modes << "default"
found_default = true found_default = true
end end
pattern.ptypename ||= "default" pattern.ptypename ||= "default"
@@ -66,12 +71,15 @@ class Propane
end end
tokens_by_name[token.name] = token tokens_by_name[token.name] = token
end end
# Check for user start rule. # Create real start rule(s).
unless @grammar.rules.find {|rule| rule.name == @grammar.start_rule} real_start_rules = @grammar.start_rules.map do |start_rule|
raise Error.new("Start rule `#{@grammar.start_rule}` not found") unless @grammar.rules.find {|rule| rule.name == start_rule}
raise Error.new("Start rule `#{start_rule}` not found")
end
Rule.new("$#{start_rule}", [start_rule, "$EOF"], nil, nil, nil)
end end
# Add "real" start rule. # Add real start rules before user-given rules.
@grammar.rules.unshift(Rule.new("$Start", [@grammar.start_rule, "$EOF"], nil, nil, nil)) @grammar.rules = real_start_rules + @grammar.rules
# Generate and add rules for optional components. # Generate and add rules for optional components.
generate_optional_component_rules!(tokens_by_name) generate_optional_component_rules!(tokens_by_name)
# Build rule sets. # Build rule sets.
@@ -129,7 +137,7 @@ class Propane
# Generate the lexer. # Generate the lexer.
@lexer = Lexer.new(@grammar) @lexer = Lexer.new(@grammar)
# Generate the parser. # Generate the parser.
@parser = Parser.new(@grammar, rule_sets, @log) @parser = Parser.new(@grammar, rule_sets, @log, @options)
end end
# Check that any referenced ptypes have been defined. # Check that any referenced ptypes have been defined.
@@ -275,9 +283,22 @@ class Propane
"statevalues[$-1-n_states+#{index}].pvalue.v_#{rule.components[index - 1].ptypename}" "statevalues[$-1-n_states+#{index}].pvalue.v_#{rule.components[index - 1].ptypename}"
end end
end end
code = code.gsub(/\$\{(\w+)\}/) do |match|
aliasname = $1
if index = rule.aliases[aliasname]
case @language
when "c"
"state_values_stack_index(statevalues, -(int)n_states + #{index})->pvalue.v_#{rule.components[index].ptypename}"
when "d"
"statevalues[$-n_states+#{index}].pvalue.v_#{rule.components[index].ptypename}"
end
else
raise Error.new("Field alias '#{aliasname}' not found")
end
end
else else
code = code.gsub(/\$\$/) do |match| code = code.gsub(/\$\$/) do |match|
if @grammar.ast if @grammar.tree
case @language case @language
when "c" when "c"
"out_token_info->pvalue" "out_token_info->pvalue"
@@ -314,9 +335,9 @@ class Propane
# #
# @return [Array<String>] # @return [Array<String>]
# Start rule parser value type name and type string. # Start rule parser value type name and type string.
def start_rule_type def start_rule_type(start_rule_index = 0)
start_rule = @grammar.rules.find do |rule| start_rule = @grammar.rules.find do |rule|
rule.name == @grammar.start_rule rule.name == @grammar.start_rules[start_rule_index]
end end
[start_rule.ptypename, @grammar.ptypes[start_rule.ptypename]] [start_rule.ptypename, @grammar.ptypes[start_rule.ptypename]]
end end
+70 -49
View File
@@ -5,13 +5,14 @@ class Propane
# Reserve identifiers beginning with a double-underscore for internal use. # Reserve identifiers beginning with a double-underscore for internal use.
IDENTIFIER_REGEX = /(?:[a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z_0-9]*/ IDENTIFIER_REGEX = /(?:[a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z_0-9]*/
attr_reader :ast attr_reader :tree
attr_reader :ast_prefix attr_reader :tree_prefix
attr_reader :ast_suffix attr_reader :tree_suffix
attr_reader :free_token_node
attr_reader :modulename attr_reader :modulename
attr_reader :patterns attr_reader :patterns
attr_reader :rules attr_accessor :rules
attr_reader :start_rule attr_reader :start_rules
attr_reader :tokens attr_reader :tokens
attr_reader :code_blocks attr_reader :code_blocks
attr_reader :ptypes attr_reader :ptypes
@@ -19,20 +20,22 @@ class Propane
def initialize(input) def initialize(input)
@patterns = [] @patterns = []
@start_rule = "Start" @start_rules = []
@tokens = [] @tokens = []
@rules = [] @rules = []
@code_blocks = {} @code_blocks = {}
@line_number = 1 @line_number = 1
@next_line_number = @line_number @next_line_number = @line_number
@mode = nil @modeline = nil
@input = input.gsub("\r\n", "\n") @input = input.gsub("\r\n", "\n")
@ptypes = {"default" => "void *"} @ptypes = {"default" => "void *"}
@prefix = "p_" @prefix = "p_"
@ast = false @tree = false
@ast_prefix = "" @tree_prefix = ""
@ast_suffix = "" @tree_suffix = ""
@free_token_node = nil
parse_grammar! parse_grammar!
@start_rules << "Start" if @start_rules.empty?
end end
def ptype def ptype
@@ -58,10 +61,11 @@ class Propane
def parse_statement! def parse_statement!
if parse_white_space! if parse_white_space!
elsif parse_comment_line! elsif parse_comment_line!
elsif @mode.nil? && parse_mode_label! elsif @modeline.nil? && parse_mode_label!
elsif parse_ast_statement! elsif parse_tree_statement!
elsif parse_ast_prefix_statement! elsif parse_tree_prefix_statement!
elsif parse_ast_suffix_statement! elsif parse_tree_suffix_statement!
elsif parse_free_token_node_statement!
elsif parse_module_statement! elsif parse_module_statement!
elsif parse_ptype_statement! elsif parse_ptype_statement!
elsif parse_pattern_statement! elsif parse_pattern_statement!
@@ -81,8 +85,8 @@ class Propane
end end
def parse_mode_label! def parse_mode_label!
if md = consume!(/(#{IDENTIFIER_REGEX})\s*:/) if md = consume!(/(#{IDENTIFIER_REGEX}(?:\s*,\s*#{IDENTIFIER_REGEX})*)\s*:/)
@mode = md[1] @modeline = md[1]
end end
end end
@@ -94,21 +98,27 @@ class Propane
consume!(/#.*\n/) consume!(/#.*\n/)
end end
def parse_ast_statement! def parse_tree_statement!
if consume!(/ast\s*;/) if consume!(/tree\s*;/)
@ast = true @tree = true
end end
end end
def parse_ast_prefix_statement! def parse_tree_prefix_statement!
if md = consume!(/ast_prefix\s+(\w+)\s*;/) if md = consume!(/tree_prefix\s+(\w+)\s*;/)
@ast_prefix = md[1] @tree_prefix = md[1]
end end
end end
def parse_ast_suffix_statement! def parse_tree_suffix_statement!
if md = consume!(/ast_suffix\s+(\w+)\s*;/) if md = consume!(/tree_suffix\s+(\w+)\s*;/)
@ast_suffix = md[1] @tree_suffix = md[1]
end
end
def parse_free_token_node_statement!
if md = consume!(/free_token_node\s+(\w+)\s*;/)
@free_token_node = md[1]
end end
end end
@@ -117,7 +127,7 @@ class Propane
md = consume!(/([\w.]+)\s*/, "expected module name") md = consume!(/([\w.]+)\s*/, "expected module name")
@modulename = md[1] @modulename = md[1]
consume!(/;/, "expected `;'") consume!(/;/, "expected `;'")
@mode = nil @modeline = nil
true true
end end
end end
@@ -126,8 +136,8 @@ class Propane
if consume!(/ptype\s+/) if consume!(/ptype\s+/)
name = "default" name = "default"
if md = consume!(/(#{IDENTIFIER_REGEX})\s*=\s*/) if md = consume!(/(#{IDENTIFIER_REGEX})\s*=\s*/)
if @ast if @tree
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
name = md[1] name = md[1]
end end
@@ -141,8 +151,8 @@ class Propane
md = consume!(/(#{IDENTIFIER_REGEX})\s*/, "expected token name") md = consume!(/(#{IDENTIFIER_REGEX})\s*/, "expected token name")
name = md[1] name = md[1]
if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/) if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/)
if @ast if @tree
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
ptypename = md[1] ptypename = md[1]
end end
@@ -153,9 +163,9 @@ class Propane
end end
token = Token.new(name, ptypename, @line_number) token = Token.new(name, ptypename, @line_number)
@tokens << token @tokens << token
pattern = Pattern.new(pattern: pattern, token: token, line_number: @line_number, code: code, mode: @mode, ptypename: ptypename) pattern = Pattern.new(pattern: pattern, token: token, line_number: @line_number, code: code, modes: get_modes_from_modeline, ptypename: ptypename)
@patterns << pattern @patterns << pattern
@mode = nil @modeline = nil
true true
end end
end end
@@ -165,15 +175,15 @@ class Propane
md = consume!(/(#{IDENTIFIER_REGEX})\s*/, "expected token name") md = consume!(/(#{IDENTIFIER_REGEX})\s*/, "expected token name")
name = md[1] name = md[1]
if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/) if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/)
if @ast if @tree
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
ptypename = md[1] ptypename = md[1]
end end
consume!(/;/, "expected `;'"); consume!(/;/, "expected `;'");
token = Token.new(name, ptypename, @line_number) token = Token.new(name, ptypename, @line_number)
@tokens << token @tokens << token
@mode = nil @modeline = nil
true true
end end
end end
@@ -186,8 +196,8 @@ class Propane
end end
consume!(/\s+/) consume!(/\s+/)
consume!(/;/, "expected `;'") consume!(/;/, "expected `;'")
@patterns << Pattern.new(pattern: pattern, line_number: @line_number, mode: @mode) @patterns << Pattern.new(pattern: pattern, line_number: @line_number, modes: get_modes_from_modeline)
@mode = nil @modeline = nil
true true
end end
end end
@@ -195,12 +205,12 @@ class Propane
def parse_rule_statement! def parse_rule_statement!
if md = consume!(/(#{IDENTIFIER_REGEX})\s*(?:\((#{IDENTIFIER_REGEX})\))?\s*->\s*/) if md = consume!(/(#{IDENTIFIER_REGEX})\s*(?:\((#{IDENTIFIER_REGEX})\))?\s*->\s*/)
rule_name, ptypename = *md[1, 2] rule_name, ptypename = *md[1, 2]
if @ast && ptypename if @tree && ptypename
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
md = consume!(/((?:#{IDENTIFIER_REGEX}\??\s*)*)\s*/, "expected rule component list") md = consume!(/((?:#{IDENTIFIER_REGEX}\??(?::#{IDENTIFIER_REGEX})?\s*)*)\s*/, "expected rule component list")
components = md[1].strip.split(/\s+/) components = md[1].strip.split(/\s+/)
if @ast if @tree
consume!(/;/, "expected `;'") consume!(/;/, "expected `;'")
else else
unless code = parse_code_block! unless code = parse_code_block!
@@ -208,7 +218,7 @@ class Propane
end end
end end
@rules << Rule.new(rule_name, components, code, ptypename, @line_number) @rules << Rule.new(rule_name, components, code, ptypename, @line_number)
@mode = nil @modeline = nil
true true
end end
end end
@@ -217,23 +227,26 @@ class Propane
if pattern = parse_pattern! if pattern = parse_pattern!
consume!(/\s+/) consume!(/\s+/)
if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/) if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/)
if @ast if @tree
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
ptypename = md[1] ptypename = md[1]
end end
unless code = parse_code_block! unless code = parse_code_block!
raise Error.new("Line #{@line_number}: expected code block to follow pattern") raise Error.new("Line #{@line_number}: expected code block to follow pattern")
end end
@patterns << Pattern.new(pattern: pattern, line_number: @line_number, code: code, mode: @mode, ptypename: ptypename) @patterns << Pattern.new(pattern: pattern, line_number: @line_number, code: code, modes: get_modes_from_modeline, ptypename: ptypename)
@mode = nil @modeline = nil
true true
end end
end end
def parse_start_statement! def parse_start_statement!
if md = consume!(/start\s+(\w+)\s*;/) if md = consume!(/start\s+([\w\s]*);/)
@start_rule = md[1] start_rules = md[1].split(/\s+/).map(&:strip)
start_rules.each do |start_rule|
@start_rules << start_rule unless @start_rules.include?(start_rule)
end
end end
end end
@@ -247,7 +260,7 @@ class Propane
else else
@code_blocks[name] = code @code_blocks[name] = code
end end
@mode = nil @modeline = nil
true true
end end
end end
@@ -315,6 +328,14 @@ class Propane
end end
end end
def get_modes_from_modeline
if @modeline
Set[*@modeline.split(",").map(&:strip)]
else
Set.new
end
end
end end
end end
+8 -2
View File
@@ -26,8 +26,14 @@ class Propane
private private
def build_tables! def build_tables!
@modes = @grammar.patterns.group_by do |pattern| modenames = @grammar.patterns.reduce(Set.new) do |result, pattern|
pattern.mode result + pattern.modes
end
@modes = modenames.reduce({}) do |result, modename|
result[modename] = @grammar.patterns.select do |pattern|
pattern.modes.include?(modename)
end
result
end.transform_values do |patterns| end.transform_values do |patterns|
{dfa: DFA.new(patterns)} {dfa: DFA.new(patterns)}
end end
+195 -59
View File
@@ -7,26 +7,38 @@ class Propane
attr_reader :reduce_table attr_reader :reduce_table
attr_reader :rule_sets attr_reader :rule_sets
def initialize(grammar, rule_sets, log) def initialize(grammar, rule_sets, log, options)
@grammar = grammar @grammar = grammar
@rule_sets = rule_sets @rule_sets = rule_sets
@log = log @log = log
@item_sets = [] @item_sets = []
@item_sets_set = {} @item_sets_set = {}
start_item = Item.new(grammar.rules.first, 0) @warnings = Set.new
eval_item_sets = Set[ItemSet.new([start_item])] @errors = Set.new
@options = options
start_items = grammar.rules[0...grammar.start_rules.length].map do |start_rule|
Item.new(start_rule, 0)
end
start_item_sets = start_items.map {|item| ItemSet.new([item])}
eval_item_sets = Set[*start_item_sets]
while eval_item_sets.size > 0 while eval_item_sets.size > 0
item_set = eval_item_sets.first item_set =
if start_item_sets.size > 0
# Ensure we evaluate start_item_sets first in order
start_item_sets.slice!(0)
else
eval_item_sets.first
end
eval_item_sets.delete(item_set) eval_item_sets.delete(item_set)
unless @item_sets_set.include?(item_set) unless @item_sets_set.include?(item_set)
item_set.id = @item_sets.size item_set.id = @item_sets.size
@item_sets << item_set @item_sets << item_set
@item_sets_set[item_set] = item_set @item_sets_set[item_set] = item_set
item_set.following_symbols.each do |following_symbol| item_set.next_symbols.each do |next_symbol|
unless following_symbol.name == "$EOF" unless next_symbol.name == "$EOF"
following_set = item_set.build_following_item_set(following_symbol) next_item_set = item_set.build_next_item_set(next_symbol)
eval_item_sets << following_set eval_item_sets << next_item_set
end end
end end
end end
@@ -37,8 +49,21 @@ class Propane
end end
build_reduce_actions! build_reduce_actions!
write_log!
build_tables! build_tables!
write_log!
errormessage = ""
if @errors.size > 0
errormessage += @errors.join("\n")
end
if @warnings.size > 0 && @options[:warnings_as_errors]
if errormessage != ""
errormessage += "\n"
end
errormessage += "Fatal errors (-w):\n" + @warnings.join("\n")
end
if errormessage != ""
raise Error.new(errormessage)
end
end end
private private
@@ -48,27 +73,24 @@ class Propane
@shift_table = [] @shift_table = []
@reduce_table = [] @reduce_table = []
@item_sets.each do |item_set| @item_sets.each do |item_set|
shift_entries = item_set.following_symbols.map do |following_symbol| unless item_set.reduce_rules.empty?
state_id = item_set.shift_entries.each do |shift_entry|
if following_symbol.name == "$EOF" token = shift_entry[:symbol]
0 if item_set.reduce_actions
else if rule = item_set.reduce_actions[token]
item_set.following_item_set[following_symbol].id @warnings << "Shift/Reduce conflict (state #{item_set.id}) between token #{token.name} and rule #{rule.name} (defined on line #{rule.line_number})"
end
end end
{ end
symbol_id: following_symbol.id,
state_id: state_id,
}
end end
reduce_entries = reduce_entries =
case ra = item_set.reduce_actions if rule = item_set.reduce_rule
when Rule [{token_id: @grammar.invalid_token_id, rule_id: rule.id, rule: rule,
[{token_id: @grammar.invalid_token_id, rule_id: ra.id, rule: ra, rule_set_id: rule.rule_set.id, n_states: rule.components.size,
rule_set_id: ra.rule_set.id, n_states: ra.components.size, propagate_optional_target: rule.optional? && rule.components.size == 1}]
propagate_optional_target: ra.optional? && ra.components.size == 1}] elsif reduce_actions = item_set.reduce_actions
when Hash reduce_actions.map do |token, rule|
ra.map do |token, rule| {token: token, token_id: token.id, rule_id: rule.id, rule: rule,
{token_id: token.id, rule_id: rule.id, rule: rule,
rule_set_id: rule.rule_set.id, n_states: rule.components.size, rule_set_id: rule.rule_set.id, n_states: rule.components.size,
propagate_optional_target: rule.optional? && rule.components.size == 1} propagate_optional_target: rule.optional? && rule.components.size == 1}
end end
@@ -77,21 +99,21 @@ class Propane
end end
@state_table << { @state_table << {
shift_index: @shift_table.size, shift_index: @shift_table.size,
n_shifts: shift_entries.size, n_shifts: item_set.shift_entries.size,
reduce_index: @reduce_table.size, reduce_index: @reduce_table.size,
n_reduces: reduce_entries.size, n_reduces: reduce_entries.size,
} }
@shift_table += shift_entries @shift_table += item_set.shift_entries
@reduce_table += reduce_entries @reduce_table += reduce_entries
end end
end end
def process_item_set(item_set) def process_item_set(item_set)
item_set.following_symbols.each do |following_symbol| item_set.next_symbols.each do |next_symbol|
unless following_symbol.name == "$EOF" unless next_symbol.name == "$EOF"
following_set = @item_sets_set[item_set.build_following_item_set(following_symbol)] next_item_set = @item_sets_set[item_set.build_next_item_set(next_symbol)]
item_set.following_item_set[following_symbol] = following_set item_set.next_item_set[next_symbol] = next_item_set
following_set.in_sets << item_set next_item_set.in_sets << item_set
end end
end end
end end
@@ -101,7 +123,109 @@ class Propane
# @return [void] # @return [void]
def build_reduce_actions! def build_reduce_actions!
@item_sets.each do |item_set| @item_sets.each do |item_set|
item_set.reduce_actions = build_reduce_actions_for_item_set(item_set) build_shift_entries(item_set)
build_reduce_actions_for_item_set(item_set)
end
item_sets_to_process = @item_sets.select do |item_set|
# We need lookahead reduce actions if:
# 1) There is more than one possible rule to reduce. In this case the
# lookahead token can help choose which rule to reduce.
# 2) There is at least one shift action and one reduce action for
# this item set. In this case the lookahead reduce actions are
# needed to test for a Shift/Reduce conflict.
item_set.reduce_rules.size > 1 ||
(item_set.reduce_rules.size > 0 && item_set.shift_entries.size > 0)
end
if RbConfig::CONFIG["host_os"] =~ /linux/
item_sets_by_id = {}
item_sets_to_process.each do |item_set|
item_sets_by_id[item_set.object_id] = item_set
end
tokens_by_id = {}
@grammar.tokens.each do |token|
tokens_by_id[token.object_id] = token
end
rules_by_id = {}
@grammar.rules.each do |rule|
rules_by_id[rule.object_id] = rule
end
n_threads = Util.determine_n_threads
semaphore = Mutex.new
queue = Queue.new
threads = {}
n_threads.times do
piper, pipew = IO.pipe
thread = Thread.new do
loop do
item_set = nil
semaphore.synchronize do
item_set = item_sets_to_process.slice!(0)
end
break if item_set.nil?
fork do
piper.close
build_lookahead_reduce_actions_for_item_set(item_set, pipew)
end
end
queue.push(Thread.current)
end
threads[thread] = [piper, pipew]
end
until threads.empty?
thread = queue.pop
piper, pipew = threads[thread]
pipew.close
thread_txt = piper.read
thread_txt.each_line do |line|
if line.start_with?("RA,")
parts = line.split(",")
item_set_id, token_id, rule_id = parts[1..3].map(&:to_i)
item_set = item_sets_by_id[item_set_id]
unless item_set
raise "Internal error: could not find item set from thread"
end
token = tokens_by_id[token_id]
unless item_set
raise "Internal error: could not find token from thread"
end
rule = rules_by_id[rule_id]
unless item_set
raise "Internal error: could not find rule from thread"
end
item_set.reduce_actions ||= {}
item_set.reduce_actions[token] = rule
elsif line.start_with?("Error: ")
@errors << line.chomp
else
raise "Internal error: unhandled thread line #{line}"
end
end
thread.join
threads.delete(thread)
end
else
# Fall back to single threaded algorithm.
item_sets_to_process.each do |item_set|
item_set.reduce_actions = build_lookahead_reduce_actions_for_item_set(item_set)
end
end
end
# Build the shift entries for a single item set.
#
# @return [void]
def build_shift_entries(item_set)
item_set.shift_entries = item_set.next_symbols.map do |next_symbol|
state_id =
if next_symbol.name == "$EOF"
0
else
item_set.next_item_set[next_symbol].id
end
{
symbol: next_symbol,
state_id: state_id,
}
end end
end end
@@ -110,40 +234,45 @@ class Propane
# @param item_set [ItemSet] # @param item_set [ItemSet]
# ItemSet (parser state) # ItemSet (parser state)
# #
# @return [nil, Rule, Hash] # @return [void]
# If no reduce actions are possible for the given item set, nil.
# If only one reduce action is possible for the given item set, the Rule
# to reduce.
# Otherwise, a mapping of lookahead Tokens to the Rules to reduce.
def build_reduce_actions_for_item_set(item_set) def build_reduce_actions_for_item_set(item_set)
# To build the reduce actions, we start by looking at any # To build the reduce actions, we start by looking at any
# "complete" items, i.e., items where the parse position is at the # "complete" items, i.e., items where the parse position is at the
# end of a rule. These are the only rules that are candidates for # end of a rule. These are the only rules that are candidates for
# reduction in the current ItemSet. # reduction in the current ItemSet.
reduce_rules = Set.new(item_set.items.select(&:complete?).map(&:rule)) item_set.reduce_rules = Set.new(item_set.items.select(&:complete?).map(&:rule))
# If there are no rules to reduce for this ItemSet, we're done here. if item_set.reduce_rules.size == 1
return nil if reduce_rules.size == 0 item_set.reduce_rule = item_set.reduce_rules.first
end
# If there is exactly one rule to reduce for this ItemSet, then do not end
# figure out the lookaheads; just reduce it.
return reduce_rules.first if reduce_rules.size == 1
# Otherwise, we have more than one possible rule to reduce.
# Build the reduce actions for a single item set (parser state).
#
# @param item_set [ItemSet]
# ItemSet (parser state)
# @param fh [File]
# Output file handle for multiprocessing mode.
#
# @return [Hash]
# Mapping of lookahead Tokens to the Rules to reduce.
def build_lookahead_reduce_actions_for_item_set(item_set, fh = nil)
# We will be looking for all possible tokens that can follow instances of # We will be looking for all possible tokens that can follow instances of
# these rules. Rather than looking through the entire grammar for the # these rules. Rather than looking through the entire grammar for the
# possible following tokens, we will only look in the item sets leading # possible following tokens, we will only look in the item sets leading
# up to this one. This restriction gives us a more precise lookahead set, # up to this one. This restriction gives us a more precise lookahead set,
# and allows us to parse LALR grammars. # and allows us to parse LALR grammars.
item_sets = Set[item_set] + item_set.leading_item_sets item_sets = Set[item_set] + item_set.leading_item_sets
reduce_rules.reduce({}) do |reduce_actions, reduce_rule| item_set.reduce_rules.reduce({}) do |reduce_actions, reduce_rule|
lookahead_tokens_for_rule = build_lookahead_tokens_to_reduce(reduce_rule, item_sets) lookahead_tokens_for_rule = build_lookahead_tokens_to_reduce(reduce_rule, item_sets)
lookahead_tokens_for_rule.each do |lookahead_token| lookahead_tokens_for_rule.each do |lookahead_token|
if existing_reduce_rule = reduce_actions[lookahead_token] if existing_reduce_rule = reduce_actions[lookahead_token]
raise Error.new("Error: reduce/reduce conflict between rule #{existing_reduce_rule.id} (#{existing_reduce_rule.name}) and rule #{reduce_rule.id} (#{reduce_rule.name})") error = "Error: reduce/reduce conflict (state #{item_set.id}) between rule #{existing_reduce_rule.name}##{existing_reduce_rule.id} (defined on line #{existing_reduce_rule.line_number}) and rule #{reduce_rule.name}##{reduce_rule.id} (defined on line #{reduce_rule.line_number}) for lookahead token #{lookahead_token}"
@errors << error
fh.puts(error) if fh
end end
reduce_actions[lookahead_token] = reduce_rule reduce_actions[lookahead_token] = reduce_rule
fh.puts "RA,#{item_set.object_id},#{lookahead_token.object_id},#{reduce_rule.object_id}" if fh
end end
reduce_actions reduce_actions
end end
@@ -183,13 +312,14 @@ class Propane
# tokens to form the lookahead token set. # tokens to form the lookahead token set.
item_sets.each do |item_set| item_sets.each do |item_set|
item_set.items.each do |item| item_set.items.each do |item|
if item.following_symbol == rule_set if item.next_symbol == rule_set
(1..).each do |offset| (1..).each do |offset|
case symbol = item.following_symbol(offset) case symbol = item.next_symbol(offset)
when nil when nil
rule_set = item.rule.rule_set rule_set = item.rule.rule_set
unless checked_rule_sets.include?(rule_set) unless checked_rule_sets.include?(rule_set)
rule_sets_to_check_after << rule_set rule_sets_to_check_after << rule_set
checked_rule_sets << rule_set
end end
break break
when Token when Token
@@ -242,20 +372,26 @@ class Propane
@log.puts @log.puts
@log.puts " Incoming states: #{incoming_ids.join(", ")}" @log.puts " Incoming states: #{incoming_ids.join(", ")}"
@log.puts " Outgoing states:" @log.puts " Outgoing states:"
item_set.following_item_set.each do |following_symbol, following_item_set| item_set.next_item_set.each do |next_symbol, next_item_set|
@log.puts " #{following_symbol.name} => #{following_item_set.id}" @log.puts " #{next_symbol.name} => #{next_item_set.id}"
end end
@log.puts @log.puts
@log.puts " Reduce actions:" @log.puts " Reduce actions:"
case item_set.reduce_actions if item_set.reduce_rule
when Rule @log.puts " * => rule #{item_set.reduce_rule.id}, rule set #{@rule_sets[item_set.reduce_rule.name].id} (#{item_set.reduce_rule.name})"
@log.puts " * => rule #{item_set.reduce_actions.id}, rule set #{@rule_sets[item_set.reduce_actions.name].id} (#{item_set.reduce_actions.name})" elsif item_set.reduce_actions
when Hash
item_set.reduce_actions.each do |token, rule| item_set.reduce_actions.each do |token, rule|
@log.puts " lookahead #{token.name} => #{rule.name} (#{rule.id}), rule set ##{rule.rule_set.id}" @log.puts " lookahead #{token.name} => #{rule.name} (#{rule.id}), rule set ##{rule.rule_set.id}"
end end
end end
end end
if @warnings.size > 0
@log.puts
@log.puts "Warnings:"
@warnings.each do |warning|
@log.puts " #{warning}"
end
end
end end
end end
+14 -13
View File
@@ -22,6 +22,7 @@ class Propane
def initialize(rule, position) def initialize(rule, position)
@rule = rule @rule = rule
@position = position @position = position
@_hash = [@rule, @position].hash
end end
# Hash function. # Hash function.
@@ -29,7 +30,7 @@ class Propane
# @return [Integer] # @return [Integer]
# Hash code. # Hash code.
def hash def hash
[@rule, @position].hash @_hash
end end
# Compare Item objects. # Compare Item objects.
@@ -56,7 +57,7 @@ class Propane
# Return the set of Items obtained by "closing" the current item. # Return the set of Items obtained by "closing" the current item.
# #
# If the following symbol for the current item is another Rule name, then # If the next symbol for the current item is another Rule name, then
# this method will return all Items for that Rule with a position of 0. # this method will return all Items for that Rule with a position of 0.
# Otherwise, an empty Array is returned. # Otherwise, an empty Array is returned.
# #
@@ -81,17 +82,17 @@ class Propane
@position == @rule.components.size @position == @rule.components.size
end end
# Get the following symbol for the Item. # Get the next symbol for the Item.
# #
# That is, the symbol which follows the parse position marker in the # That is, the symbol which is after the parse position marker in the
# current Item. # current Item.
# #
# @param offset [Integer] # @param offset [Integer]
# Offset from current parse position to examine. # Offset from current parse position to examine.
# #
# @return [Token, RuleSet, nil] # @return [Token, RuleSet, nil]
# Following symbol for the Item. # Next symbol for the Item.
def following_symbol(offset = 0) def next_symbol(offset = 0)
@rule.components[@position + offset] @rule.components[@position + offset]
end end
@@ -108,25 +109,25 @@ class Propane
end end
end end
# Get whether this Item is followed by the provided symbol. # Get whether this Item's next symbol is the given symbol.
# #
# @param symbol [Token, RuleSet] # @param symbol [Token, RuleSet]
# Symbol to query. # Symbol to query.
# #
# @return [Boolean] # @return [Boolean]
# Whether this Item is followed by the provided symbol. # Whether this Item's next symbol is the given symbol.
def followed_by?(symbol) def next_symbol?(symbol)
following_symbol == symbol next_symbol == symbol
end end
# Get the following item for this Item. # Get the next item for this Item.
# #
# That is, the Item formed by moving the parse position marker one place # That is, the Item formed by moving the parse position marker one place
# forward from its position in this Item. # forward from its position in this Item.
# #
# @return [Item] # @return [Item]
# The following item for this Item. # The next item for this Item.
def following_item def next_item
Item.new(@rule, @position + 1) Item.new(@rule, @position + 1)
end end
+49 -33
View File
@@ -2,7 +2,7 @@ class Propane
class Parser class Parser
# Represent a parser "item set", which is a set of possible items that the # Represent a parser "item set", which is a set of possible items that the
# parser could currently be parsing. # parser could currently be parsing. This is equivalent to a parser state.
class ItemSet class ItemSet
# @return [Set<Item>] # @return [Set<Item>]
@@ -14,45 +14,58 @@ class Propane
attr_accessor :id attr_accessor :id
# @return [Hash] # @return [Hash]
# Maps a following symbol to its ItemSet. # Maps a next symbol to its ItemSet.
attr_reader :following_item_set attr_reader :next_item_set
# @return [Set<ItemSet>] # @return [Set<ItemSet>]
# ItemSets leading to this item set. # ItemSets leading to this item set.
attr_reader :in_sets attr_reader :in_sets
# @return [nil, Rule, Hash] # @return [nil, Rule]
# Reduce actions, mapping lookahead tokens to rules. # Rule to reduce if there is only one possibility.
attr_accessor :reduce_rule
# @return [Set<Rule>]
# Set of rules that could be reduced in this parser state.
attr_accessor :reduce_rules
# @return [nil, Hash]
# Reduce actions, mapping lookahead tokens to rules, if there is
# more than one rule that could be reduced.
attr_accessor :reduce_actions attr_accessor :reduce_actions
# @return [Array<Hash>]
# Shift table entries.
attr_accessor :shift_entries
# Build an ItemSet. # Build an ItemSet.
# #
# @param items [Array<Item>] # @param items [Array<Item>]
# Items in this ItemSet. # Items in this ItemSet.
def initialize(items) def initialize(items)
@items = Set.new(items) @items = Set.new(items)
@following_item_set = {} @next_item_set = {}
@in_sets = Set.new @in_sets = Set.new
close! close!
end end
# Get the set of following symbols for all Items in this ItemSet. # Get the set of next symbols for all Items in this ItemSet.
# #
# @return [Set<Token, RuleSet>] # @return [Set<Token, RuleSet>]
# Set of following symbols for all Items in this ItemSet. # Set of next symbols for all Items in this ItemSet.
def following_symbols def next_symbols
Set.new(@items.map(&:following_symbol).compact) @_next_symbols ||= Set.new(@items.map(&:next_symbol).compact)
end end
# Build a following ItemSet for the given following symbol. # Build a next ItemSet for the given next symbol.
# #
# @param symbol [Token, RuleSet] # @param symbol [Token, RuleSet]
# Following symbol to build the following ItemSet for. # Next symbol to build the next ItemSet for.
# #
# @return [ItemSet] # @return [ItemSet]
# Following ItemSet for the given following symbol. # Next ItemSet for the given next symbol.
def build_following_item_set(symbol) def build_next_item_set(symbol)
ItemSet.new(items_followed_by(symbol).map(&:following_item)) ItemSet.new(items_with_next(symbol).map(&:next_item))
end end
# Hash function. # Hash function.
@@ -90,21 +103,24 @@ class Propane
# @return [Set<ItemSet>] # @return [Set<ItemSet>]
# Set of all ItemSets that lead up to this ItemSet. # Set of all ItemSets that lead up to this ItemSet.
def leading_item_sets def leading_item_sets
result = Set.new @_leading_item_sets ||=
eval_sets = Set[self] begin
evaled = Set.new result = Set.new
while eval_sets.size > 0 eval_sets = Set[self]
eval_set = eval_sets.first evaled = Set.new
eval_sets.delete(eval_set) while eval_sets.size > 0
evaled << eval_set eval_set = eval_sets.first
eval_set.in_sets.each do |in_set| eval_sets.delete(eval_set)
result << in_set evaled << eval_set
unless evaled.include?(in_set) eval_set.in_sets.each do |in_set|
eval_sets << in_set result << in_set
unless evaled.include?(in_set)
eval_sets << in_set
end
end
end end
result
end end
end
result
end end
# Represent the ItemSet as a String. # Represent the ItemSet as a String.
@@ -137,16 +153,16 @@ class Propane
end end
end end
# Get the Items followed by the given following symbol. # Get the Items with the given next symbol.
# #
# @param symbol [Token, RuleSet] # @param symbol [Token, RuleSet]
# Following symbol. # Next symbol.
# #
# @return [Array<Item>] # @return [Array<Item>]
# Items followed by the given following symbol. # Items with the given next symbol.
def items_followed_by(symbol) def items_with_next(symbol)
@items.select do |item| @items.select do |item|
item.followed_by?(symbol) item.next_symbol?(symbol)
end end
end end
+7 -7
View File
@@ -26,9 +26,9 @@ class Propane
# Regex NFA for matching the pattern. # Regex NFA for matching the pattern.
attr_reader :nfa attr_reader :nfa
# @return [String, nil] # @return [Set]
# Lexer mode for this pattern. # Lexer modes for this pattern.
attr_accessor :mode attr_accessor :modes
# @return [String, nil] # @return [String, nil]
# Parser value type name. # Parser value type name.
@@ -46,16 +46,16 @@ class Propane
# Token to be returned by this pattern. # Token to be returned by this pattern.
# @option options [Integer, nil] :line_number # @option options [Integer, nil] :line_number
# Line number where the token was defined in the input grammar. # Line number where the token was defined in the input grammar.
# @option options [String, nil] :mode # @option options [String, nil] :modes
# Lexer mode for this pattern. # Lexer modes for this pattern.
def initialize(options) def initialize(options)
@code = options[:code] @code = options[:code]
@pattern = options[:pattern] @pattern = options[:pattern]
@token = options[:token] @token = options[:token]
@line_number = options[:line_number] @line_number = options[:line_number]
@mode = options[:mode] @modes = options[:modes]
@ptypename = options[:ptypename] @ptypename = options[:ptypename]
regex = Regex.new(@pattern) regex = Regex.new(@pattern, @line_number)
regex.nfa.end_state.accepts = self regex.nfa.end_state.accepts = self
@nfa = regex.nfa @nfa = regex.nfa
end end
+48 -17
View File
@@ -4,12 +4,13 @@ class Propane
attr_reader :unit attr_reader :unit
attr_reader :nfa attr_reader :nfa
def initialize(pattern) def initialize(pattern, line_number)
@pattern = pattern.dup @pattern = pattern.dup
@line_number = line_number
@unit = parse_alternates @unit = parse_alternates
@nfa = @unit.to_nfa @nfa = @unit.to_nfa
if @pattern != "" if @pattern != ""
raise Error.new(%[Unexpected "#{@pattern}" in pattern]) raise Error.new(%[Line #{@line_number}: unexpected "#{@pattern}" in pattern])
end end
end end
@@ -41,7 +42,7 @@ class Propane
mu = MultiplicityUnit.new(last_unit, min_count, max_count) mu = MultiplicityUnit.new(last_unit, min_count, max_count)
au.replace_last!(mu) au.replace_last!(mu)
else else
raise Error.new("#{c} follows nothing") raise Error.new("Line #{@line_number}: #{c} follows nothing")
end end
when "|" when "|"
au.new_alternate! au.new_alternate!
@@ -59,7 +60,7 @@ class Propane
def parse_group def parse_group
au = parse_alternates au = parse_alternates
if @pattern[0] != ")" if @pattern[0] != ")"
raise Error.new("Unterminated group in pattern") raise Error.new("Line #{@line_number}: unterminated group in pattern")
end end
@pattern.slice!(0) @pattern.slice!(0)
au au
@@ -70,7 +71,7 @@ class Propane
index = 0 index = 0
loop do loop do
if @pattern == "" if @pattern == ""
raise Error.new("Unterminated character class") raise Error.new("Line #{@line_number}: unterminated character class")
end end
c = @pattern.slice!(0) c = @pattern.slice!(0)
if c == "]" if c == "]"
@@ -84,13 +85,13 @@ class Propane
elsif c == "-" && @pattern[0] != "]" elsif c == "-" && @pattern[0] != "]"
begin_cu = ccu.last_unit begin_cu = ccu.last_unit
unless begin_cu.is_a?(CharacterRangeUnit) && begin_cu.code_point_range.size == 1 unless begin_cu.is_a?(CharacterRangeUnit) && begin_cu.code_point_range.size == 1
raise Error.new("Character range must be between single characters") raise Error.new("Line #{@line_number}: character range must be between single characters")
end end
if @pattern[0] == "\\" if @pattern[0] == "\\"
@pattern.slice!(0) @pattern.slice!(0)
end_cu = parse_backslash end_cu = parse_backslash
unless end_cu.is_a?(CharacterRangeUnit) && end_cu.code_point_range.size == 1 unless end_cu.is_a?(CharacterRangeUnit) && end_cu.code_point_range.size == 1
raise Error.new("Character range must be between single characters") raise Error.new("Line #{@line_number}: character range must be between single characters")
end end
max_code_point = end_cu.code_point max_code_point = end_cu.code_point
else else
@@ -116,7 +117,7 @@ class Propane
elsif max_count.to_s != "" elsif max_count.to_s != ""
max_count = max_count.to_i max_count = max_count.to_i
if max_count < min_count if max_count < min_count
raise Error.new("Maximum repetition count cannot be less than minimum repetition count") raise Error.new("Line #{@line_number}: maximum repetition count cannot be less than minimum repetition count")
end end
else else
max_count = nil max_count = nil
@@ -124,28 +125,33 @@ class Propane
@pattern = pattern @pattern = pattern
[min_count, max_count] [min_count, max_count]
else else
raise Error.new("Unexpected match count at #{@pattern}") raise Error.new("Line #{@line_number}: unexpected match count following {")
end end
end end
def parse_backslash def parse_backslash
if @pattern == "" if @pattern == ""
raise Error.new("Error: unfollowed \\") raise Error.new("Line #{@line_number}: error: unfollowed \\")
else else
c = @pattern.slice!(0) c = @pattern.slice!(0)
case c case c
when "a" when "a"
CharacterRangeUnit.new("\a", "\a") CharacterRangeUnit.new("\a")
when "b" when "b"
CharacterRangeUnit.new("\b", "\b") CharacterRangeUnit.new("\b")
when "d" when "d"
CharacterRangeUnit.new("0", "9") CharacterRangeUnit.new("0", "9")
when "D"
ccu = CharacterClassUnit.new
ccu << CharacterRangeUnit.new("0", "9")
ccu.negate = true
ccu
when "f" when "f"
CharacterRangeUnit.new("\f", "\f") CharacterRangeUnit.new("\f")
when "n" when "n"
CharacterRangeUnit.new("\n", "\n") CharacterRangeUnit.new("\n")
when "r" when "r"
CharacterRangeUnit.new("\r", "\r") CharacterRangeUnit.new("\r")
when "s" when "s"
ccu = CharacterClassUnit.new ccu = CharacterClassUnit.new
ccu << CharacterRangeUnit.new(" ") ccu << CharacterRangeUnit.new(" ")
@@ -155,10 +161,35 @@ class Propane
ccu << CharacterRangeUnit.new("\f") ccu << CharacterRangeUnit.new("\f")
ccu << CharacterRangeUnit.new("\v") ccu << CharacterRangeUnit.new("\v")
ccu ccu
when "S"
ccu = CharacterClassUnit.new
ccu << CharacterRangeUnit.new(" ")
ccu << CharacterRangeUnit.new("\t")
ccu << CharacterRangeUnit.new("\r")
ccu << CharacterRangeUnit.new("\n")
ccu << CharacterRangeUnit.new("\f")
ccu << CharacterRangeUnit.new("\v")
ccu.negate = true
ccu
when "t" when "t"
CharacterRangeUnit.new("\t", "\t") CharacterRangeUnit.new("\t")
when "v" when "v"
CharacterRangeUnit.new("\v", "\v") CharacterRangeUnit.new("\v")
when "w"
ccu = CharacterClassUnit.new
ccu << CharacterRangeUnit.new("_")
ccu << CharacterRangeUnit.new("0", "9")
ccu << CharacterRangeUnit.new("a", "z")
ccu << CharacterRangeUnit.new("A", "Z")
ccu
when "W"
ccu = CharacterClassUnit.new
ccu << CharacterRangeUnit.new("_")
ccu << CharacterRangeUnit.new("0", "9")
ccu << CharacterRangeUnit.new("a", "z")
ccu << CharacterRangeUnit.new("A", "Z")
ccu.negate = true
ccu
else else
CharacterRangeUnit.new(c) CharacterRangeUnit.new(c)
end end
+10 -7
View File
@@ -92,16 +92,19 @@ class Propane
@units = [] @units = []
@negate = false @negate = false
end end
def initialize def method_missing(*args, &block)
@units = [] @units.__send__(*args, &block)
end
def method_missing(*args)
@units.__send__(*args)
end end
def <<(thing) def <<(thing)
if thing.is_a?(CharacterClassUnit) if thing.is_a?(CharacterClassUnit)
thing.each do |ccu_unit| if thing.negate
@units << ccu_unit CodePointRange.invert_ranges(thing.map(&:code_point_range)).each do |cpr|
CharacterRangeUnit.new(cpr.first, cpr.last)
end
else
thing.each do |ccu_unit|
@units << ccu_unit
end
end end
else else
@units << thing @units << thing
+18 -2
View File
@@ -6,6 +6,10 @@ class Propane
# Rule components. # Rule components.
attr_reader :components attr_reader :components
# @return [Hash]
# Field aliases.
attr_reader :aliases
# @return [String] # @return [String]
# User code associated with the rule. # User code associated with the rule.
attr_reader :code attr_reader :code
@@ -32,7 +36,7 @@ class Propane
# @return [Array<Integer>] # @return [Array<Integer>]
# Map this rule's components to their positions in the parent RuleSet's # Map this rule's components to their positions in the parent RuleSet's
# node field pointer array. This is used for AST construction. # node field pointer array. This is used for tree construction.
attr_accessor :rule_set_node_field_index_map attr_accessor :rule_set_node_field_index_map
# Construct a Rule. # Construct a Rule.
@@ -49,7 +53,19 @@ class Propane
# Line number where the rule was defined in the input grammar. # Line number where the rule was defined in the input grammar.
def initialize(name, components, code, ptypename, line_number) def initialize(name, components, code, ptypename, line_number)
@name = name @name = name
@components = components @aliases = {}
@components = components.each_with_index.map do |component, i|
if component =~ /(\S+):(\S+)/
c, aliasname = $1, $2
if @aliases[aliasname]
raise Error.new("Error: duplicate field alias `#{aliasname}` for rule #{name} defined on line #{line_number}")
end
@aliases[aliasname] = i
c
else
component
end
end
@rule_set_node_field_index_map = components.map {0} @rule_set_node_field_index_map = components.map {0}
@code = code @code = code
@ptypename = ptypename @ptypename = ptypename
+30 -16
View File
@@ -4,8 +4,8 @@ class Propane
class RuleSet class RuleSet
# @return [Array<Hash>] # @return [Array<Hash>]
# AST fields. # tree fields.
attr_reader :ast_fields attr_reader :tree_fields
# @return [Integer] # @return [Integer]
# ID of the RuleSet. # ID of the RuleSet.
@@ -100,24 +100,28 @@ class Propane
# Finalize a RuleSet after adding all Rules to it. # Finalize a RuleSet after adding all Rules to it.
def finalize(grammar) def finalize(grammar)
build_ast_fields(grammar) if grammar.tree
build_tree_fields(grammar)
end
end end
private private
# Build the set of AST fields for this RuleSet. # Build the set of tree fields for this RuleSet.
# #
# This is an Array of Hashes. Each entry in the Array corresponds to a # This is an Array of Hashes. Each entry in the Array corresponds to a
# field location in the AST node. The entry is a Hash. It could have one or # field location in the tree node. The entry is a Hash. It could have one or
# two keys. It will always have the field name with a positional suffix as # two keys. It will always have the field name with a positional suffix as
# a key. It may also have the field name without the positional suffix if # a key. It may also have the field name without the positional suffix if
# that field only exists in one position across all Rules in the RuleSet. # that field only exists in one position across all Rules in the RuleSet.
# #
# @return [void] # @return [void]
def build_ast_fields(grammar) def build_tree_fields(grammar)
field_ast_node_indexes = {} field_tree_node_indexes = {}
field_indexes_across_all_rules = {} field_indexes_across_all_rules = {}
@ast_fields = [] # Stores the index into @tree_fields by field alias name.
field_aliases = {}
@tree_fields = []
@rules.each do |rule| @rules.each do |rule|
rule.components.each_with_index do |component, i| rule.components.each_with_index do |component, i|
if component.is_a?(RuleSet) && component.optional? if component.is_a?(RuleSet) && component.optional?
@@ -128,15 +132,25 @@ class Propane
else else
node_name = component.name node_name = component.name
end end
struct_name = "#{grammar.ast_prefix}#{node_name}#{grammar.ast_suffix}" struct_name = "#{grammar.tree_prefix}#{node_name}#{grammar.tree_suffix}"
field_name = "p#{node_name}#{i + 1}" field_name = "p#{node_name}#{i + 1}"
unless field_ast_node_indexes[field_name] unless field_tree_node_indexes[field_name]
field_ast_node_indexes[field_name] = @ast_fields.size field_tree_node_indexes[field_name] = @tree_fields.size
@ast_fields << {field_name => struct_name} @tree_fields << {field_name => struct_name}
end
rule.aliases.each do |alias_name, index|
if index == i
alias_tree_fields_index = field_tree_node_indexes[field_name]
if field_aliases[alias_name] && field_aliases[alias_name] != alias_tree_fields_index
raise Error.new("Error: conflicting tree node field positions for alias `#{alias_name}` in rule #{rule.name} defined on line #{rule.line_number}")
end
field_aliases[alias_name] = alias_tree_fields_index
@tree_fields[alias_tree_fields_index][alias_name] = @tree_fields[alias_tree_fields_index].first[1]
end
end end
field_indexes_across_all_rules[node_name] ||= Set.new field_indexes_across_all_rules[node_name] ||= Set.new
field_indexes_across_all_rules[node_name] << field_ast_node_indexes[field_name] field_indexes_across_all_rules[node_name] << field_tree_node_indexes[field_name]
rule.rule_set_node_field_index_map[i] = field_ast_node_indexes[field_name] rule.rule_set_node_field_index_map[i] = field_tree_node_indexes[field_name]
end end
end end
field_indexes_across_all_rules.each do |node_name, indexes_across_all_rules| field_indexes_across_all_rules.each do |node_name, indexes_across_all_rules|
@@ -144,8 +158,8 @@ class Propane
# If this field was only seen in one position across all rules, # If this field was only seen in one position across all rules,
# then add an alias to the positional field name that does not # then add an alias to the positional field name that does not
# include the position. # include the position.
@ast_fields[indexes_across_all_rules.first]["p#{node_name}"] = @tree_fields[indexes_across_all_rules.first]["p#{node_name}"] =
"#{grammar.ast_prefix}#{node_name}#{grammar.ast_suffix}" "#{grammar.tree_prefix}#{node_name}#{grammar.tree_suffix}"
end end
end end
end end
+26
View File
@@ -10,6 +10,32 @@ class Propane
"#{s}\n* #{message} *\n#{s}\n" "#{s}\n* #{message} *\n#{s}\n"
end end
# Determine the number of threads to use.
#
# @return [Integer]
# The number of threads to use.
def determine_n_threads
# Try to figure out how many threads are available on the host hardware.
begin
case RbConfig::CONFIG["host_os"]
when /linux/
return File.read("/proc/cpuinfo").scan(/^processor\s*:/).size
when /mswin|mingw|msys/
if `wmic cpu get NumberOfLogicalProcessors -value` =~ /NumberOfLogicalProcessors=(\d+)/
return $1.to_i
end
when /darwin/
if `sysctl -n hw.ncpu` =~ /(\d+)/
return $1.to_i
end
end
rescue
end
# If we can't figure it out, default to 4.
4
end
end end
end end
+1 -1
View File
@@ -1,3 +1,3 @@
class Propane class Propane
VERSION = "1.4.0" VERSION = "3.0.0"
end end
+2 -2
View File
@@ -120,11 +120,11 @@ string: /\\t/ <<
>> >>
string: /\\u[0-9a-fA-F]{4}/ << string: /\\u[0-9a-fA-F]{4}/ <<
/* Not actually going to encode the code point for this example... */ /* Not actually going to encode the code point for this example... */
char s[] = {'{', match[2], match[3], match[4], match[5], '}', 0}; char s[] = {'{', (char)match[2], (char)match[3], (char)match[4], (char)match[5], '}', 0};
str_append(&string_value, s); str_append(&string_value, s);
>> >>
string: /[^\\]/ << string: /[^\\]/ <<
char s[] = {match[0], 0}; char s[] = {(char)match[0], 0};
str_append(&string_value, s); str_append(&string_value, s);
>> >>
Start -> Value << Start -> Value <<
+3 -3
View File
@@ -5,7 +5,7 @@
JSONValue * JSONValue_new(size_t id) JSONValue * JSONValue_new(size_t id)
{ {
JSONValue * jv = calloc(1, sizeof(JSONValue)); JSONValue * jv = (JSONValue *)calloc(1, sizeof(JSONValue));
jv->id = id; jv->id = id;
return jv; return jv;
} }
@@ -29,7 +29,7 @@ void JSONObject_append(JSONValue * object, char const * name, JSONValue * value)
} }
} }
size_t const new_size = size + 1; size_t const new_size = size + 1;
void * new_entries = malloc(sizeof(object->object.entries[0]) * new_size); JSONObjectEntry * new_entries = (JSONObjectEntry *)malloc(sizeof(object->object.entries[0]) * new_size);
if (size > 0) if (size > 0)
{ {
memcpy(new_entries, object->object.entries, size * sizeof(object->object.entries[0])); memcpy(new_entries, object->object.entries, size * sizeof(object->object.entries[0]));
@@ -52,7 +52,7 @@ void JSONArray_append(JSONValue * array, JSONValue * value)
{ {
size_t const size = array->array.size; size_t const size = array->array.size;
size_t const new_size = size + 1; size_t const new_size = size + 1;
JSONValue ** new_entries = malloc(sizeof(JSONValue *) * new_size); JSONValue ** new_entries = (JSONValue **)malloc(sizeof(JSONValue *) * new_size);
if (array->array.size > 0) if (array->array.size > 0)
{ {
memcpy(new_entries, array->array.entries, sizeof(JSONValue *) * size); memcpy(new_entries, array->array.entries, sizeof(JSONValue *) * size);
+7 -5
View File
@@ -11,6 +11,12 @@
#define JSON_FALSE 5u #define JSON_FALSE 5u
#define JSON_NULL 6u #define JSON_NULL 6u
typedef struct JSONObjectEntry_s
{
char const * name;
struct JSONValue_s * value;
} JSONObjectEntry;
typedef struct JSONValue_s typedef struct JSONValue_s
{ {
size_t id; size_t id;
@@ -19,11 +25,7 @@ typedef struct JSONValue_s
struct struct
{ {
size_t size; size_t size;
struct JSONObjectEntry * entries;
{
char const * name;
struct JSONValue_s * value;
} * entries;
} object; } object;
struct struct
{ {
+6 -6
View File
@@ -151,30 +151,30 @@ EOF
o = grammar.patterns.find {|pattern| pattern.token == o} o = grammar.patterns.find {|pattern| pattern.token == o}
expect(o).to_not be_nil expect(o).to_not be_nil
expect(o.mode).to be_nil expect(o.modes).to be_empty
o = grammar.tokens.find {|token| token.name == "b"} o = grammar.tokens.find {|token| token.name == "b"}
expect(o).to_not be_nil expect(o).to_not be_nil
o = grammar.patterns.find {|pattern| pattern.token == o} o = grammar.patterns.find {|pattern| pattern.token == o}
expect(o).to_not be_nil expect(o).to_not be_nil
expect(o.mode).to eq "m1" expect(o.modes).to eq Set["m1"]
o = grammar.patterns.find {|pattern| pattern.pattern == "foo"} o = grammar.patterns.find {|pattern| pattern.pattern == "foo"}
expect(o).to_not be_nil expect(o).to_not be_nil
expect(o.mode).to be_nil expect(o.modes).to be_empty
o = grammar.patterns.find {|pattern| pattern.pattern == "bar"} o = grammar.patterns.find {|pattern| pattern.pattern == "bar"}
expect(o).to_not be_nil expect(o).to_not be_nil
expect(o.mode).to eq "m2" expect(o.modes).to eq Set["m2"]
o = grammar.patterns.find {|pattern| pattern.pattern == "q"} o = grammar.patterns.find {|pattern| pattern.pattern == "q"}
expect(o).to_not be_nil expect(o).to_not be_nil
expect(o.mode).to be_nil expect(o.modes).to be_empty
o = grammar.patterns.find {|pattern| pattern.pattern == "r"} o = grammar.patterns.find {|pattern| pattern.pattern == "r"}
expect(o).to_not be_nil expect(o).to_not be_nil
expect(o.mode).to eq "m3" expect(o.modes).to eq Set["m3"]
end end
it "allows assigning ptypes to tokens and rules" do it "allows assigning ptypes to tokens and rules" do
+68
View File
@@ -126,6 +126,74 @@ EOF
] ]
expect(run(<<EOF, ";")).to eq expected expect(run(<<EOF, ";")).to eq expected
token semicolon /;/; token semicolon /;/;
EOF
end
it "matches a negated character class" do
expected = [
["pattern", "/abc/"],
]
expect(run(<<EOF, "/abc/")).to eq expected
token pattern /\\/[^\\s]*\\//;
EOF
end
it "matches special character classes " do
expected = [
["a", "abc123_FOO"],
]
expect(run(<<EOF, "abc123_FOO")).to eq expected
token a /\\w+/;
EOF
expected = [
["b", "FROG*%$#"],
]
expect(run(<<EOF, "FROG*%$#")).to eq expected
token b /FROG\\D{1,4}/;
EOF
expected = [
["c", "$883366"],
]
expect(run(<<EOF, "$883366")).to eq expected
token c /$\\d+/;
EOF
expected = [
["d", "^&$@"],
]
expect(run(<<EOF, "^&$@")).to eq expected
token d /^\\W+/;
EOF
expected = [
["a", "abc123_FOO"],
[nil, " "],
["b", "FROG*%$#"],
[nil, " "],
["c", "$883366"],
[nil, " "],
["d", "^&$@"],
]
expect(run(<<EOF, "abc123_FOO FROG*%$# $883366 ^&$@")).to eq expected
token a /\\w+/;
token b /FROG\\D{1,4}/;
token c /$\\d+/;
token d /^\\W+/;
drop /\\s+/;
EOF
end
it "matches a negated character class with a nested inner negated character class" do
expected = [
["t", "$&*"],
]
expect(run(<<EOF, "$&*")).to eq expected
token t /[^%\\W]+/;
EOF
end
it "\\s matches a newline" do
expected = [["s", "\n"]]
expect(run(<<EOF, "\n")).to eq expected
token s /\\s/;
EOF EOF
end end
end end
+39 -22
View File
@@ -2,14 +2,14 @@ class Propane
RSpec.describe Regex do RSpec.describe Regex do
it "parses an empty expression" do it "parses an empty expression" do
regex = Regex.new("") regex = Regex.new("", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0].size).to eq 0 expect(regex.unit.alternates[0].size).to eq 0
end end
it "parses a single character unit expression" do it "parses a single character unit expression" do
regex = Regex.new("a") regex = Regex.new("a", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -19,7 +19,7 @@ class Propane
end end
it "parses a group with a single character unit expression" do it "parses a group with a single character unit expression" do
regex = Regex.new("(a)") regex = Regex.new("(a)", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -33,7 +33,7 @@ class Propane
end end
it "parses a *" do it "parses a *" do
regex = Regex.new("a*") regex = Regex.new("a*", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -47,7 +47,7 @@ class Propane
end end
it "parses a +" do it "parses a +" do
regex = Regex.new("a+") regex = Regex.new("a+", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -61,7 +61,7 @@ class Propane
end end
it "parses a ?" do it "parses a ?" do
regex = Regex.new("a?") regex = Regex.new("a?", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -75,7 +75,7 @@ class Propane
end end
it "parses a multiplicity count" do it "parses a multiplicity count" do
regex = Regex.new("a{5}") regex = Regex.new("a{5}", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -89,7 +89,7 @@ class Propane
end end
it "parses a minimum-only multiplicity count" do it "parses a minimum-only multiplicity count" do
regex = Regex.new("a{5,}") regex = Regex.new("a{5,}", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -103,7 +103,7 @@ class Propane
end end
it "parses a minimum and maximum multiplicity count" do it "parses a minimum and maximum multiplicity count" do
regex = Regex.new("a{5,8}") regex = Regex.new("a{5,8}", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -118,7 +118,7 @@ class Propane
end end
it "parses an escaped *" do it "parses an escaped *" do
regex = Regex.new("a\\*") regex = Regex.new("a\\*", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -131,7 +131,7 @@ class Propane
end end
it "parses an escaped +" do it "parses an escaped +" do
regex = Regex.new("a\\+") regex = Regex.new("a\\+", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -144,7 +144,7 @@ class Propane
end end
it "parses an escaped \\" do it "parses an escaped \\" do
regex = Regex.new("\\\\d") regex = Regex.new("\\\\d", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -157,7 +157,7 @@ class Propane
end end
it "parses a character class" do it "parses a character class" do
regex = Regex.new("[a-z_]") regex = Regex.new("[a-z_]", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -175,7 +175,7 @@ class Propane
end end
it "parses a negated character class" do it "parses a negated character class" do
regex = Regex.new("[^xyz]") regex = Regex.new("[^xyz]", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -189,8 +189,25 @@ class Propane
expect(ccu[0].first).to eq "x".ord expect(ccu[0].first).to eq "x".ord
end end
it "parses a negated character class with inner character classes" do
regex = Regex.new("[^x\\sz]", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
seq_unit = regex.unit.alternates[0]
expect(seq_unit.size).to eq 1
expect(seq_unit[0]).to be_a Regex::CharacterClassUnit
ccu = seq_unit[0]
expect(ccu.negate).to be_truthy
expect(ccu.size).to eq 8
expect(ccu[0]).to be_a Regex::CharacterRangeUnit
expect(ccu[0].first).to eq "x".ord
expect(ccu[1].first).to eq " ".ord
expect(ccu[7].first).to eq "z".ord
end
it "parses - as a plain character at beginning of a character class" do it "parses - as a plain character at beginning of a character class" do
regex = Regex.new("[-9]") regex = Regex.new("[-9]", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -204,7 +221,7 @@ class Propane
end end
it "parses - as a plain character at end of a character class" do it "parses - as a plain character at end of a character class" do
regex = Regex.new("[0-]") regex = Regex.new("[0-]", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -220,7 +237,7 @@ class Propane
end end
it "parses - as a plain character at beginning of a negated character class" do it "parses - as a plain character at beginning of a negated character class" do
regex = Regex.new("[^-9]") regex = Regex.new("[^-9]", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -235,7 +252,7 @@ class Propane
end end
it "parses . as a plain character in a character class" do it "parses . as a plain character in a character class" do
regex = Regex.new("[.]") regex = Regex.new("[.]", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -250,7 +267,7 @@ class Propane
end end
it "parses - as a plain character when escaped in middle of character class" do it "parses - as a plain character when escaped in middle of character class" do
regex = Regex.new("[0\\-9]") regex = Regex.new("[0\\-9]", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -269,7 +286,7 @@ class Propane
end end
it "parses alternates" do it "parses alternates" do
regex = Regex.new("ab|c") regex = Regex.new("ab|c", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 2 expect(regex.unit.alternates.size).to eq 2
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -279,7 +296,7 @@ class Propane
end end
it "parses a ." do it "parses a ." do
regex = Regex.new("a.b") regex = Regex.new("a.b", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 1 expect(regex.unit.alternates.size).to eq 1
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
@@ -290,7 +307,7 @@ class Propane
end end
it "parses something complex" do it "parses something complex" do
regex = Regex.new("(a|)*|[^^]|\\|v|[x-y]+") regex = Regex.new("(a|)*|[^^]|\\|v|[x-y]+", 1)
expect(regex.unit).to be_a Regex::AlternatesUnit expect(regex.unit).to be_a Regex::AlternatesUnit
expect(regex.unit.alternates.size).to eq 4 expect(regex.unit.alternates.size).to eq 4
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
+518 -59
View File
@@ -54,6 +54,7 @@ EOF
else else
command += %W[spec/run/testparser#{options[:name]}.propane spec/run/testparser#{options[:name]}.#{options[:language]} --log spec/run/testparser#{options[:name]}.log] command += %W[spec/run/testparser#{options[:name]}.propane spec/run/testparser#{options[:name]}.#{options[:language]} --log spec/run/testparser#{options[:name]}.log]
end end
command += (options[:extra_args] || [])
if (options[:capture]) if (options[:capture])
stdout, stderr, status = Open3.capture3(*command) stdout, stderr, status = Open3.capture3(*command)
Results.new(stdout, stderr, status) Results.new(stdout, stderr, status)
@@ -64,26 +65,47 @@ EOF
end end
def compile(test_files, options = {}) def compile(test_files, options = {})
test_files = Array(test_files) test_files = Array(test_files).map do |test_file|
if !File.exist?(test_file) && test_file.end_with?(".cpp")
test_file.sub(%r{\.cpp$}, ".c")
else
test_file
end
end
options[:parsers] ||= [""] options[:parsers] ||= [""]
parsers = options[:parsers].map do |name| parsers = options[:parsers].map do |name|
"spec/run/testparser#{name}.#{options[:language]}" "spec/run/testparser#{name}.#{options[:language]}"
end end
case options[:language] case options[:language]
when "c" when "c"
result = system(*%w[gcc -Wall -o spec/run/testparser -Ispec -Ispec/run], *parsers, *test_files, "spec/testutils.c", "-lm") command = [*%w[gcc -g -Wall -o spec/run/testparser -Ispec -Ispec/run], *parsers, *test_files, "spec/testutils.c", "-lm"]
when "cpp"
command = [*%w[g++ -g -x c++ -Wall -o spec/run/testparser -Ispec -Ispec/run], *parsers, *test_files, "spec/testutils.c", "-lm"]
when "d" when "d"
result = system(*%w[ldc2 --unittest -of spec/run/testparser -Ispec], *parsers, *test_files, "spec/testutils.d") command = [*%w[ldc2 -g --unittest -of spec/run/testparser -Ispec], *parsers, *test_files, "spec/testutils.d"]
end end
result = system(*command)
expect(result).to be_truthy expect(result).to be_truthy
end end
def run_test def run_test(options)
stdout, stderr, status = Open3.capture3("spec/run/testparser") stdout, stderr, status = Open3.capture3("spec/run/testparser")
File.binwrite("spec/run/.stderr", stderr) File.binwrite("spec/run/.stderr", stderr)
File.binwrite("spec/run/.stdout", stdout) File.binwrite("spec/run/.stdout", stdout)
stderr.sub!(/^.*modules passed unittests\n/, "") stderr.sub!(/^.*modules passed unittests\n/, "")
Results.new(stdout, stderr, status) results = Results.new(stdout, stderr, status)
if %w[c cpp].include?(options[:language])
stdout, stderr, status = Open3.capture3("valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose spec/run/testparser")
vgout = stdout + stderr
File.binwrite("spec/run/.vgout", vgout)
vgout.scan(/lost: (\d+) bytes/) do |match|
bytes = $1.to_i
if bytes > 0
raise "Valgrind detected memory leak"
end
end
end
results
end end
def lines(str) def lines(str)
@@ -184,7 +206,83 @@ EOF
expect(results.status).to_not eq 0 expect(results.status).to_not eq 0
end end
%w[d c].each do |language| it "warns on shift/reduce conflicts" do
write_grammar <<EOF
token a;
token b;
Start -> As? b?;
As -> a As2?;
As2 -> b a As2?;
EOF
results = run_propane(capture: true)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
expect(File.binread("spec/run/testparser.log")).to match %r{Shift/Reduce conflict \(state \d+\) between token b and rule As2\? \(defined on line 4\)}
end
it "errors on shift/reduce conflicts with -w" do
write_grammar <<EOF
token a;
token b;
Start -> As? b?;
As -> a As2?;
As2 -> b a As2?;
EOF
results = run_propane(extra_args: %w[-w], capture: true)
expect(results.stderr).to match %r{Shift/Reduce conflict \(state \d+\) between token b and rule As2\? \(defined on line 4\)}m
expect(results.status).to_not eq 0
expect(File.binread("spec/run/testparser.log")).to match %r{Shift/Reduce conflict \(state \d+\) between token b and rule As2\? \(defined on line 4\)}
end
it "errors on duplicate field aliases in a rule" do
write_grammar <<EOF
token a;
token b;
Start -> a:foo b:foo;
EOF
results = run_propane(extra_args: %w[-w], capture: true)
expect(results.stderr).to match %r{Error: duplicate field alias `foo` for rule Start defined on line 3}
expect(results.status).to_not eq 0
end
it "errors when an alias is in different positions for different rules in a rule set when tree mode is enabled" do
write_grammar <<EOF
tree;
token a;
token b;
Start -> a:foo b;
Start -> b b:foo;
EOF
results = run_propane(extra_args: %w[-w], capture: true)
expect(results.stderr).to match %r{Error: conflicting tree node field positions for alias `foo`}
expect(results.status).to_not eq 0
end
it "does not error when an alias is in different positions for different rules in a rule set when tree mode is not enabled" do
write_grammar <<EOF
token a;
token b;
Start -> a:foo b;
Start -> b b:foo;
EOF
results = run_propane(extra_args: %w[-w], capture: true)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "shows error line number for unmatched left curly brace" do
write_grammar <<EOF
# Line 1
# Line 2
token a /a{/;
Start -> a;
EOF
results = run_propane(capture: true)
expect(results.stderr).to match /Line 3: unexpected match count following \{/
expect(results.status).to_not eq 0
end
%w[d c cpp].each do |language|
context "#{language.upcase} language" do context "#{language.upcase} language" do
@@ -200,14 +298,14 @@ Foo -> plus <<>>
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer.#{language}", language: language) compile("spec/test_lexer.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "detects a lexer error when an unknown character is seen" do it "detects a lexer error when an unknown character is seen" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
ptype int; ptype int;
token int /\\d+/ << token int /\\d+/ <<
@@ -238,7 +336,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_unknown_character.#{language}", language: language) compile("spec/test_lexer_unknown_character.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -261,7 +359,7 @@ EOF
it "generates a parser that does basic math - user guide example" do it "generates a parser that does basic math - user guide example" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <math.h> #include <math.h>
@@ -332,7 +430,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_basic_math_grammar.#{language}", language: language) compile("spec/test_basic_math_grammar.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -358,7 +456,7 @@ R2 -> a b;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_parser_identical_rules_lookahead.#{language}", language: language) compile("spec/test_parser_identical_rules_lookahead.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -373,13 +471,13 @@ R1 -> b;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_parser_rule_from_multiple_states.#{language}", language: language) compile("spec/test_parser_rule_from_multiple_states.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "executes user code when matching lexer token" do it "executes user code when matching lexer token" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -408,7 +506,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_user_code.#{language}", language: language) compile("spec/test_user_code.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"abc!", "abc!",
@@ -421,7 +519,7 @@ EOF
it "supports a pattern statement" do it "supports a pattern statement" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -444,7 +542,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_pattern.#{language}", language: language) compile("spec/test_pattern.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"def!", "def!",
@@ -457,7 +555,7 @@ EOF
it "supports returning tokens from pattern code blocks" do it "supports returning tokens from pattern code blocks" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -486,7 +584,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_return_token_from_pattern.#{language}", language: language) compile("spec/test_return_token_from_pattern.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"def!", "def!",
@@ -497,7 +595,7 @@ EOF
it "supports lexer modes" do it "supports lexer modes" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -544,7 +642,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_modes.#{language}", language: language) compile("spec/test_lexer_modes.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"begin string mode", "begin string mode",
@@ -556,9 +654,65 @@ EOF
]) ])
end end
it "multiple lexer modes may apply to a pattern" do
case language
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
>>
ptype char;
token abc;
token def;
default, identonly: token ident /[a-z]+/ <<
$$ = match[0];
$mode(default);
return $token(ident);
>>
token dot /\\./ <<
$mode(identonly);
>>
default, identonly: drop /\\s+/;
Start -> abc dot ident <<
printf("ident: %c\\n", $3);
>>
EOF
when "d"
write_grammar <<EOF
<<
import std.stdio;
>>
ptype char;
token abc;
token def;
default, identonly: token ident /[a-z]+/ <<
$$ = match[0];
$mode(default);
>>
token dot /\\./ <<
$mode(identonly);
>>
default, identonly: drop /\\s+/;
Start -> abc dot ident <<
writeln("ident: ", $3);
>>
EOF
end
run_propane(language: language)
compile("spec/test_lexer_multiple_modes.#{language}", language: language)
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"ident: d",
"pass1",
"ident: a",
"pass2",
])
end
it "executes user code associated with a parser rule" do it "executes user code associated with a parser rule" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -583,7 +737,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_parser_rule_user_code.#{language}", language: language) compile("spec/test_parser_rule_user_code.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"A!", "A!",
@@ -594,7 +748,7 @@ EOF
it "parses lists" do it "parses lists" do
write_grammar <<EOF write_grammar <<EOF
ptype #{language == "c" ? "uint32_t" : "uint"}; ptype #{language == "d" ? "uint" : "uint32_t"};
token a; token a;
Start -> As << $$ = $1; >> Start -> As << $$ = $1; >>
As -> << $$ = 0u; >> As -> << $$ = 0u; >>
@@ -602,7 +756,7 @@ As -> As a << $$ = $1 + 1u; >>
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_parsing_lists.#{language}", language: language) compile("spec/test_parsing_lists.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
end end
@@ -623,19 +777,20 @@ F -> e;
EOF EOF
results = run_propane(capture: true, language: language) results = run_propane(capture: true, language: language)
expect(results.status).to_not eq 0 expect(results.status).to_not eq 0
expect(results.stderr).to match %r{reduce/reduce conflict.*\(E\).*\(F\)} expect(results.stderr).to match %r{Error: reduce/reduce conflict \(state \d+\) between rule E#\d+ \(defined on line 10\) and rule F#\d+ \(defined on line 11\)}
expect(File.binread("spec/run/testparser.log")).to match %r{Reduce.actions:}
end end
it "provides matched text to user code blocks" do it "provides matched text to user code blocks" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
>> >>
token id /[a-zA-Z_][a-zA-Z0-9_]*/ << token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * t = malloc(match_length + 1); char * t = (char *)malloc(match_length + 1);
strncpy(t, (char *)match, match_length); strncpy(t, (char *)match, match_length);
printf("Matched token is %s\\n", t); printf("Matched token is %s\\n", t);
free(t); free(t);
@@ -655,7 +810,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_match_text.#{language}", language: language) compile("spec/test_lexer_match_text.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"Matched token is identifier_123", "Matched token is identifier_123",
@@ -665,7 +820,7 @@ EOF
it "allows storing a result value for the lexer" do it "allows storing a result value for the lexer" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
ptype uint64_t; ptype uint64_t;
token word /[a-z]+/ << token word /[a-z]+/ <<
@@ -688,7 +843,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_result_value.#{language}", language: language) compile("spec/test_lexer_result_value.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -703,13 +858,14 @@ Start -> a num;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_error_positions.#{language}", language: language) compile("spec/test_error_positions.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "allows creating a JSON parser" do it "allows creating a JSON parser" do
write_grammar(File.read("spec/json_parser.#{language}.propane")) ext = language == "cpp" ? "c" : language
write_grammar(File.read("spec/json_parser.#{ext}.propane"))
run_propane(language: language) run_propane(language: language)
compile(["spec/test_parsing_json.#{language}", "spec/json_types.#{language}"], language: language) compile(["spec/test_parsing_json.#{language}", "spec/json_types.#{language}"], language: language)
end end
@@ -731,7 +887,7 @@ Start -> b c b;
EOF EOF
run_propane(name: "myp2", language: language) run_propane(name: "myp2", language: language)
compile("spec/test_multiple_parsers.#{language}", parsers: %w[myp1 myp2], language: language) compile("spec/test_multiple_parsers.#{language}", parsers: %w[myp1 myp2], language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -750,7 +906,7 @@ Any -> c;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_user_terminate_lexer.#{language}", language: language) compile("spec/test_user_terminate_lexer.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -768,14 +924,14 @@ Any -> ;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_user_terminate.#{language}", language: language) compile("spec/test_user_terminate.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "matches backslash escape sequences" do it "matches backslash escape sequences" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -812,7 +968,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_match_backslashes.#{language}", language: language) compile("spec/test_match_backslashes.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
@@ -840,9 +996,9 @@ EOF
run_propane(language: language) run_propane(language: language)
end end
it "generates an AST" do it "generates a tree" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
ptype int; ptype int;
@@ -876,17 +1032,17 @@ One -> one;
Two -> two; Two -> two;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast.#{language}", language: language) compile("spec/test_tree.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "supports AST node prefix and suffix" do it "supports tree node prefix and suffix" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
ast_prefix P ; tree_prefix P ;
ast_suffix S; tree_suffix S;
ptype int; ptype int;
@@ -920,8 +1076,8 @@ One -> one;
Two -> two; Two -> two;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast_ps.#{language}", language: language) compile("spec/test_tree_ps.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -936,15 +1092,15 @@ EOF
compile("spec/test_start_rule.#{language}", language: language) compile("spec/test_start_rule.#{language}", language: language)
end end
it "allows specifying a different start rule with AST generation" do it "allows specifying a different start rule with tree generation" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
token hi; token hi;
start Top; start Top;
Top -> hi; Top -> hi;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_start_rule_ast.#{language}", language: language) compile("spec/test_start_rule_tree.#{language}", language: language)
end end
it "allows marking a rule component as optional" do it "allows marking a rule component as optional" do
@@ -989,13 +1145,13 @@ Start -> a? b R? <<
printf("b: %d\\n", $2); printf("b: %d\\n", $2);
printf("R: %s\\n", $3 == NULL ? "" : $3); printf("R: %s\\n", $3 == NULL ? "" : $3);
>> >>
R -> c d << $$ = "cd"; >> R -> c d << $$ = (char *)"cd"; >>
R (string) -> d c << $$ = "dc"; >> R (string) -> d c << $$ = (char *)"dc"; >>
EOF EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_optional_rule_component.#{language}", language: language) compile("spec/test_optional_rule_component.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
@@ -1011,10 +1167,10 @@ EOF
]) ])
end end
it "allows marking a rule component as optional in AST generation mode" do it "allows marking a rule component as optional in tree generation mode" do
if language == "d" if language == "d"
write_grammar <<EOF write_grammar <<EOF
ast; tree;
<< <<
import std.stdio; import std.stdio;
@@ -1030,7 +1186,7 @@ R -> d c;
EOF EOF
else else
write_grammar <<EOF write_grammar <<EOF
ast; tree;
<< <<
#include <stdio.h> #include <stdio.h>
@@ -1046,11 +1202,314 @@ R -> d c;
EOF EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_optional_rule_component_ast.#{language}", language: language) compile("spec/test_optional_rule_component_tree.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "allows naming an optional rule component in tree generation mode" do
if language == "d"
write_grammar <<EOF
tree;
<<
import std.stdio;
>>
token a;
token b;
token c;
token d;
Start -> a?:a b R?:r;
R -> c d;
R -> d c;
EOF
else
write_grammar <<EOF
tree;
<<
#include <stdio.h>
>>
token a;
token b;
token c;
token d;
Start -> a?:a b R?:r;
R -> c d;
R -> d c;
EOF
end
run_propane(language: language)
compile("spec/test_named_optional_rule_component_tree.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "stores token and rule positions in tree nodes" do
write_grammar <<EOF
tree;
token a;
token bb;
token c /c(.|\\n)*c/;
drop /\\s+/;
Start -> T T T;
T -> a;
T -> bb;
T -> c;
EOF
run_propane(language: language)
compile("spec/test_tree_token_positions.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "stores invalid positions for empty rule matches" do
write_grammar <<EOF
tree;
token a;
token bb;
token c /c(.|\\n)*c/;
drop /\\s+/;
Start -> T Start;
Start -> ;
T -> a A;
A -> bb? c?;
EOF
run_propane(language: language)
compile("spec/test_tree_invalid_positions.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "allows specifying field aliases in tree mode" do
write_grammar <<EOF
tree;
token a;
token b;
token c;
drop /\\s+/;
Start -> T:first T:second T:third;
T -> a;
T -> b;
T -> c;
EOF
run_propane(language: language)
compile("spec/test_tree_field_aliases.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "aliases the correct field when multiple rules are in a rule set in tree mode" do
write_grammar <<EOF
tree;
token a;
token b;
token c;
drop /\\s+/;
Start -> a;
Start -> Foo;
Start -> T:first T:second T:third;
Foo -> b;
T -> a;
T -> b;
T -> c;
EOF
run_propane(language: language)
compile("spec/test_tree_field_aliases.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "allows specifying field aliases when tree mode is not enabled" do
if language == "d"
write_grammar <<EOF
<<
import std.stdio;
>>
ptype string;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
$$ = match;
>>
drop /\\s+/;
Start -> id:first id:second <<
writeln("first is ", ${first});
writeln("second is ", ${second});
>>
EOF
else
write_grammar <<EOF
<<
#include <stdio.h>
#include <string.h>
>>
ptype char *;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * s = (char *)malloc(match_length + 1);
strncpy(s, (char const *)match, match_length);
s[match_length] = 0;
$$ = s;
>>
drop /\\s+/;
Start -> id:first id:second <<
printf("first is %s\\n", ${first});
printf("second is %s\\n", ${second});
free(${first});
free(${second});
>>
EOF
end
run_propane(language: language)
compile("spec/test_field_aliases.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
expect(results.stdout).to match /first is foo1.*second is bar2/m
end
it "aliases the correct field when multiple rules are in a rule set when tree mode is not enabled" do
if language == "d"
write_grammar <<EOF
<<
import std.stdio;
>>
ptype string;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
$$ = match;
>>
drop /\\s+/;
Start -> id;
Start -> Foo;
Start -> id:first id:second <<
writeln("first is ", ${first});
writeln("second is ", ${second});
>>
Foo -> ;
EOF
else
write_grammar <<EOF
<<
#include <stdio.h>
#include <string.h>
>>
ptype char *;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * s = (char *)malloc(match_length + 1);
strncpy(s, (char const *)match, match_length);
s[match_length] = 0;
$$ = s;
>>
drop /\\s+/;
Start -> id;
Start -> Foo;
Start -> id:first id:second <<
printf("first is %s\\n", ${first});
printf("second is %s\\n", ${second});
free(${first});
free(${second});
>>
Foo -> ;
EOF
end
run_propane(language: language)
compile("spec/test_field_aliases.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
expect(results.stdout).to match /first is foo1.*second is bar2/m
end
it "does not free memory allocated for tree nodes" do
ext = language == "cpp" ? "c" : language
write_grammar(File.read("spec/tree_node_memory_remains.#{ext}.propane"))
run_propane(language: language)
compile("spec/test_tree_node_memory_remains.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "allows multiple starting rules" do
write_grammar <<EOF
ptype int;
token a << $$ = 1; >>
token b << $$ = 2; >>
token c << $$ = 3; >>
Start -> a b R;
Start -> Bs:bs << $$ = $1; >>
R -> c:c << $$ = $1; >>
Bs -> << $$ = 0; >>
Bs -> b:b Bs:bs << $$ = $1 + $2; >>
start Start R Bs;
EOF
run_propane(language: language)
compile("spec/test_starting_rules.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "allows multiple starting rules in tree mode" do
write_grammar <<EOF
tree;
ptype int;
token a << $$ = 1; >>
token b << $$ = 2; >>
token c << $$ = 3; >>
Start -> a b R;
Start -> Bs:bs;
R -> c:c;
Bs -> ;
Bs -> b:b Bs:bs;
start Start R Bs;
EOF
run_propane(language: language)
compile("spec/test_starting_rules_tree.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
if %w[c cpp].include?(language)
it "allows a user function to free token node memory in tree mode" do
write_grammar <<EOF
<<
static void free_token(Token * token)
{
free(token->pvalue);
}
>>
tree;
free_token_node free_token;
ptype int *;
token a <<
$$ = (int *)malloc(sizeof(int));
*$$ = 1;
>>
token b <<
$$ = (int *)malloc(sizeof(int));
*$$ = 2;
>>
Start -> a:a b:b;
EOF
run_propane(language: language)
compile("spec/test_free_tree_token_node_memory.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
end
end end
end end
end end
+5
View File
@@ -2,6 +2,10 @@ unless ENV["dist_specs"]
require "bundler/setup" require "bundler/setup"
require "simplecov" require "simplecov"
class MyFormatter
def format(*args)
end
end
SimpleCov.start do SimpleCov.start do
add_filter "/spec/" add_filter "/spec/"
add_filter "/.bundle/" add_filter "/.bundle/"
@@ -12,6 +16,7 @@ unless ENV["dist_specs"]
end end
project_name "Propane" project_name "Propane"
merge_timeout 3600 merge_timeout 3600
formatter(MyFormatter)
end end
RSpec.configure do |config| RSpec.configure do |config|
+8 -8
View File
@@ -12,28 +12,28 @@ int main()
input = "a\n123\na a"; input = "a\n123\na a";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_UNEXPECTED_TOKEN); assert(p_parse(&context) == P_UNEXPECTED_TOKEN);
assert(p_position(&context).row == 2); assert(p_position(&context).row == 3);
assert(p_position(&context).col == 3); assert(p_position(&context).col == 4);
assert(p_token(&context) == TOKEN_a); assert(p_token(&context) == TOKEN_a);
input = "12"; input = "12";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_UNEXPECTED_TOKEN); assert(p_parse(&context) == P_UNEXPECTED_TOKEN);
assert(p_position(&context).row == 0); assert(p_position(&context).row == 1);
assert(p_position(&context).col == 0); assert(p_position(&context).col == 1);
assert(p_token(&context) == TOKEN_num); assert(p_token(&context) == TOKEN_num);
input = "a 12\n\nab"; input = "a 12\n\nab";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_UNEXPECTED_INPUT); assert(p_parse(&context) == P_UNEXPECTED_INPUT);
assert(p_position(&context).row == 2); assert(p_position(&context).row == 3);
assert(p_position(&context).col == 1); assert(p_position(&context).col == 2);
input = "a 12\n\na\n\n77\na \xAA"; input = "a 12\n\na\n\n77\na \xAA";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_DECODE_ERROR); assert(p_parse(&context) == P_DECODE_ERROR);
assert(p_position(&context).row == 5); assert(p_position(&context).row == 6);
assert(p_position(&context).col == 4); assert(p_position(&context).col == 5);
assert(strcmp(p_token_names[TOKEN_a], "a") == 0); assert(strcmp(p_token_names[TOKEN_a], "a") == 0);
assert(strcmp(p_token_names[TOKEN_num], "num") == 0); assert(strcmp(p_token_names[TOKEN_num], "num") == 0);
+4 -4
View File
@@ -16,24 +16,24 @@ unittest
input = "a\n123\na a"; input = "a\n123\na a";
p_context_init(&context, input); p_context_init(&context, input);
assert(p_parse(&context) == P_UNEXPECTED_TOKEN); assert(p_parse(&context) == P_UNEXPECTED_TOKEN);
assert(p_position(&context) == p_position_t(2, 3)); assert(p_position(&context) == p_position_t(3, 4));
assert(p_token(&context) == TOKEN_a); assert(p_token(&context) == TOKEN_a);
input = "12"; input = "12";
p_context_init(&context, input); p_context_init(&context, input);
assert(p_parse(&context) == P_UNEXPECTED_TOKEN); assert(p_parse(&context) == P_UNEXPECTED_TOKEN);
assert(p_position(&context) == p_position_t(0, 0)); assert(p_position(&context) == p_position_t(1, 1));
assert(p_token(&context) == TOKEN_num); assert(p_token(&context) == TOKEN_num);
input = "a 12\n\nab"; input = "a 12\n\nab";
p_context_init(&context, input); p_context_init(&context, input);
assert(p_parse(&context) == P_UNEXPECTED_INPUT); assert(p_parse(&context) == P_UNEXPECTED_INPUT);
assert(p_position(&context) == p_position_t(2, 1)); assert(p_position(&context) == p_position_t(3, 2));
input = "a 12\n\na\n\n77\na \xAA"; input = "a 12\n\na\n\n77\na \xAA";
p_context_init(&context, input); p_context_init(&context, input);
assert(p_parse(&context) == P_DECODE_ERROR); assert(p_parse(&context) == P_DECODE_ERROR);
assert(p_position(&context) == p_position_t(5, 4)); assert(p_position(&context) == p_position_t(6, 5));
assert(p_token_names[TOKEN_a] == "a"); assert(p_token_names[TOKEN_a] == "a");
assert(p_token_names[TOKEN_num] == "num"); assert(p_token_names[TOKEN_num] == "num");
+13
View File
@@ -0,0 +1,13 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "foo1\nbar2";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
import testparser;
import std.stdio;
int main()
{
return 0;
}
unittest
{
string input = "foo1\nbar2";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
}
+19
View File
@@ -0,0 +1,19 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "ab";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
Start * start = p_result(&context);
assert(start->a != NULL);
assert(*start->a->pvalue == 1);
assert(start->b != NULL);
assert(*start->b->pvalue == 2);
p_free_tree(start);
}
+36 -18
View File
@@ -41,50 +41,68 @@ int main()
p_context_t context; p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 0u); assert(token_info.position.row == 1u);
assert(token_info.position.col == 0u); assert(token_info.position.col == 1u);
assert(token_info.end_position.row == 1u);
assert(token_info.end_position.col == 1u);
assert(token_info.length == 1u); assert(token_info.length == 1u);
assert(token_info.token == TOKEN_int); assert(token_info.token == TOKEN_int);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 0u); assert(token_info.position.row == 1u);
assert(token_info.position.col == 2u); assert(token_info.position.col == 3u);
assert(token_info.end_position.row == 1u);
assert(token_info.end_position.col == 3u);
assert(token_info.length == 1u); assert(token_info.length == 1u);
assert(token_info.token == TOKEN_plus); assert(token_info.token == TOKEN_plus);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 0u); assert(token_info.position.row == 1u);
assert(token_info.position.col == 4u); assert(token_info.position.col == 5u);
assert(token_info.end_position.row == 1u);
assert(token_info.end_position.col == 5u);
assert(token_info.length == 1u); assert(token_info.length == 1u);
assert(token_info.token == TOKEN_int); assert(token_info.token == TOKEN_int);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 0u); assert(token_info.position.row == 1u);
assert(token_info.position.col == 6u); assert(token_info.position.col == 7u);
assert(token_info.end_position.row == 1u);
assert(token_info.end_position.col == 7u);
assert(token_info.length == 1u); assert(token_info.length == 1u);
assert(token_info.token == TOKEN_times); assert(token_info.token == TOKEN_times);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 1u); assert(token_info.position.row == 2u);
assert(token_info.position.col == 0u); assert(token_info.position.col == 1u);
assert(token_info.end_position.row == 2u);
assert(token_info.end_position.col == 3u);
assert(token_info.length == 3u); assert(token_info.length == 3u);
assert(token_info.token == TOKEN_int); assert(token_info.token == TOKEN_int);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 1u); assert(token_info.position.row == 2u);
assert(token_info.position.col == 4u); assert(token_info.position.col == 5u);
assert(token_info.end_position.row == 2u);
assert(token_info.end_position.col == 5u);
assert(token_info.length == 1u); assert(token_info.length == 1u);
assert(token_info.token == TOKEN_plus); assert(token_info.token == TOKEN_plus);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 1u); assert(token_info.position.row == 2u);
assert(token_info.position.col == 6u); assert(token_info.position.col == 7u);
assert(token_info.end_position.row == 2u);
assert(token_info.end_position.col == 9u);
assert(token_info.length == 3u); assert(token_info.length == 3u);
assert(token_info.token == TOKEN_int); assert(token_info.token == TOKEN_int);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 1u); assert(token_info.position.row == 2u);
assert(token_info.position.col == 9u); assert(token_info.position.col == 10u);
assert(token_info.end_position.row == 2u);
assert(token_info.end_position.col == 10u);
assert(token_info.length == 0u); assert(token_info.length == 0u);
assert(token_info.token == TOKEN___EOF); assert(token_info.token == TOKEN___EOF);
p_context_init(&context, (uint8_t const *)"", 0u); p_context_init(&context, (uint8_t const *)"", 0u);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info.position.row == 0u); assert(token_info.position.row == 1u);
assert(token_info.position.col == 0u); assert(token_info.position.col == 1u);
assert(token_info.end_position.row == 1u);
assert(token_info.end_position.col == 1u);
assert(token_info.length == 0u); assert(token_info.length == 0u);
assert(token_info.token == TOKEN___EOF); assert(token_info.token == TOKEN___EOF);
+9 -9
View File
@@ -47,23 +47,23 @@ unittest
p_context_t context; p_context_t context;
p_context_init(&context, input); p_context_init(&context, input);
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(0, 0), 1, TOKEN_int)); assert(token_info == p_token_info_t(p_position_t(1, 1), p_position_t(1, 1), 1, TOKEN_int));
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(0, 2), 1, TOKEN_plus)); assert(token_info == p_token_info_t(p_position_t(1, 3), p_position_t(1, 3), 1, TOKEN_plus));
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(0, 4), 1, TOKEN_int)); assert(token_info == p_token_info_t(p_position_t(1, 5), p_position_t(1, 5), 1, TOKEN_int));
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(0, 6), 1, TOKEN_times)); assert(token_info == p_token_info_t(p_position_t(1, 7), p_position_t(1, 7), 1, TOKEN_times));
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(1, 0), 3, TOKEN_int)); assert(token_info == p_token_info_t(p_position_t(2, 1), p_position_t(2, 3), 3, TOKEN_int));
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(1, 4), 1, TOKEN_plus)); assert(token_info == p_token_info_t(p_position_t(2, 5), p_position_t(2, 5), 1, TOKEN_plus));
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(1, 6), 3, TOKEN_int)); assert(token_info == p_token_info_t(p_position_t(2, 7), p_position_t(2, 9), 3, TOKEN_int));
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(1, 9), 0, TOKEN___EOF)); assert(token_info == p_token_info_t(p_position_t(2, 10), p_position_t(2, 10), 0, TOKEN___EOF));
p_context_init(&context, ""); p_context_init(&context, "");
assert(p_lex(&context, &token_info) == P_SUCCESS); assert(p_lex(&context, &token_info) == P_SUCCESS);
assert(token_info == p_token_info_t(p_position_t(0, 0), 0, TOKEN___EOF)); assert(token_info == p_token_info_t(p_position_t(1, 1), p_position_t(1, 1), 0, TOKEN___EOF));
} }
+20
View File
@@ -0,0 +1,20 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
int main()
{
char const * input = "abc.def";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
printf("pass1\n");
input = "abc . abc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
printf("pass2\n");
return 0;
}
+21
View File
@@ -0,0 +1,21 @@
import testparser;
import std.stdio;
int main()
{
return 0;
}
unittest
{
string input = `abc.def`;
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
writeln("pass1");
input = `abc . abc`;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
writeln("pass2");
}
@@ -0,0 +1,51 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "b";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert(start->a == NULL);
assert(start->pToken2 != NULL);
assert_eq(TOKEN_b, start->pToken2->token);
assert(start->pR3 == NULL);
assert(start->pR == NULL);
assert(start->r == NULL);
p_free_tree(start);
input = "abcd";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert(start->a != NULL);
assert_eq(TOKEN_a, start->pToken1->token);
assert(start->pToken2 != NULL);
assert(start->pR3 != NULL);
assert(start->pR != NULL);
assert(start->r != NULL);
assert(start->pR == start->pR3);
assert(start->pR == start->r);
assert_eq(TOKEN_c, start->pR->pToken1->token);
p_free_tree(start);
input = "bdc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert(start->a == NULL);
assert(start->pToken2 != NULL);
assert(start->r != NULL);
assert_eq(TOKEN_d, start->pR->pToken1->token);
p_free_tree(start);
return 0;
}
@@ -0,0 +1,46 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "b";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert(start.pToken1 is null);
assert(start.pToken2 !is null);
assert_eq(TOKEN_b, start.pToken2.token);
assert(start.pR3 is null);
assert(start.pR is null);
assert(start.r is null);
input = "abcd";
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert(start.pToken1 != null);
assert_eq(TOKEN_a, start.pToken1.token);
assert(start.pToken2 != null);
assert(start.pR3 != null);
assert(start.pR != null);
assert(start.r != null);
assert(start.pR == start.pR3);
assert(start.pR == start.r);
assert_eq(TOKEN_c, start.pR.pToken1.token);
input = "bdc";
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert(start.pToken1 is null);
assert(start.pToken2 !is null);
assert(start.pR !is null);
assert_eq(TOKEN_d, start.pR.pToken1.token);
}
@@ -16,6 +16,8 @@ int main()
assert(start->pR3 == NULL); assert(start->pR3 == NULL);
assert(start->pR == NULL); assert(start->pR == NULL);
p_free_tree(start);
input = "abcd"; input = "abcd";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); assert(p_parse(&context) == P_SUCCESS);
@@ -28,6 +30,8 @@ int main()
assert(start->pR == start->pR3); assert(start->pR == start->pR3);
assert_eq(TOKEN_c, start->pR->pToken1->token); assert_eq(TOKEN_c, start->pR->pToken1->token);
p_free_tree(start);
input = "bdc"; input = "bdc";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); assert(p_parse(&context) == P_SUCCESS);
@@ -37,6 +41,8 @@ int main()
assert(start->pR != NULL); assert(start->pR != NULL);
assert_eq(TOKEN_d, start->pR->pToken1->token); assert_eq(TOKEN_d, start->pR->pToken1->token);
p_free_tree(start);
return 0; return 0;
} }
+2 -2
View File
@@ -8,8 +8,8 @@ int main()
p_context_t context; p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_UNEXPECTED_TOKEN); assert(p_parse(&context) == P_UNEXPECTED_TOKEN);
assert(p_position(&context).row == 0); assert(p_position(&context).row == 1);
assert(p_position(&context).col == 1); assert(p_position(&context).col == 2);
assert(context.token == TOKEN___EOF); assert(context.token == TOKEN___EOF);
input = "a b"; input = "a b";
+1 -1
View File
@@ -12,7 +12,7 @@ unittest
p_context_t context; p_context_t context;
p_context_init(&context, input); p_context_init(&context, input);
assert(p_parse(&context) == P_UNEXPECTED_TOKEN); assert(p_parse(&context) == P_UNEXPECTED_TOKEN);
assert(p_position(&context) == p_position_t(0, 1)); assert(p_position(&context) == p_position_t(1, 2));
assert(context.token == TOKEN___EOF); assert(context.token == TOKEN___EOF);
input = "a b"; input = "a b";
@@ -13,5 +13,7 @@ int main()
assert(top->pToken != NULL); assert(top->pToken != NULL);
assert_eq(TOKEN_hi, top->pToken->token); assert_eq(TOKEN_hi, top->pToken->token);
p_free_tree(top);
return 0; return 0;
} }
File renamed without changes.
+27
View File
@@ -0,0 +1,27 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "bbbb";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
int result = p_result(&context);
assert_eq(8, result);
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse_Bs(&context) == P_SUCCESS);
result = p_result_Bs(&context);
assert_eq(8, result);
input = "c";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse_R(&context) == P_SUCCESS);
result = p_result_R(&context);
assert_eq(3, result);
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "bbbb";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
int result = p_result(&context);
assert(result == 8);
p_context_init(&context, input);
assert(p_parse_Bs(&context) == P_SUCCESS);
result = p_result_Bs(&context);
assert(result == 8);
input = "c";
p_context_init(&context, input);
assert(p_parse_R(&context) == P_SUCCESS);
result = p_result_R(&context);
assert(result == 3);
}
+37
View File
@@ -0,0 +1,37 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "bbbb";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert_not_null(start->bs);
assert_not_null(start->bs->b);
assert_not_null(start->bs->bs->b);
assert_not_null(start->bs->bs->bs->b);
assert_not_null(start->bs->bs->bs->bs->b);
p_free_tree(start);
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse_Bs(&context) == P_SUCCESS);
Bs * bs = p_result_Bs(&context);
assert_not_null(bs->b);
assert_not_null(bs->bs->b);
assert_not_null(bs->bs->bs->b);
assert_not_null(bs->bs->bs->bs->b);
p_free_tree_Bs(bs);
input = "c";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse_R(&context) == P_SUCCESS);
R * r = p_result_R(&context);
assert_not_null(r->c);
p_free_tree_R(r);
return 0;
}
+36
View File
@@ -0,0 +1,36 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "bbbb";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert(start.bs);
assert(start.bs.b);
assert(start.bs.bs.b);
assert(start.bs.bs.bs.b);
assert(start.bs.bs.bs.bs.b);
p_context_init(&context, input);
assert(p_parse_Bs(&context) == P_SUCCESS);
Bs * bs = p_result_Bs(&context);
assert(bs.b);
assert(bs.bs.b);
assert(bs.bs.bs.b);
assert(bs.bs.bs.bs.b);
input = "c";
p_context_init(&context, input);
assert(p_parse_R(&context) == P_SUCCESS);
R * r = p_result_R(&context);
assert(r.c);
}
+6
View File
@@ -33,12 +33,16 @@ int main()
assert_eq(22, itemsmore->pItem->pToken1->pvalue); assert_eq(22, itemsmore->pItem->pToken1->pvalue);
assert(itemsmore->pItemsMore == NULL); assert(itemsmore->pItemsMore == NULL);
p_free_tree(start);
input = ""; input = "";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context)); assert_eq(P_SUCCESS, p_parse(&context));
start = p_result(&context); start = p_result(&context);
assert(start->pItems == NULL); assert(start->pItems == NULL);
p_free_tree(start);
input = "2 1"; input = "2 1";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context)); assert_eq(P_SUCCESS, p_parse(&context));
@@ -51,5 +55,7 @@ int main()
assert(start->pItems->pItem->pDual->pTwo2 == NULL); assert(start->pItems->pItem->pDual->pTwo2 == NULL);
assert(start->pItems->pItem->pDual->pOne1 == NULL); assert(start->pItems->pItem->pDual->pOne1 == NULL);
p_free_tree(start);
return 0; return 0;
} }
File renamed without changes.
+21
View File
@@ -0,0 +1,21 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "\na\nb\nc";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert_eq(TOKEN_a, start->first->pToken->token);
assert_eq(TOKEN_b, start->second->pToken->token);
assert_eq(TOKEN_c, start->third->pToken->token);
p_free_tree(start);
return 0;
}
+21
View File
@@ -0,0 +1,21 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "\na\nb\nc";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert_eq(TOKEN_a, start.first.pToken.token);
assert_eq(TOKEN_b, start.second.pToken.token);
assert_eq(TOKEN_c, start.third.pToken.token);
}
+110
View File
@@ -0,0 +1,110 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "\na\n bb ccc";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert_eq(2, start->pT1->pToken->position.row);
assert_eq(1, start->pT1->pToken->position.col);
assert_eq(2, start->pT1->pToken->end_position.row);
assert_eq(1, start->pT1->pToken->end_position.col);
assert(p_position_valid(start->pT1->pA->position));
assert_eq(3, start->pT1->pA->position.row);
assert_eq(3, start->pT1->pA->position.col);
assert_eq(3, start->pT1->pA->end_position.row);
assert_eq(8, start->pT1->pA->end_position.col);
assert_eq(2, start->pT1->position.row);
assert_eq(1, start->pT1->position.col);
assert_eq(3, start->pT1->end_position.row);
assert_eq(8, start->pT1->end_position.col);
assert_eq(2, start->position.row);
assert_eq(1, start->position.col);
assert_eq(3, start->end_position.row);
assert_eq(8, start->end_position.col);
p_free_tree(start);
input = "a\nbb";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert_eq(1, start->pT1->pToken->position.row);
assert_eq(1, start->pT1->pToken->position.col);
assert_eq(1, start->pT1->pToken->end_position.row);
assert_eq(1, start->pT1->pToken->end_position.col);
assert(p_position_valid(start->pT1->pA->position));
assert_eq(2, start->pT1->pA->position.row);
assert_eq(1, start->pT1->pA->position.col);
assert_eq(2, start->pT1->pA->end_position.row);
assert_eq(2, start->pT1->pA->end_position.col);
assert_eq(1, start->pT1->position.row);
assert_eq(1, start->pT1->position.col);
assert_eq(2, start->pT1->end_position.row);
assert_eq(2, start->pT1->end_position.col);
assert_eq(1, start->position.row);
assert_eq(1, start->position.col);
assert_eq(2, start->end_position.row);
assert_eq(2, start->end_position.col);
p_free_tree(start);
input = "a\nc\nc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert_eq(1, start->pT1->pToken->position.row);
assert_eq(1, start->pT1->pToken->position.col);
assert_eq(1, start->pT1->pToken->end_position.row);
assert_eq(1, start->pT1->pToken->end_position.col);
assert(p_position_valid(start->pT1->pA->position));
assert_eq(2, start->pT1->pA->position.row);
assert_eq(1, start->pT1->pA->position.col);
assert_eq(3, start->pT1->pA->end_position.row);
assert_eq(1, start->pT1->pA->end_position.col);
assert_eq(1, start->pT1->position.row);
assert_eq(1, start->pT1->position.col);
assert_eq(3, start->pT1->end_position.row);
assert_eq(1, start->pT1->end_position.col);
assert_eq(1, start->position.row);
assert_eq(1, start->position.col);
assert_eq(3, start->end_position.row);
assert_eq(1, start->end_position.col);
p_free_tree(start);
input = "a";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert_eq(1, start->pT1->pToken->position.row);
assert_eq(1, start->pT1->pToken->position.col);
assert_eq(1, start->pT1->pToken->end_position.row);
assert_eq(1, start->pT1->pToken->end_position.col);
assert(!p_position_valid(start->pT1->pA->position));
assert_eq(1, start->pT1->position.row);
assert_eq(1, start->pT1->position.col);
assert_eq(1, start->pT1->end_position.row);
assert_eq(1, start->pT1->end_position.col);
assert_eq(1, start->position.row);
assert_eq(1, start->position.col);
assert_eq(1, start->end_position.row);
assert_eq(1, start->end_position.col);
p_free_tree(start);
return 0;
}
+104
View File
@@ -0,0 +1,104 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "\na\n bb ccc";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert_eq(2, start.pT1.pToken.position.row);
assert_eq(1, start.pT1.pToken.position.col);
assert_eq(2, start.pT1.pToken.end_position.row);
assert_eq(1, start.pT1.pToken.end_position.col);
assert(start.pT1.pA.position.valid);
assert_eq(3, start.pT1.pA.position.row);
assert_eq(3, start.pT1.pA.position.col);
assert_eq(3, start.pT1.pA.end_position.row);
assert_eq(8, start.pT1.pA.end_position.col);
assert_eq(2, start.pT1.position.row);
assert_eq(1, start.pT1.position.col);
assert_eq(3, start.pT1.end_position.row);
assert_eq(8, start.pT1.end_position.col);
assert_eq(2, start.position.row);
assert_eq(1, start.position.col);
assert_eq(3, start.end_position.row);
assert_eq(8, start.end_position.col);
input = "a\nbb";
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert_eq(1, start.pT1.pToken.position.row);
assert_eq(1, start.pT1.pToken.position.col);
assert_eq(1, start.pT1.pToken.end_position.row);
assert_eq(1, start.pT1.pToken.end_position.col);
assert(start.pT1.pA.position.valid);
assert_eq(2, start.pT1.pA.position.row);
assert_eq(1, start.pT1.pA.position.col);
assert_eq(2, start.pT1.pA.end_position.row);
assert_eq(2, start.pT1.pA.end_position.col);
assert_eq(1, start.pT1.position.row);
assert_eq(1, start.pT1.position.col);
assert_eq(2, start.pT1.end_position.row);
assert_eq(2, start.pT1.end_position.col);
assert_eq(1, start.position.row);
assert_eq(1, start.position.col);
assert_eq(2, start.end_position.row);
assert_eq(2, start.end_position.col);
input = "a\nc\nc";
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert_eq(1, start.pT1.pToken.position.row);
assert_eq(1, start.pT1.pToken.position.col);
assert_eq(1, start.pT1.pToken.end_position.row);
assert_eq(1, start.pT1.pToken.end_position.col);
assert(start.pT1.pA.position.valid);
assert_eq(2, start.pT1.pA.position.row);
assert_eq(1, start.pT1.pA.position.col);
assert_eq(3, start.pT1.pA.end_position.row);
assert_eq(1, start.pT1.pA.end_position.col);
assert_eq(1, start.pT1.position.row);
assert_eq(1, start.pT1.position.col);
assert_eq(3, start.pT1.end_position.row);
assert_eq(1, start.pT1.end_position.col);
assert_eq(1, start.position.row);
assert_eq(1, start.position.col);
assert_eq(3, start.end_position.row);
assert_eq(1, start.end_position.col);
input = "a";
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert_eq(1, start.pT1.pToken.position.row);
assert_eq(1, start.pT1.pToken.position.col);
assert_eq(1, start.pT1.pToken.end_position.row);
assert_eq(1, start.pT1.pToken.end_position.col);
assert(!start.pT1.pA.position.valid);
assert_eq(1, start.pT1.position.row);
assert_eq(1, start.pT1.position.col);
assert_eq(1, start.pT1.end_position.row);
assert_eq(1, start.pT1.end_position.col);
assert_eq(1, start.position.row);
assert_eq(1, start.position.col);
assert_eq(1, start.end_position.row);
assert_eq(1, start.end_position.col);
}
+418
View File
@@ -0,0 +1,418 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include "testutils.h"
int main(int argc, char * argv[])
{
const char * input =
"# 0\n"
"def byte_val() -> byte\n"
"{\n"
" return 0x42;\n"
"}\n"
"\n"
"# 1\n"
"def short_val() -> short\n"
"{\n"
" return 0x4242;\n"
"}\n"
"\n"
"# 2\n"
"def int_val() -> int\n"
"{\n"
" return 0x42424242;\n"
"}\n"
"\n"
"# 3\n"
"def long_val() -> long\n"
"{\n"
" return 0x4242_4242_4242_4242;\n"
"}\n"
"\n"
"# 4\n"
"def ssize_t_val() -> ssize_t\n"
"{\n"
" return 0x42424242;\n"
"}\n"
"\n"
"# 5\n"
"def byte_to_short() -> short\n"
"{\n"
" return byte_val();\n"
"}\n"
"\n"
"# 6\n"
"def byte_to_int() -> int\n"
"{\n"
" return byte_val();\n"
"}\n"
"\n"
"# 7\n"
"def byte_to_long() -> long\n"
"{\n"
" return byte_val();\n"
"}\n"
"\n"
"# 8\n"
"def byte_to_ssize_t() -> ssize_t\n"
"{\n"
" return byte_val();\n"
"}\n"
"\n"
"# 9\n"
"def short_to_byte() -> byte\n"
"{\n"
" return short_val();\n"
"}\n"
"\n"
"# 10\n"
"def short_to_int() -> int\n"
"{\n"
" return short_val();\n"
"}\n"
"\n"
"# 11\n"
"def short_to_long() -> long\n"
"{\n"
" return short_val();\n"
"}\n"
"\n"
"# 12\n"
"def short_to_ssize_t() -> ssize_t\n"
"{\n"
" return short_val();\n"
"}\n"
"\n"
"# 13\n"
"def int_to_byte() -> byte\n"
"{\n"
" return int_val();\n"
"}\n"
"\n"
"# 14\n"
"def int_to_short() -> short\n"
"{\n"
" return int_val();\n"
"}\n"
"\n"
"# 15\n"
"def int_to_long() -> long\n"
"{\n"
" return int_val();\n"
"}\n"
"\n"
"# 16\n"
"def int_to_ssize_t() -> ssize_t\n"
"{\n"
" return int_val();\n"
"}\n"
"\n"
"# 17\n"
"def long_to_byte() -> byte\n"
"{\n"
" return long_val();\n"
"}\n"
"\n"
"# 18\n"
"def long_to_short() -> short\n"
"{\n"
" return long_val();\n"
"}\n"
"\n"
"# 19\n"
"def long_to_int() -> int\n"
"{\n"
" return long_val();\n"
"}\n"
"\n"
"# 20\n"
"def long_to_ssize_t() -> ssize_t\n"
"{\n"
" return long_val();\n"
"}\n"
"\n"
"# 21\n"
"def ssize_t_to_byte() -> byte\n"
"{\n"
" return ssize_t_val();\n"
"}\n"
"\n"
"# 22\n"
"def ssize_t_to_short() -> short\n"
"{\n"
" return ssize_t_val();\n"
"}\n"
"\n"
"# 23\n"
"def ssize_t_to_int() -> int\n"
"{\n"
" return ssize_t_val();\n"
"}\n"
"\n"
"# 24\n"
"def ssize_t_to_long() -> long\n"
"{\n"
" return ssize_t_val();\n"
"}\n"
"\n"
"# 25\n"
"def ubyte_val() -> ubyte\n"
"{\n"
" return 0x42;\n"
"}\n"
"\n"
"# 26\n"
"def ushort_val() -> ushort\n"
"{\n"
" return 0x4242;\n"
"}\n"
"\n"
"# 27\n"
"def uint_val() -> uint\n"
"{\n"
" return 0x42424242;\n"
"}\n"
"\n"
"# 28\n"
"def ulong_val() -> ulong\n"
"{\n"
" return 0x4242_4242_4242_4242;\n"
"}\n"
"\n"
"# 29\n"
"def size_t_val() -> size_t\n"
"{\n"
" return 0x42424242;\n"
"}\n"
"\n"
"# 30\n"
"def ubyte_to_ushort() -> ushort\n"
"{\n"
" return ubyte_val();\n"
"}\n"
"\n"
"# 31\n"
"def ubyte_to_uint() -> uint\n"
"{\n"
" return ubyte_val();\n"
"}\n"
"\n"
"# 32\n"
"def ubyte_to_ulong() -> ulong\n"
"{\n"
" return ubyte_val();\n"
"}\n"
"\n"
"# 33\n"
"def ubyte_to_size_t() -> size_t\n"
"{\n"
" return ubyte_val();\n"
"}\n"
"\n"
"# 34\n"
"def ushort_to_ubyte() -> ubyte\n"
"{\n"
" return ushort_val();\n"
"}\n"
"\n"
"# 35\n"
"def ushort_to_uint() -> uint\n"
"{\n"
" return ushort_val();\n"
"}\n"
"\n"
"# 36\n"
"def ushort_to_ulong() -> ulong\n"
"{\n"
" return ushort_val();\n"
"}\n"
"\n"
"# 37\n"
"def ushort_to_size_t() -> size_t\n"
"{\n"
" return ushort_val();\n"
"}\n"
"\n"
"# 38\n"
"def uint_to_ubyte() -> ubyte\n"
"{\n"
" return uint_val();\n"
"}\n"
"\n"
"# 39\n"
"def uint_to_ushort() -> ushort\n"
"{\n"
" return uint_val();\n"
"}\n"
"\n"
"# 40\n"
"def uint_to_ulong() -> ulong\n"
"{\n"
" return uint_val();\n"
"}\n"
"\n"
"# 41\n"
"def uint_to_size_t() -> size_t\n"
"{\n"
" return uint_val();\n"
"}\n"
"\n"
"# 42\n"
"def ulong_to_ubyte() -> ubyte\n"
"{\n"
" return ulong_val();\n"
"}\n"
"\n"
"# 43\n"
"def ulong_to_ushort() -> ushort\n"
"{\n"
" return ulong_val();\n"
"}\n"
"\n"
"# 44\n"
"def ulong_to_uint() -> uint\n"
"{\n"
" return ulong_val();\n"
"}\n"
"\n"
"# 45\n"
"def ulong_to_size_t() -> size_t\n"
"{\n"
" return ulong_val();\n"
"}\n"
"\n"
"# 46\n"
"def size_t_to_ubyte() -> ubyte\n"
"{\n"
" return size_t_val();\n"
"}\n"
"\n"
"# 47\n"
"def size_t_to_ushort() -> ushort\n"
"{\n"
" return size_t_val();\n"
"}\n"
"\n"
"# 48\n"
"def size_t_to_int() -> int\n"
"{\n"
" return size_t_val();\n"
"}\n"
"\n"
"# 49\n"
"def size_t_to_ulong() -> ulong\n"
"{\n"
" return size_t_val();\n"
"}\n"
"\n"
"# 50\n"
"def main() -> int\n"
"{\n"
" return int_val();\n"
"}\n";
struct
{
const char * name;
p_token_t token;
} expected[] = {
{"byte_val", TOKEN_byte},
{"short_val", TOKEN_short},
{"int_val", TOKEN_int},
{"long_val", TOKEN_long},
{"ssize_t_val", TOKEN_ssize_t},
{"byte_to_short", TOKEN_short},
{"byte_to_int", TOKEN_int},
{"byte_to_long", TOKEN_long},
{"byte_to_ssize_t", TOKEN_ssize_t},
{"short_to_byte", TOKEN_byte},
{"short_to_int", TOKEN_int},
{"short_to_long", TOKEN_long},
{"short_to_ssize_t", TOKEN_ssize_t},
{"int_to_byte", TOKEN_byte},
{"int_to_short", TOKEN_short},
{"int_to_long", TOKEN_long},
{"int_to_ssize_t", TOKEN_ssize_t},
{"long_to_byte", TOKEN_byte},
{"long_to_short", TOKEN_short},
{"long_to_int", TOKEN_int},
{"long_to_ssize_t", TOKEN_ssize_t},
{"ssize_t_to_byte", TOKEN_byte},
{"ssize_t_to_short", TOKEN_short},
{"ssize_t_to_int", TOKEN_int},
{"ssize_t_to_long", TOKEN_long},
{"ubyte_val", TOKEN_ubyte},
{"ushort_val", TOKEN_ushort},
{"uint_val", TOKEN_uint},
{"ulong_val", TOKEN_ulong},
{"size_t_val", TOKEN_size_t},
{"ubyte_to_ushort", TOKEN_ushort},
{"ubyte_to_uint", TOKEN_uint},
{"ubyte_to_ulong", TOKEN_ulong},
{"ubyte_to_size_t", TOKEN_size_t},
{"ushort_to_ubyte", TOKEN_ubyte},
{"ushort_to_uint", TOKEN_uint},
{"ushort_to_ulong", TOKEN_ulong},
{"ushort_to_size_t", TOKEN_size_t},
{"uint_to_ubyte", TOKEN_ubyte},
{"uint_to_ushort", TOKEN_ushort},
{"uint_to_ulong", TOKEN_ulong},
{"uint_to_size_t", TOKEN_size_t},
{"ulong_to_ubyte", TOKEN_ubyte},
{"ulong_to_ushort", TOKEN_ushort},
{"ulong_to_uint", TOKEN_uint},
{"ulong_to_size_t", TOKEN_size_t},
{"size_t_to_ubyte", TOKEN_ubyte},
{"size_t_to_ushort", TOKEN_ushort},
{"size_t_to_int", TOKEN_int},
{"size_t_to_ulong", TOKEN_ulong},
{"main", TOKEN_int},
};
p_context_t context;
p_context_init(&context, (const uint8_t *)input, strlen(input));
size_t result = p_parse(&context);
assert_eq(P_SUCCESS, result);
PModule * pmod = p_result(&context);
PModuleItems * pmis = pmod->pModuleItems;
PFunctionDefinition ** pfds;
size_t n_pfds = 0u;
while (pmis != NULL)
{
PModuleItem * pmi = pmis->pModuleItem;
if (pmi->pFunctionDefinition != NULL)
{
n_pfds++;
}
pmis = pmis->pModuleItems;
}
pfds = (PFunctionDefinition **)malloc(n_pfds * sizeof(PModuleItems *));
pmis = pmod->pModuleItems;
size_t pfd_i = n_pfds;
while (pmis != NULL)
{
PModuleItem * pmi = pmis->pModuleItem;
PFunctionDefinition * pfd = pmi->pFunctionDefinition;
if (pfd != NULL)
{
pfd_i--;
assert(pfd_i < n_pfds);
pfds[pfd_i] = pfd;
}
pmis = pmis->pModuleItems;
}
assert_eq(51, n_pfds);
for (size_t i = 0; i < n_pfds; i++)
{
if (strncmp(expected[i].name, (const char *)pfds[i]->name->pvalue.s, strlen(expected[i].name)) != 0 ||
(expected[i].token != pfds[i]->returntype->pType->pTypeBase->pToken1->token))
{
fprintf(stderr, "Index %lu: expected %s/%u, got %u\n", i, expected[i].name, expected[i].token, pfds[i]->returntype->pType->pTypeBase->pToken1->token);
}
}
free(pfds);
p_free_tree(pmod);
return 0;
}
+408
View File
@@ -0,0 +1,408 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "
# 0
def byte_val() -> byte
{
return 0x42;
}
# 1
def short_val() -> short
{
return 0x4242;
}
# 2
def int_val() -> int
{
return 0x42424242;
}
# 3
def long_val() -> long
{
return 0x4242_4242_4242_4242;
}
# 4
def ssize_t_val() -> ssize_t
{
return 0x42424242;
}
# 5
def byte_to_short() -> short
{
return byte_val();
}
# 6
def byte_to_int() -> int
{
return byte_val();
}
# 7
def byte_to_long() -> long
{
return byte_val();
}
# 8
def byte_to_ssize_t() -> ssize_t
{
return byte_val();
}
# 9
def short_to_byte() -> byte
{
return short_val();
}
# 10
def short_to_int() -> int
{
return short_val();
}
# 11
def short_to_long() -> long
{
return short_val();
}
# 12
def short_to_ssize_t() -> ssize_t
{
return short_val();
}
# 13
def int_to_byte() -> byte
{
return int_val();
}
# 14
def int_to_short() -> short
{
return int_val();
}
# 15
def int_to_long() -> long
{
return int_val();
}
# 16
def int_to_ssize_t() -> ssize_t
{
return int_val();
}
# 17
def long_to_byte() -> byte
{
return long_val();
}
# 18
def long_to_short() -> short
{
return long_val();
}
# 19
def long_to_int() -> int
{
return long_val();
}
# 20
def long_to_ssize_t() -> ssize_t
{
return long_val();
}
# 21
def ssize_t_to_byte() -> byte
{
return ssize_t_val();
}
# 22
def ssize_t_to_short() -> short
{
return ssize_t_val();
}
# 23
def ssize_t_to_int() -> int
{
return ssize_t_val();
}
# 24
def ssize_t_to_long() -> long
{
return ssize_t_val();
}
# 25
def ubyte_val() -> ubyte
{
return 0x42;
}
# 26
def ushort_val() -> ushort
{
return 0x4242;
}
# 27
def uint_val() -> uint
{
return 0x42424242;
}
# 28
def ulong_val() -> ulong
{
return 0x4242_4242_4242_4242;
}
# 29
def size_t_val() -> size_t
{
return 0x42424242;
}
# 30
def ubyte_to_ushort() -> ushort
{
return ubyte_val();
}
# 31
def ubyte_to_uint() -> uint
{
return ubyte_val();
}
# 32
def ubyte_to_ulong() -> ulong
{
return ubyte_val();
}
# 33
def ubyte_to_size_t() -> size_t
{
return ubyte_val();
}
# 34
def ushort_to_ubyte() -> ubyte
{
return ushort_val();
}
# 35
def ushort_to_uint() -> uint
{
return ushort_val();
}
# 36
def ushort_to_ulong() -> ulong
{
return ushort_val();
}
# 37
def ushort_to_size_t() -> size_t
{
return ushort_val();
}
# 38
def uint_to_ubyte() -> ubyte
{
return uint_val();
}
# 39
def uint_to_ushort() -> ushort
{
return uint_val();
}
# 40
def uint_to_ulong() -> ulong
{
return uint_val();
}
# 41
def uint_to_size_t() -> size_t
{
return uint_val();
}
# 42
def ulong_to_ubyte() -> ubyte
{
return ulong_val();
}
# 43
def ulong_to_ushort() -> ushort
{
return ulong_val();
}
# 44
def ulong_to_uint() -> uint
{
return ulong_val();
}
# 45
def ulong_to_size_t() -> size_t
{
return ulong_val();
}
# 46
def size_t_to_ubyte() -> ubyte
{
return size_t_val();
}
# 47
def size_t_to_ushort() -> ushort
{
return size_t_val();
}
# 48
def size_t_to_int() -> int
{
return size_t_val();
}
# 49
def size_t_to_ulong() -> ulong
{
return size_t_val();
}
# 50
def main() -> int
{
return int_val();
}
";
struct Expected
{
string name;
p_token_t token;
}
Expected[] expected = [
Expected("byte_val", TOKEN_byte),
Expected("short_val", TOKEN_short),
Expected("int_val", TOKEN_int),
Expected("long_val", TOKEN_long),
Expected("ssize_t_val", TOKEN_ssize_t),
Expected("byte_to_short", TOKEN_short),
Expected("byte_to_int", TOKEN_int),
Expected("byte_to_long", TOKEN_long),
Expected("byte_to_ssize_t", TOKEN_ssize_t),
Expected("short_to_byte", TOKEN_byte),
Expected("short_to_int", TOKEN_int),
Expected("short_to_long", TOKEN_long),
Expected("short_to_ssize_t", TOKEN_ssize_t),
Expected("int_to_byte", TOKEN_byte),
Expected("int_to_short", TOKEN_short),
Expected("int_to_long", TOKEN_long),
Expected("int_to_ssize_t", TOKEN_ssize_t),
Expected("long_to_byte", TOKEN_byte),
Expected("long_to_short", TOKEN_short),
Expected("long_to_int", TOKEN_int),
Expected("long_to_ssize_t", TOKEN_ssize_t),
Expected("ssize_t_to_byte", TOKEN_byte),
Expected("ssize_t_to_short", TOKEN_short),
Expected("ssize_t_to_int", TOKEN_int),
Expected("ssize_t_to_long", TOKEN_long),
Expected("ubyte_val", TOKEN_ubyte),
Expected("ushort_val", TOKEN_ushort),
Expected("uint_val", TOKEN_uint),
Expected("ulong_val", TOKEN_ulong),
Expected("size_t_val", TOKEN_size_t),
Expected("ubyte_to_ushort", TOKEN_ushort),
Expected("ubyte_to_uint", TOKEN_uint),
Expected("ubyte_to_ulong", TOKEN_ulong),
Expected("ubyte_to_size_t", TOKEN_size_t),
Expected("ushort_to_ubyte", TOKEN_ubyte),
Expected("ushort_to_uint", TOKEN_uint),
Expected("ushort_to_ulong", TOKEN_ulong),
Expected("ushort_to_size_t", TOKEN_size_t),
Expected("uint_to_ubyte", TOKEN_ubyte),
Expected("uint_to_ushort", TOKEN_ushort),
Expected("uint_to_ulong", TOKEN_ulong),
Expected("uint_to_size_t", TOKEN_size_t),
Expected("ulong_to_ubyte", TOKEN_ubyte),
Expected("ulong_to_ushort", TOKEN_ushort),
Expected("ulong_to_uint", TOKEN_uint),
Expected("ulong_to_size_t", TOKEN_size_t),
Expected("size_t_to_ubyte", TOKEN_ubyte),
Expected("size_t_to_ushort", TOKEN_ushort),
Expected("size_t_to_int", TOKEN_int),
Expected("size_t_to_ulong", TOKEN_ulong),
Expected("main", TOKEN_int),
];
p_context_t context;
p_context_init(&context, input);
size_t result = p_parse(&context);
assert_eq(P_SUCCESS, result);
PModule * pmod = p_result(&context);
PModuleItems * pmis = pmod.pModuleItems;
PFunctionDefinition *[] pfds;
while (pmis !is null)
{
PModuleItem * pmi = pmis.pModuleItem;
if (pmi is null)
{
stderr.writeln("pmi is null!!!?");
assert(0);
}
PFunctionDefinition * pfd = pmi.pFunctionDefinition;
if (pfd !is null)
{
pfds = [pfd] ~ pfds;
}
pmis = pmis.pModuleItems;
}
assert_eq(51, pfds.length);
for (size_t i = 0; i < pfds.length; i++)
{
if ((expected[i].name != pfds[i].name.pvalue.s) ||
(expected[i].token != pfds[i].returntype.pType.pTypeBase.pToken1.token))
{
stderr.writeln("Index ", i, ": expected ", expected[i].name, "/", expected[i].token, ", got ", pfds[i].name.pvalue.s, "/", pfds[i].returntype.pType.pTypeBase.pToken1.token);
}
}
}
@@ -33,12 +33,16 @@ int main()
assert_eq(22, itemsmore->pItem->pToken1->pvalue); assert_eq(22, itemsmore->pItem->pToken1->pvalue);
assert(itemsmore->pItemsMore == NULL); assert(itemsmore->pItemsMore == NULL);
p_free_tree(start);
input = ""; input = "";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context)); assert_eq(P_SUCCESS, p_parse(&context));
start = p_result(&context); start = p_result(&context);
assert(start->pItems == NULL); assert(start->pItems == NULL);
p_free_tree(start);
input = "2 1"; input = "2 1";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context)); assert_eq(P_SUCCESS, p_parse(&context));
@@ -51,5 +55,7 @@ int main()
assert(start->pItems->pItem->pDual->pTwo2 == NULL); assert(start->pItems->pItem->pDual->pTwo2 == NULL);
assert(start->pItems->pItem->pDual->pOne1 == NULL); assert(start->pItems->pItem->pDual->pOne1 == NULL);
p_free_tree(start);
return 0; return 0;
} }
File renamed without changes.
+88
View File
@@ -0,0 +1,88 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "abbccc";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert_eq(1, start->pT1->pToken->position.row);
assert_eq(1, start->pT1->pToken->position.col);
assert_eq(1, start->pT1->pToken->end_position.row);
assert_eq(1, start->pT1->pToken->end_position.col);
assert_eq(1, start->pT1->position.row);
assert_eq(1, start->pT1->position.col);
assert_eq(1, start->pT1->end_position.row);
assert_eq(1, start->pT1->end_position.col);
assert_eq(1, start->pT2->pToken->position.row);
assert_eq(2, start->pT2->pToken->position.col);
assert_eq(1, start->pT2->pToken->end_position.row);
assert_eq(3, start->pT2->pToken->end_position.col);
assert_eq(1, start->pT2->position.row);
assert_eq(2, start->pT2->position.col);
assert_eq(1, start->pT2->end_position.row);
assert_eq(3, start->pT2->end_position.col);
assert_eq(1, start->pT3->pToken->position.row);
assert_eq(4, start->pT3->pToken->position.col);
assert_eq(1, start->pT3->pToken->end_position.row);
assert_eq(6, start->pT3->pToken->end_position.col);
assert_eq(1, start->pT3->position.row);
assert_eq(4, start->pT3->position.col);
assert_eq(1, start->pT3->end_position.row);
assert_eq(6, start->pT3->end_position.col);
assert_eq(1, start->position.row);
assert_eq(1, start->position.col);
assert_eq(1, start->end_position.row);
assert_eq(6, start->end_position.col);
p_free_tree(start);
input = "\n\n bb\nc\ncc\n\n a";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert_eq(3, start->pT1->pToken->position.row);
assert_eq(3, start->pT1->pToken->position.col);
assert_eq(3, start->pT1->pToken->end_position.row);
assert_eq(4, start->pT1->pToken->end_position.col);
assert_eq(3, start->pT1->position.row);
assert_eq(3, start->pT1->position.col);
assert_eq(3, start->pT1->end_position.row);
assert_eq(4, start->pT1->end_position.col);
assert_eq(4, start->pT2->pToken->position.row);
assert_eq(1, start->pT2->pToken->position.col);
assert_eq(5, start->pT2->pToken->end_position.row);
assert_eq(2, start->pT2->pToken->end_position.col);
assert_eq(4, start->pT2->position.row);
assert_eq(1, start->pT2->position.col);
assert_eq(5, start->pT2->end_position.row);
assert_eq(2, start->pT2->end_position.col);
assert_eq(7, start->pT3->pToken->position.row);
assert_eq(6, start->pT3->pToken->position.col);
assert_eq(7, start->pT3->pToken->end_position.row);
assert_eq(6, start->pT3->pToken->end_position.col);
assert_eq(7, start->pT3->position.row);
assert_eq(6, start->pT3->position.col);
assert_eq(7, start->pT3->end_position.row);
assert_eq(6, start->pT3->end_position.col);
assert_eq(3, start->position.row);
assert_eq(3, start->position.col);
assert_eq(7, start->end_position.row);
assert_eq(6, start->end_position.col);
p_free_tree(start);
return 0;
}
+86
View File
@@ -0,0 +1,86 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "abbccc";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert_eq(1, start.pT1.pToken.position.row);
assert_eq(1, start.pT1.pToken.position.col);
assert_eq(1, start.pT1.pToken.end_position.row);
assert_eq(1, start.pT1.pToken.end_position.col);
assert_eq(1, start.pT1.position.row);
assert_eq(1, start.pT1.position.col);
assert_eq(1, start.pT1.end_position.row);
assert_eq(1, start.pT1.end_position.col);
assert_eq(1, start.pT2.pToken.position.row);
assert_eq(2, start.pT2.pToken.position.col);
assert_eq(1, start.pT2.pToken.end_position.row);
assert_eq(3, start.pT2.pToken.end_position.col);
assert_eq(1, start.pT2.position.row);
assert_eq(2, start.pT2.position.col);
assert_eq(1, start.pT2.end_position.row);
assert_eq(3, start.pT2.end_position.col);
assert_eq(1, start.pT3.pToken.position.row);
assert_eq(4, start.pT3.pToken.position.col);
assert_eq(1, start.pT3.pToken.end_position.row);
assert_eq(6, start.pT3.pToken.end_position.col);
assert_eq(1, start.pT3.position.row);
assert_eq(4, start.pT3.position.col);
assert_eq(1, start.pT3.end_position.row);
assert_eq(6, start.pT3.end_position.col);
assert_eq(1, start.position.row);
assert_eq(1, start.position.col);
assert_eq(1, start.end_position.row);
assert_eq(6, start.end_position.col);
input = "\n\n bb\nc\ncc\n\n a";
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
start = p_result(&context);
assert_eq(3, start.pT1.pToken.position.row);
assert_eq(3, start.pT1.pToken.position.col);
assert_eq(3, start.pT1.pToken.end_position.row);
assert_eq(4, start.pT1.pToken.end_position.col);
assert_eq(3, start.pT1.position.row);
assert_eq(3, start.pT1.position.col);
assert_eq(3, start.pT1.end_position.row);
assert_eq(4, start.pT1.end_position.col);
assert_eq(4, start.pT2.pToken.position.row);
assert_eq(1, start.pT2.pToken.position.col);
assert_eq(5, start.pT2.pToken.end_position.row);
assert_eq(2, start.pT2.pToken.end_position.col);
assert_eq(4, start.pT2.position.row);
assert_eq(1, start.pT2.position.col);
assert_eq(5, start.pT2.end_position.row);
assert_eq(2, start.pT2.end_position.col);
assert_eq(7, start.pT3.pToken.position.row);
assert_eq(6, start.pT3.pToken.position.col);
assert_eq(7, start.pT3.pToken.end_position.row);
assert_eq(6, start.pT3.pToken.end_position.col);
assert_eq(7, start.pT3.position.row);
assert_eq(6, start.pT3.position.col);
assert_eq(7, start.pT3.end_position.row);
assert_eq(6, start.pT3.end_position.col);
assert_eq(3, start.position.row);
assert_eq(3, start.position.col);
assert_eq(7, start.end_position.row);
assert_eq(6, start.end_position.col);
}
+20 -2
View File
@@ -14,10 +14,28 @@ void assert_eq_size_t_i(size_t expected, size_t actual, char const * file, size_
} }
} }
void assert_ne_size_t_i(size_t expected, size_t actual, char const * file, size_t line)
{
if (expected == actual)
{
fprintf(stderr, "%s:%lu: expected not %lu, got %lu\n", file, line, expected, actual);
assert(false);
}
}
void assert_not_null_i(void * ptr, char const * file, size_t line)
{
if (ptr == NULL)
{
fprintf(stderr, "%s:%lu: expected not NULL\n", file, line);
assert(false);
}
}
void str_init(str_t * str, char const * cs) void str_init(str_t * str, char const * cs)
{ {
size_t length = strlen(cs); size_t length = strlen(cs);
str->cs = malloc(length + 1u); str->cs = (char *)malloc(length + 1u);
strcpy(str->cs, cs); strcpy(str->cs, cs);
} }
@@ -25,7 +43,7 @@ void str_append(str_t * str, char const * cs)
{ {
size_t length = strlen(str->cs); size_t length = strlen(str->cs);
size_t length2 = strlen(cs); size_t length2 = strlen(cs);
char * new_cs = malloc(length + length2 + 1u); char * new_cs = (char *)malloc(length + length2 + 1u);
memcpy(new_cs, str->cs, length); memcpy(new_cs, str->cs, length);
strcpy(&new_cs[length], cs); strcpy(&new_cs[length], cs);
free(str->cs); free(str->cs);
+10
View File
@@ -5,6 +5,16 @@ void assert_eq_size_t_i(size_t expected, size_t actual, char const * file, size_
#define assert_eq(expected, actual) \ #define assert_eq(expected, actual) \
assert_eq_size_t_i(expected, actual, __FILE__, __LINE__) assert_eq_size_t_i(expected, actual, __FILE__, __LINE__)
void assert_ne_size_t_i(size_t expected, size_t actual, char const * file, size_t line);
#define assert_ne(expected, actual) \
assert_ne_size_t_i(expected, actual, __FILE__, __LINE__)
void assert_not_null_i(void * ptr, char const * file, size_t line);
#define assert_not_null(ptr) \
assert_not_null_i(ptr, __FILE__, __LINE__)
typedef struct typedef struct
{ {
char * cs; char * cs;
+151
View File
@@ -0,0 +1,151 @@
tree;
tree_prefix P;
<<header
#include <stdio.h>
#include <stdint.h>
typedef union
{
uint64_t i64;
const uint8_t * s;
double dou;
} TokenVal;
>>
ptype TokenVal;
# Keywords.
token byte;
token def;
token int;
token long;
token module;
token return;
token short;
token size_t;
token ssize_t;
token ubyte;
token uint;
token ulong;
token ushort;
# Symbols.
token arrow /->/;
token comma /,/;
token lbrace /\{/;
token lparen /\(/;
token rbrace /\}/;
token rparen /\)/;
token semicolon /;/;
# Integer literals.
token hex_int_l /0[xX][0-9a-fA-F][0-9a-fA-F_]*/ <<
$$.i64 = 64u;
>>
# Identifier.
token ident /\$?[a-zA-Z_][a-zA-Z_0-9]*\??/ <<
$$.s = match;
$mode(default);
return $token(ident);
>>
# Comments.
drop /#.*/;
# Whitespace.
drop /[ \r\n]*/;
start Module;
# Assignment operators - right associative
Expression -> Expression_Or:exp0;
# Logical OR operator - left associative
Expression_Or -> Expression_And:exp0;
# Logical AND operator - left associative
Expression_And -> Expression_Comp:exp0;
# Equality operators - left associative
Expression_Comp -> Expression_Relational:exp0;
# Relational operators - left associative
Expression_Relational -> Expression_REMatch:exp0;
# Regular expression - left associative
Expression_REMatch -> Expression_BinOr:exp0;
# Binary OR operator - left associative
Expression_BinOr -> Expression_Xor:exp0;
# Binary XOR operator - left associative
Expression_Xor -> Expression_BinAnd:exp0;
# Binary AND operator - left associative
Expression_BinAnd -> Expression_BitShift:exp0;
# Bit shift operators - left associative
Expression_BitShift -> Expression_Plus:exp0;
# Add/subtract operators - left associative
Expression_Plus -> Expression_Mul:exp0;
# Multiplication/divide/modulus operators - left associative
Expression_Mul -> Expression_Range:exp0;
# Range construction operators - left associative
Expression_Range -> Expression_UnaryPrefix:exp0;
# Unary prefix operators
Expression_UnaryPrefix -> Expression_Dot:exp0;
# Postfix operators
Expression_Dot -> Expression_Ident:exp0;
Expression_Dot -> Expression_Dot:exp1 lparen rparen;
# Literals, identifiers, and parenthesized expressions
Expression_Ident -> Literal;
Expression_Ident -> ident;
FunctionDefinition -> def ident:name lparen FunctionParameterList?:parameters rparen FunctionReturnType?:returntype lbrace Statements rbrace;
FunctionParameterList -> ident:name Type:type FunctionParameterListMore?:more;
FunctionParameterListMore -> comma ident:name Type:type FunctionParameterListMore?:more;
FunctionReturnType -> arrow Type;
Literal -> LiteralInteger;
LiteralInteger -> hex_int_l;
Module -> ModuleStatement? ModuleItems;
ModuleItem -> FunctionDefinition;
ModuleItems -> ;
ModuleItems -> ModuleItems ModuleItem;
ModulePath -> ident;
ModuleStatement -> module ModulePath semicolon;
ReturnStatement -> return Expression?:exp0 semicolon;
Statements -> ;
Statements -> Statements Statement;
Statement -> Expression semicolon;
Statement -> ReturnStatement;
Type -> TypeBase;
TypeBase -> byte;
TypeBase -> ubyte;
TypeBase -> short;
TypeBase -> ushort;
TypeBase -> int;
TypeBase -> uint;
TypeBase -> long;
TypeBase -> ulong;
TypeBase -> size_t;
TypeBase -> ssize_t;
+177
View File
@@ -0,0 +1,177 @@
tree;
tree_prefix P;
<<
import std.bigint;
private string stringvalue;
union TokenVal
{
BigInt bi;
string s;
double dou;
}
>>
ptype TokenVal;
# Keywords.
token byte;
token def;
token int;
token long;
token module;
token return;
token short;
token size_t;
token ssize_t;
token ubyte;
token uint;
token ulong;
token ushort;
# Symbols.
token arrow /->/;
token comma /,/;
token lbrace /\{/;
token lparen /\(/;
token rbrace /\}/;
token rparen /\)/;
token semicolon /;/;
# Integer literals.
token hex_int_l /0[xX][0-9a-fA-F][0-9a-fA-F_]*/ <<
$$.bi = BigInt(match[0..3]);
foreach (c; match[3..$])
{
if (('0' <= c) && (c <= '9'))
{
$$.bi *= 0x10;
$$.bi += (c - '0');
}
if (('a' <= c) && (c <= 'f'))
{
$$.bi *= 0x10;
$$.bi += (c - 'a' + 10);
}
if (('A' <= c) && (c <= 'F'))
{
$$.bi *= 0x10;
$$.bi += (c - 'A' + 10);
}
}
>>
# Identifier.
token ident /\$?[a-zA-Z_][a-zA-Z_0-9]*\??/ <<
if (match[0] == '$')
{
$$.s = match[1..$];
}
else
{
$$.s = match;
}
$mode(default);
return $token(ident);
>>
# Comments.
drop /#.*/;
# Whitespace.
drop /[ \r\n]*/;
start Module;
# Assignment operators - right associative
Expression -> Expression_Or:exp0;
# Logical OR operator - left associative
Expression_Or -> Expression_And:exp0;
# Logical AND operator - left associative
Expression_And -> Expression_Comp:exp0;
# Equality operators - left associative
Expression_Comp -> Expression_Relational:exp0;
# Relational operators - left associative
Expression_Relational -> Expression_REMatch:exp0;
# Regular expression - left associative
Expression_REMatch -> Expression_BinOr:exp0;
# Binary OR operator - left associative
Expression_BinOr -> Expression_Xor:exp0;
# Binary XOR operator - left associative
Expression_Xor -> Expression_BinAnd:exp0;
# Binary AND operator - left associative
Expression_BinAnd -> Expression_BitShift:exp0;
# Bit shift operators - left associative
Expression_BitShift -> Expression_Plus:exp0;
# Add/subtract operators - left associative
Expression_Plus -> Expression_Mul:exp0;
# Multiplication/divide/modulus operators - left associative
Expression_Mul -> Expression_Range:exp0;
# Range construction operators - left associative
Expression_Range -> Expression_UnaryPrefix:exp0;
# Unary prefix operators
Expression_UnaryPrefix -> Expression_Dot:exp0;
# Postfix operators
Expression_Dot -> Expression_Ident:exp0;
Expression_Dot -> Expression_Dot:exp1 lparen rparen;
# Literals, identifiers, and parenthesized expressions
Expression_Ident -> Literal;
Expression_Ident -> ident;
FunctionDefinition -> def ident:name lparen FunctionParameterList?:parameters rparen FunctionReturnType?:returntype lbrace Statements rbrace;
FunctionParameterList -> ident:name Type:type FunctionParameterListMore?:more;
FunctionParameterListMore -> comma ident:name Type:type FunctionParameterListMore?:more;
FunctionReturnType -> arrow Type;
Literal -> LiteralInteger;
LiteralInteger -> hex_int_l;
Module -> ModuleStatement? ModuleItems;
ModuleItem -> FunctionDefinition;
ModuleItems -> ;
ModuleItems -> ModuleItems ModuleItem;
ModulePath -> ident;
ModuleStatement -> module ModulePath semicolon;
ReturnStatement -> return Expression?:exp0 semicolon;
Statements -> ;
Statements -> Statements Statement;
Statement -> Expression semicolon;
Statement -> ReturnStatement;
Type -> TypeBase;
TypeBase -> byte;
TypeBase -> ubyte;
TypeBase -> short;
TypeBase -> ushort;
TypeBase -> int;
TypeBase -> uint;
TypeBase -> long;
TypeBase -> ulong;
TypeBase -> size_t;
TypeBase -> ssize_t;