10 Commits
Author SHA1 Message Date
josh cb914e8bb0 v4.5.0 2026-06-29 23:49:01 -04:00
josh ece95dd7e0 Fix propane.vim keyword detection 2026-06-29 23:48:43 -04:00
josh 232af68081 Attempt to autodetect target language (D/C++) in extra/vim/syntax/propane.vim 2026-06-29 23:02:57 -04:00
josh 23834bef82 Update keyword list in extra/vim/syntax/propane.vim 2026-06-29 23:02:05 -04:00
josh 7b698d7b31 Add noline grammar statement to skip emitting #line directives 2026-06-29 22:41:07 -04:00
josh 45843da0df Fix #line reset directives 2026-06-29 22:21:45 -04:00
josh f27a8688f7 Add CHANGELOG for v4.4.0 2026-06-29 22:15:01 -04:00
josh c459507612 v4.4.0 2026-06-22 23:46:32 -04:00
josh feacbc907d Update gems 2026-06-22 23:46:06 -04:00
josh 5ac3ad1655 Add p_value_get() / p_value_get_XXX() accessors
Allow user code to access specific pvalue values, especially when
multiple ptypes are in use.
2026-06-22 22:12:29 -04:00
13 changed files with 235 additions and 10 deletions

No files matched your search

+19
View File
@@ -1,3 +1,22 @@
## v4.5.0
### New Features
- Add `noline` grammar statement to skip emitting `#line` directives
- Attempt to autodetect target language (D/C++) in extra/vim/syntax/propane.vim
### Fixes
- Fix #line reset directives
- Update keyword list in extra/vim/syntax/propane.vim
- Fix propane.vim keyword detection
## v4.4.0
### New Features
- Add p_value_get() / p_value_get_XXX() accessors
## v4.3.0 ## v4.3.0
### New Features ### New Features
+2 -2
View File
@@ -6,7 +6,7 @@ GEM
diff-lcs (1.6.2) diff-lcs (1.6.2)
docile (1.4.1) docile (1.4.1)
erb (6.0.4) erb (6.0.4)
psych (5.3.1) psych (5.4.0)
date date
stringio stringio
rake (13.4.2) rake (13.4.2)
@@ -51,4 +51,4 @@ DEPENDENCIES
syntax syntax
BUNDLED WITH BUNDLED WITH
4.0.11 4.0.14
+8
View File
@@ -94,6 +94,14 @@ public <%= @grammar.prefix %>value_t <%= @grammar.prefix %>value<%= name == "def
return <%= @grammar.prefix %>value_t(v_<%= name %>: v); return <%= @grammar.prefix %>value_t(v_<%= name %>: v);
} }
<% end %> <% end %>
/** Parser value accessor(s). */
<% @grammar.ptypes.each do |name, typestring| %>
public <%= typestring %> <%= @grammar.prefix %>value_get<%= name == "default" ? "" : "_#{name}" %>(<%= @grammar.prefix %>value_t * pvalue)
{
return pvalue.v_<%= name %>;
}
<% end %>
<% end %> <% end %>
<% if @grammar.tree %> <% if @grammar.tree %>
+8
View File
@@ -77,6 +77,14 @@ static inline <%= @grammar.prefix %>value_t <%= @grammar.prefix %>value<%= name
return (<%= @grammar.prefix %>value_t){.v_<%= name %> = v}; return (<%= @grammar.prefix %>value_t){.v_<%= name %> = v};
} }
<% end %> <% end %>
/** Parser value accessor(s). */
<% @grammar.ptypes.each do |name, typestring| %>
static inline <%= typestring %> <%= @grammar.prefix %>value_get<%= name == "default" ? "" : "_#{name}" %>(<%= @grammar.prefix %>value_t const * pvalue)
{
return pvalue->v_<%= name %>;
}
<% end %>
<% end %> <% end %>
<% if @grammar.tree %> <% if @grammar.tree %>
+46 -5
View File
@@ -383,6 +383,27 @@ module proj.parser;
If a module statement is not present, then the generated D module will not If a module statement is not present, then the generated D module will not
contain a module statement and the default module name will be used. contain a module statement and the default module name will be used.
##> `noline` statement - disabling `#line` directives
By default, Propane emits `#line` directives into the generated output around
user code blocks.
These directives instruct the compiler to report any warnings or errors in the
user code using the file name and line number of the original grammar file,
rather than the generated output file.
This makes it easier to locate the source of a compiler diagnostic in the
grammar file.
The `noline` statement disables the emission of these `#line` directives.
```
noline;
```
When the `noline` statement is present, user code blocks are copied to the
generated output without any surrounding `#line` directives.
This can be useful when debugging the generated parser itself, or when the
`#line` directives interfere with other tooling.
##> `on_tree_node` statement - custom initialization of a token tree node ##> `on_tree_node` statement - custom initialization of a token tree node
The `on_token_node` statement can be used to provide code that initializes The `on_token_node` statement can be used to provide code that initializes
@@ -1082,6 +1103,9 @@ The `p_token_info_t` structure contains the following fields:
* `length` (`size_t`) holds the number of input bytes used by 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 * `token` (`p_token_t`) holds the token ID of the lexed token
* `pvalue` (`p_value_t`) holds the parser value associated with the token. * `pvalue` (`p_value_t`) holds the parser value associated with the token.
The actual user value can be extracted with `p_value_get(&token_info.pvalue)`
for the default value or `p_value_get_XXX(&token_info.pvalue)` for named
`ptype` values.
### Tree Node Types ### Tree Node Types
@@ -1404,18 +1428,35 @@ In this case, Propane will free a `Statement` tree structure returned by the
### `p_value` ### `p_value`
When tree generation mode is not active, the `p_value_t` union can hold one of The `p_value(v)` function builds an instance of the `p_value_t` with the
several different possible value types.
The `p_value(v)` function returns an instance of the `p_value_t` with the
default member set to the value of `v`. default member set to the value of `v`.
A `p_value_XXX(v)` function is set for each user-defined `ptype` name with A `p_value_XXX(v)` function is defined for each user-defined `ptype` name with
the user-given name in place of the `XXX`. the user-given name in place of the `XXX`.
These functions are useful for custom lexer functions which need to return a These functions are useful for custom lexer functions which need to return a
parser value corresponding to a lexed token. parser value corresponding to a lexed token.
They are especially useful when tree generation mode is not active.
In that case, the `p_value_t` union can hold one of several different possible
value types.
### `p_value_get`
The `p_value_get()` accessor functions can be used to extract a user value
from a `p_value_t` union.
The `p_value_get(pvalue)` function accepts a pointer to a `p_value_t` and
returns the value of its default member.
A `p_value_get_XXX(pvalue)` function is generated for each user-defined `ptype`
name with the user-given name in place of the `XXX`, returning the value of the
corresponding member.
These functions are the counterpart to `p_value()` constructor functions and
are useful for reading the parser value associated with a lexed token, for
example `p_value_get(&token_info.pvalue)`.
##> Data ##> Data
### `p_token_names` ### `p_token_names`
+12 -1
View File
@@ -8,7 +8,11 @@ if exists("b:current_syntax")
endif endif
if !exists("b:propane_subtype") if !exists("b:propane_subtype")
if search('\<import\s\+\%(std\|core\)\.', 'nw') > 0
let b:propane_subtype = "d" let b:propane_subtype = "d"
else
let b:propane_subtype = "cpp"
endif
endif endif
exe "syn include @propaneTarget syntax/".b:propane_subtype.".vim" exe "syn include @propaneTarget syntax/".b:propane_subtype.".vim"
@@ -20,12 +24,19 @@ syn match propaneOperator "->"
syn match propaneFieldAlias ":[a-zA-Z0-9_]\+" contains=propaneFieldOperator syn match propaneFieldAlias ":[a-zA-Z0-9_]\+" contains=propaneFieldOperator
syn match propaneFieldOperator ":" contained syn match propaneFieldOperator ":" contained
syn match propaneOperator "?" syn match propaneOperator "?"
syn keyword propaneKeyword drop free_token_node free_token_user_fields module prefix ptype start token token_user_fields tokenid tree tree_prefix tree_suffix " Keywords that introduce a user-defined name. The name is consumed by
" propaneName via nextgroup so a name matching a keyword (e.g. 'token start')
" is not highlighted as a keyword. These must be a match (not syn keyword)
" because a syn keyword always wins over a contained nextgroup match.
syn match propaneNameDecl "\<\%(tokenid\|token\|lex_fn\|module\|start\|tree_prefix\|tree_suffix\)\>" nextgroup=propaneName skipwhite
syn match propaneName "\<\h\w*\>" contained
syn match propaneKeyword "\<\%(context_user_fields\|drop\|free_token_node\|noline\|on_token_node\|prefix\|ptype\|token_user_fields\|tree\)\>"
syn region propaneRegex start="/" end="/" skip="\v\\\\|\\/" syn region propaneRegex start="/" end="/" skip="\v\\\\|\\/"
hi def link propaneComment Comment hi def link propaneComment Comment
hi def link propaneKeyword Keyword hi def link propaneKeyword Keyword
hi def link propaneNameDecl Keyword
hi def link propaneRegex String hi def link propaneRegex String
hi def link propaneOperator Operator hi def link propaneOperator Operator
hi def link propaneFieldOperator Operator hi def link propaneFieldOperator Operator
+1 -1
View File
@@ -40,7 +40,7 @@ class Propane
erb = ERB.new(template, trim_mode: "<>") erb = ERB.new(template, trim_mode: "<>")
result = erb.result(binding.clone).lines.each_with_index.map do |line, i| result = erb.result(binding.clone).lines.each_with_index.map do |line, i|
if line == "#linereset\n" if line == "#linereset\n"
"#line #{i + 2}\n" %[#line #{i + 2} "#{output_file}"\n]
else else
line line
end end
+12
View File
@@ -88,6 +88,7 @@ class Propane
elsif parse_rule_statement! elsif parse_rule_statement!
elsif parse_code_block_statement! elsif parse_code_block_statement!
elsif parse_prefix_statement! elsif parse_prefix_statement!
elsif parse_noline_statement!
else else
if @input.size > 25 if @input.size > 25
@input = @input.slice(0..20) + "..." @input = @input.slice(0..20) + "..."
@@ -306,11 +307,13 @@ class Propane
if md = consume!(/<<([a-z]*)(.*?)>>\n/m) if md = consume!(/<<([a-z]*)(.*?)>>\n/m)
name, code = md[1..2] name, code = md[1..2]
code = code.chomp code = code.chomp
unless @noline
if code.start_with?("\n") if code.start_with?("\n")
code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n] code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n]
else else
code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n] code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n]
end end
end
if @code_blocks[name] if @code_blocks[name]
@code_blocks[name] += code @code_blocks[name] += code
else else
@@ -328,6 +331,13 @@ class Propane
end end
end end
def parse_noline_statement!
if md = consume!(/noline\s*;/)
@noline = true
true
end
end
def parse_pattern! def parse_pattern!
if md = consume!(%r{/}) if md = consume!(%r{/})
pattern = "" pattern = ""
@@ -352,11 +362,13 @@ class Propane
def parse_code_block! def parse_code_block!
if md = consume!(/<<(.*?)>>\n/m) if md = consume!(/<<(.*?)>>\n/m)
code = md[1].chomp code = md[1].chomp
unless @noline
if code.start_with?("\n") if code.start_with?("\n")
code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n] code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n]
else else
code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n] code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n]
end end
end
code code
end end
end end
+1 -1
View File
@@ -1,3 +1,3 @@
class Propane class Propane
VERSION = "4.3.0" VERSION = "4.5.0"
end end
+35
View File
@@ -133,6 +133,41 @@ EOF
expect(o.code).to eq %[#line 7 "test.propane"\n writeln("Hello there");\n#linereset\n] expect(o.code).to eq %[#line 7 "test.propane"\n writeln("Hello there");\n#linereset\n]
end end
it "does not emit #line directives with noline statement" do
input = <<EOF
noline;
token code1 <<
a = b;
return c;
>>
token code2 <<
writeln("Hello there");
>>
tokenid token_with_no_pattern;
prefix myparser_;
EOF
grammar = Grammar.new(input, "test.propane")
expect(grammar.prefix).to eq "myparser_"
o = grammar.tokens.find {|token| token.name == "code1"}
expect(o).to_not be_nil
o = grammar.patterns.find {|pattern| pattern.token == o}
expect(o).to_not be_nil
expect(o.code).to eq %[\n a = b;\n return c;]
o = grammar.tokens.find {|token| token.name == "code2"}
expect(o).to_not be_nil
o = grammar.patterns.find {|pattern| pattern.token == o}
expect(o).to_not be_nil
expect(o.code).to eq %[\n writeln("Hello there");]
end
it "supports mode labels" do it "supports mode labels" do
input = <<EOF input = <<EOF
token a; token a;
+33
View File
@@ -1781,6 +1781,39 @@ EOF
results = run_test(language: language) results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "provides accessor functions to extract user values from a parser value" do
if language == "d"
write_grammar <<EOF
ptype int;
ptype float = float;
ptype string = string;
drop /\\s+/;
token num /\\d+/ << $$ = 42; >>
token flt (float) /f/ << $$ = 1.5; >>
token str (string) /s/ << $$ = "hello"; >>
Start -> num flt str;
EOF
else
write_grammar <<EOF
ptype int;
ptype float = float;
ptype string = char *;
drop /\\s+/;
token num /\\d+/ << $$ = 42; >>
token flt (float) /f/ << $$ = 1.5; >>
token str (string) /s/ << $$ = (char *)"hello"; >>
Start -> num flt str;
EOF
end
run_propane(language: language)
compile("spec/test_value_accessors.#{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
+29
View File
@@ -0,0 +1,29 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
int main()
{
p_token_info_t token_info;
char const * input = "42 f s";
p_context_t * context;
context = p_context_new((uint8_t const *)input, strlen(input));
/* Default ptype value extracted with p_value_get(). */
assert(p_lex(context, &token_info) == P_SUCCESS);
assert(token_info.token == TOKEN_num);
assert(p_value_get(&token_info.pvalue) == 42);
/* Named ptype values extracted with p_value_get_XXX(). */
assert(p_lex(context, &token_info) == P_SUCCESS);
assert(token_info.token == TOKEN_flt);
assert(p_value_get_float(&token_info.pvalue) == 1.5);
assert(p_lex(context, &token_info) == P_SUCCESS);
assert(token_info.token == TOKEN_str);
assert(strcmp(p_value_get_string(&token_info.pvalue), "hello") == 0);
p_context_delete(context);
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
import testparser;
import std.stdio;
int main()
{
return 0;
}
unittest
{
p_token_info_t token_info;
string input = "42 f s";
p_context_t * context;
context = p_context_new(input);
/* Default ptype value extracted with p_value_get(). */
assert(p_lex(context, &token_info) == P_SUCCESS);
assert(token_info.token == TOKEN_num);
assert(p_value_get(&token_info.pvalue) == 42);
/* Named ptype values extracted with p_value_get_XXX(). */
assert(p_lex(context, &token_info) == P_SUCCESS);
assert(token_info.token == TOKEN_flt);
assert(p_value_get_float(&token_info.pvalue) == 1.5);
assert(p_lex(context, &token_info) == P_SUCCESS);
assert(token_info.token == TOKEN_str);
assert(p_value_get_string(&token_info.pvalue) == "hello");
}