9 Commits
Author SHA1 Message Date
josh 7122bdc2db wip 2026-02-08 22:22:27 -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
26 changed files with 417 additions and 125 deletions

No files matched your search

-1
View File
@@ -1,3 +1,2 @@
--format documentation
--color
--require spec_helper
+12 -10
View File
@@ -2,39 +2,41 @@ GEM
remote: https://rubygems.org/
specs:
base64 (0.3.0)
date (3.4.1)
date (3.5.1)
diff-lcs (1.6.2)
docile (1.4.1)
erb (5.0.2)
psych (5.2.6)
erb (6.0.1)
psych (5.3.1)
date
stringio
rake (13.3.0)
rdoc (6.14.2)
rake (13.3.1)
rdoc (7.1.0)
erb
psych (>= 4.0.0)
tsort
redcarpet (3.6.1)
rspec (3.13.1)
rspec (3.13.2)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.5)
rspec-core (3.13.6)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.5)
rspec-mocks (3.13.7)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.4)
rspec-support (3.13.6)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4)
stringio (3.1.7)
stringio (3.2.0)
syntax (1.2.2)
tsort (0.2.0)
PLATFORMS
ruby
+1 -1
View File
@@ -6,7 +6,7 @@ Propane is a LALR Parser Generator (LPG) which:
* generates a built-in lexer to tokenize input
* supports UTF-8 lexer inputs
* 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
* is MIT-licensed
* is distributable as a standalone Ruby script
+14
View File
@@ -1,5 +1,7 @@
require "rake/clean"
require "rspec/core/rake_task"
require "simplecov"
require "stringio"
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]
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
# useful for coverage information.
+51 -15
View File
@@ -695,11 +695,13 @@ typedef struct
} state_value_t;
/** Common AST node structure. */
typedef struct
typedef struct ASTNode_s
{
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
void * fields[];
uint16_t n_fields;
uint8_t is_token;
struct ASTNode_s * fields[];
} ASTNode;
/** Parser shift table. */
@@ -805,7 +807,7 @@ static void state_values_stack_push(state_values_stack_t * stack)
if (current_length >= current_capacity)
{
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));
free(stack->entries);
stack->capacity = new_capacity;
@@ -972,7 +974,7 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
{
/* Successful parse. */
<% if @grammar.ast %>
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)->ast_node;
<% else %>
context->parse_result = state_values_stack_index(&statevalues, -1)->pvalue;
<% end %>
@@ -989,9 +991,11 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
{
/* We shifted a token, mark it consumed. */
<% if @grammar.ast %>
<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> * token_ast_node = malloc(sizeof(<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>));
<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> * token_ast_node = (<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> *)malloc(sizeof(<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>));
token_ast_node->position = token_info.position;
token_ast_node->end_position = token_info.end_position;
token_ast_node->n_fields = 0u;
token_ast_node->is_token = 1u;
token_ast_node->token = token;
token_ast_node->pvalue = token_info.pvalue;
state_values_stack_index(&statevalues, -1)->ast_node = token_ast_node;
@@ -1028,31 +1032,30 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
else if (parser_reduce_table[reduce_index].n_states > 0)
{
size_t n_fields = parser_reduce_table[reduce_index].rule_set_node_field_array_size;
ASTNode * node = (ASTNode *)malloc(sizeof(ASTNode) + n_fields * sizeof(void *));
size_t bytes = sizeof(ASTNode) + n_fields * sizeof(void *);
ASTNode * node = (ASTNode *)malloc(bytes);
memset(node, 0, bytes);
node->position = INVALID_POSITION;
node->end_position = INVALID_POSITION;
for (size_t i = 0; i < n_fields; i++)
{
node->fields[i] = NULL;
}
node->n_fields = n_fields;
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++)
{
node->fields[i] = state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node;
node->fields[i] = (ASTNode *)state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node;
}
}
else
{
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]] = (ASTNode *)state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node;
}
}
bool position_found = false;
for (size_t i = 0; i < n_fields; i++)
{
ASTNode * child = (ASTNode *)node->fields[i];
ASTNode * child = node->fields[i];
if ((child != NULL) && <%= @grammar.prefix %>position_valid(child->position))
{
if (!position_found)
@@ -1074,6 +1077,7 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
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)
{
state_values_stack_free(&statevalues);
return P_USER_TERMINATED;
}
reduced_parser_value = reduced_parser_value2;
@@ -1106,13 +1110,13 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
* @return Parse result value.
*/
<% if @grammar.ast %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<%= @grammar.ast_prefix %><%= @grammar.start_rules[0] %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% end %>
{
<% if @grammar.ast %>
return context->parse_result;
return (<%= @grammar.ast_prefix %><%= @grammar.start_rules[0] %><%= @grammar.ast_suffix %> *) context->parse_result;
<% else %>
return context->parse_result.v_<%= start_rule_type[0] %>;
<% end %>
@@ -1153,3 +1157,35 @@ size_t <%= @grammar.prefix %>user_terminate_code(<%= @grammar.prefix %>context_t
{
return context->token;
}
<% if @grammar.ast %>
static void free_ast_node(ASTNode * node)
{
if (node->is_token)
{
<% if @grammar.free_token_node %>
<%= @grammar.free_token_node %>((<%= @grammar.ast_prefix %>Token<%= @grammar.ast_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_ast_node(node->fields[i]);
}
}
}
free(node);
}
/**
* Free all AST node memory.
*/
void <%= @grammar.prefix %>free_ast(<%= @grammar.ast_prefix %><%= @grammar.start_rules[0] %><%= @grammar.ast_suffix %> * ast)
{
free_ast_node((ASTNode *)ast);
}
<% end %>
+4 -4
View File
@@ -173,7 +173,7 @@ public struct <%= @grammar.prefix %>context_t
/** Parse result value. */
<% if @grammar.ast %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * parse_result;
void * parse_result;
<% else %>
<%= @grammar.prefix %>value_t parse_result;
<% end %>
@@ -1031,7 +1031,7 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
{
/* Successful parse. */
<% if @grammar.ast %>
context.parse_result = cast(<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> *)statevalues[$-1].ast_node;
context.parse_result = statevalues[$-1].ast_node;
<% else %>
context.parse_result = statevalues[$-1].pvalue;
<% end %>
@@ -1156,13 +1156,13 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
* @return Parse result value.
*/
<% if @grammar.ast %>
public <%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
public <%= @grammar.ast_prefix %><%= @grammar.start_rules[0] %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% else %>
public <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% end %>
{
<% if @grammar.ast %>
return context.parse_result;
return cast(<%= @grammar.ast_prefix %><%= @grammar.start_rules[0] %><%= @grammar.ast_suffix %> *)context.parse_result;
<% else %>
return context.parse_result.v_<%= start_rule_type[0] %>;
<% end %>
+10 -3
View File
@@ -75,9 +75,11 @@ typedef union
/** AST node types. @{ */
typedef struct <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>
{
/* ASTNode fields must be present in the same order here. */
<% # ASTNode 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.ast_prefix %>Token<%= @grammar.ast_suffix %>;
@@ -93,8 +95,11 @@ struct <%= name %>;
<% next if rule_set.optional? %>
typedef struct <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %>
{
<% # ASTNode 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.ast_fields.each do |fields| %>
union
{
@@ -157,7 +162,7 @@ typedef struct
/** Parse result value. */
<% if @grammar.ast %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * parse_result;
void * parse_result;
<% else %>
<%= @grammar.prefix %>value_t parse_result;
<% end %>
@@ -186,7 +191,9 @@ size_t <%= @grammar.prefix %>lex(<%= @grammar.prefix %>context_t * context, <%=
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context);
<% if @grammar.ast %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<%= @grammar.ast_prefix %><%= @grammar.start_rules[0] %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
void <%= @grammar.prefix %>free_ast(<%= @grammar.ast_prefix %><%= @grammar.start_rules[0] %><%= @grammar.ast_suffix %> * ast);
<% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<% end %>
+96 -4
View File
@@ -13,7 +13,7 @@ Propane is a LALR Parser Generator (LPG) which:
* generates a built-in lexer to tokenize input
* supports UTF-8 lexer inputs
* 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
* tracks input text start and end positions for all matched tokens/rules
* is MIT-licensed
@@ -326,6 +326,36 @@ assert(itemsmore.pItem.pItem.pItem !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_ast()` call to free the memory
associated with a token node.
Example:
```
<<
static void free_token(Token * token)
{
free(token->pvalue);
}
>>
ast;
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
The `token` statement allows defining a lexer token and a pattern to match that
@@ -751,7 +781,7 @@ Some example uses of this functionality could be to:
* 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.
* 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
`$terminate(code)` function, passing an integer expression argument.
@@ -787,7 +817,7 @@ Propane generates the following result code constants:
* `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.
Result codes are returned by the functions `p_decode_code_point()`, `p_lex()`, and `p_parse()`.
Result codes are returned by the API functions `p_decode_code_point()`, `p_lex()`, and `p_parse()`.
##> Types
@@ -807,7 +837,7 @@ A pointer to this instance is passed to the generated functions.
### `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 1-based row and column describing a parser position.
For D targets, the `p_position_t` structure can be checked for validity by
@@ -817,6 +847,16 @@ 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.
### AST Node Types
If AST generation mode is enabled, a structure type for each rule will be
@@ -927,6 +967,44 @@ p_context_t context;
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`
The `p_parse()` function is the main entry point to the parser.
@@ -1050,6 +1128,20 @@ assert(code_point == 0x1F9E1u);
assert(code_point_length == 4u);
```
### `p_free_ast`
The `p_free_ast()` function can be used to free the memory used by the AST.
It should be passed the same value that is returned by `p_result()`.
The `p_free_ast()` 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_ast()` process to allow user code to free any memory associated with
a token node's `pvalue`.
##> Data
### `p_token_names`
+19 -13
View File
@@ -11,24 +11,28 @@ class Propane
@log = StringIO.new
end
@language =
if output_file =~ /\.([a-z]+)$/
$1
else
if output_file.end_with?(".d")
"d"
else
"c"
end
@options = options
process_grammar!
end
def generate
extensions = [@language]
extensions = [nil]
if @language == "c"
extensions += %w[h]
end
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: "<>")
output_file = @output_file.sub(%r{\.[a-z]+$}, ".#{extension}")
result = erb.result(binding.clone)
File.open(output_file, "wb") do |fh|
fh.write(result)
@@ -67,12 +71,14 @@ class Propane
end
tokens_by_name[token.name] = token
end
# Check for user start rule.
unless @grammar.rules.find {|rule| rule.name == @grammar.start_rule}
raise Error.new("Start rule `#{@grammar.start_rule}` not found")
# Check for user start rules.
@grammar.start_rules.each_with_index do |start_rule, i|
unless @grammar.rules.find {|rule| rule.name == start_rule}
raise Error.new("Start rule `#{start_rule}` not found")
end
# Add "real" start rule.
@grammar.rules.unshift(Rule.new("$#{start_rule}", [start_rule, "$EOF"], nil, nil, nil))
end
# Add "real" start rule.
@grammar.rules.unshift(Rule.new("$Start", [@grammar.start_rule, "$EOF"], nil, nil, nil))
# Generate and add rules for optional components.
generate_optional_component_rules!(tokens_by_name)
# Build rule sets.
@@ -328,9 +334,9 @@ class Propane
#
# @return [Array<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|
rule.name == @grammar.start_rule
rule.name == @grammar.start_rules[start_rule_index]
end
[start_rule.ptypename, @grammar.ptypes[start_rule.ptypename]]
end
+13 -3
View File
@@ -8,10 +8,11 @@ class Propane
attr_reader :ast
attr_reader :ast_prefix
attr_reader :ast_suffix
attr_reader :free_token_node
attr_reader :modulename
attr_reader :patterns
attr_reader :rules
attr_reader :start_rule
attr_reader :start_rules
attr_reader :tokens
attr_reader :code_blocks
attr_reader :ptypes
@@ -19,7 +20,7 @@ class Propane
def initialize(input)
@patterns = []
@start_rule = "Start"
@start_rules = []
@tokens = []
@rules = []
@code_blocks = {}
@@ -32,7 +33,9 @@ class Propane
@ast = false
@ast_prefix = ""
@ast_suffix = ""
@free_token_node = nil
parse_grammar!
@start_rules << "Start" if @start_rules.empty?
end
def ptype
@@ -62,6 +65,7 @@ class Propane
elsif parse_ast_statement!
elsif parse_ast_prefix_statement!
elsif parse_ast_suffix_statement!
elsif parse_free_token_node_statement!
elsif parse_module_statement!
elsif parse_ptype_statement!
elsif parse_pattern_statement!
@@ -112,6 +116,12 @@ class Propane
end
end
def parse_free_token_node_statement!
if md = consume!(/free_token_node\s+(\w+)\s*;/)
@free_token_node = md[1]
end
end
def parse_module_statement!
if consume!(/module\s+/)
md = consume!(/([\w.]+)\s*/, "expected module name")
@@ -233,7 +243,7 @@ class Propane
def parse_start_statement!
if md = consume!(/start\s+(\w+)\s*;/)
@start_rule = md[1]
@start_rules << md[1] unless @start_rules.include?(md[1])
end
end
+2 -2
View File
@@ -120,11 +120,11 @@ string: /\\t/ <<
>>
string: /\\u[0-9a-fA-F]{4}/ <<
/* 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);
>>
string: /[^\\]/ <<
char s[] = {match[0], 0};
char s[] = {(char)match[0], 0};
str_append(&string_value, s);
>>
Start -> Value <<
+3 -3
View File
@@ -5,7 +5,7 @@
JSONValue * JSONValue_new(size_t id)
{
JSONValue * jv = calloc(1, sizeof(JSONValue));
JSONValue * jv = (JSONValue *)calloc(1, sizeof(JSONValue));
jv->id = id;
return jv;
}
@@ -29,7 +29,7 @@ void JSONObject_append(JSONValue * object, char const * name, JSONValue * value)
}
}
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)
{
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 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)
{
memcpy(new_entries, array->array.entries, sizeof(JSONValue *) * size);
+7 -5
View File
@@ -11,6 +11,12 @@
#define JSON_FALSE 5u
#define JSON_NULL 6u
typedef struct JSONObjectEntry_s
{
char const * name;
struct JSONValue_s * value;
} JSONObjectEntry;
typedef struct JSONValue_s
{
size_t id;
@@ -19,11 +25,7 @@ typedef struct JSONValue_s
struct
{
size_t size;
struct
{
char const * name;
struct JSONValue_s * value;
} * entries;
JSONObjectEntry * entries;
} object;
struct
{
+115 -58
View File
@@ -65,26 +65,47 @@ EOF
end
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] ||= [""]
parsers = options[:parsers].map do |name|
"spec/run/testparser#{name}.#{options[:language]}"
end
case options[:language]
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"
result = system(*%w[ldc2 -g --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
result = system(*command)
expect(result).to be_truthy
end
def run_test
def run_test(options)
stdout, stderr, status = Open3.capture3("spec/run/testparser")
File.binwrite("spec/run/.stderr", stderr)
File.binwrite("spec/run/.stdout", stdout)
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
def lines(str)
@@ -261,7 +282,7 @@ EOF
expect(results.status).to_not eq 0
end
%w[d c].each do |language|
%w[d c cpp].each do |language|
context "#{language.upcase} language" do
@@ -277,14 +298,14 @@ Foo -> plus <<>>
EOF
run_propane(language: language)
compile("spec/test_lexer.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "detects a lexer error when an unknown character is seen" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
ptype int;
token int /\\d+/ <<
@@ -315,7 +336,7 @@ EOF
end
run_propane(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.status).to eq 0
end
@@ -338,7 +359,7 @@ EOF
it "generates a parser that does basic math - user guide example" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <math.h>
@@ -409,7 +430,7 @@ EOF
end
run_propane(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.status).to eq 0
end
@@ -435,7 +456,7 @@ R2 -> a b;
EOF
run_propane(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
end
@@ -450,13 +471,13 @@ R1 -> b;
EOF
run_propane(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
end
it "executes user code when matching lexer token" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
@@ -485,7 +506,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_user_code.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"abc!",
@@ -498,7 +519,7 @@ EOF
it "supports a pattern statement" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
@@ -521,7 +542,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_pattern.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"def!",
@@ -534,7 +555,7 @@ EOF
it "supports returning tokens from pattern code blocks" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
@@ -563,7 +584,7 @@ EOF
end
run_propane(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
verify_lines(results.stdout, [
"def!",
@@ -574,7 +595,7 @@ EOF
it "supports lexer modes" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
@@ -621,7 +642,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_lexer_modes.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"begin string mode",
@@ -635,7 +656,7 @@ EOF
it "multiple lexer modes may apply to a pattern" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
@@ -679,7 +700,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_lexer_multiple_modes.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"ident: d",
@@ -691,7 +712,7 @@ EOF
it "executes user code associated with a parser rule" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
@@ -716,7 +737,7 @@ EOF
end
run_propane(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
verify_lines(results.stdout, [
"A!",
@@ -727,7 +748,7 @@ EOF
it "parses lists" do
write_grammar <<EOF
ptype #{language == "c" ? "uint32_t" : "uint"};
ptype #{language == "d" ? "uint" : "uint32_t"};
token a;
Start -> As << $$ = $1; >>
As -> << $$ = 0u; >>
@@ -735,7 +756,7 @@ As -> As a << $$ = $1 + 1u; >>
EOF
run_propane(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.stderr).to eq ""
end
@@ -762,14 +783,14 @@ EOF
it "provides matched text to user code blocks" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
#include <stdlib.h>
>>
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);
printf("Matched token is %s\\n", t);
free(t);
@@ -789,7 +810,7 @@ EOF
end
run_propane(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
verify_lines(results.stdout, [
"Matched token is identifier_123",
@@ -799,7 +820,7 @@ EOF
it "allows storing a result value for the lexer" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
ptype uint64_t;
token word /[a-z]+/ <<
@@ -822,7 +843,7 @@ EOF
end
run_propane(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.status).to eq 0
end
@@ -837,13 +858,14 @@ Start -> a num;
EOF
run_propane(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.status).to eq 0
end
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)
compile(["spec/test_parsing_json.#{language}", "spec/json_types.#{language}"], language: language)
end
@@ -865,7 +887,7 @@ Start -> b c b;
EOF
run_propane(name: "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.status).to eq 0
end
@@ -884,7 +906,7 @@ Any -> c;
EOF
run_propane(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.status).to eq 0
end
@@ -902,14 +924,14 @@ Any -> ;
EOF
run_propane(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.status).to eq 0
end
it "matches backslash escape sequences" do
case language
when "c"
when "c", "cpp"
write_grammar <<EOF
<<
#include <stdio.h>
@@ -946,7 +968,7 @@ EOF
end
run_propane(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.status).to eq 0
verify_lines(results.stdout, [
@@ -1011,7 +1033,7 @@ Two -> two;
EOF
run_propane(language: language)
compile("spec/test_ast.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@@ -1055,7 +1077,7 @@ Two -> two;
EOF
run_propane(language: language)
compile("spec/test_ast_ps.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@@ -1123,13 +1145,13 @@ Start -> a? b R? <<
printf("b: %d\\n", $2);
printf("R: %s\\n", $3 == NULL ? "" : $3);
>>
R -> c d << $$ = "cd"; >>
R (string) -> d c << $$ = "dc"; >>
R -> c d << $$ = (char *)"cd"; >>
R (string) -> d c << $$ = (char *)"dc"; >>
EOF
end
run_propane(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.status).to eq 0
verify_lines(results.stdout, [
@@ -1181,7 +1203,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_optional_rule_component_ast.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@@ -1222,7 +1244,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_named_optional_rule_component_ast.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@@ -1242,7 +1264,7 @@ T -> c;
EOF
run_propane(language: language)
compile("spec/test_ast_token_positions.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@@ -1262,7 +1284,7 @@ A -> bb? c?;
EOF
run_propane(language: language)
compile("spec/test_ast_invalid_positions.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@@ -1282,7 +1304,7 @@ T -> c;
EOF
run_propane(language: language)
compile("spec/test_ast_field_aliases.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@@ -1305,7 +1327,7 @@ T -> c;
EOF
run_propane(language: language)
compile("spec/test_ast_field_aliases.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@@ -1332,9 +1354,9 @@ EOF
#include <stdio.h>
#include <string.h>
>>
ptype char const *;
ptype char *;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * s = malloc(match_length + 1);
char * s = (char *)malloc(match_length + 1);
strncpy(s, (char const *)match, match_length);
s[match_length] = 0;
$$ = s;
@@ -1343,12 +1365,14 @@ 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
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
@@ -1379,9 +1403,9 @@ EOF
#include <stdio.h>
#include <string.h>
>>
ptype char const *;
ptype char *;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * s = malloc(match_length + 1);
char * s = (char *)malloc(match_length + 1);
strncpy(s, (char const *)match, match_length);
s[match_length] = 0;
$$ = s;
@@ -1392,26 +1416,59 @@ 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
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 AST nodes" do
write_grammar(File.read("spec/ast_node_memory_remains.#{language}.propane"))
ext = language == "cpp" ? "c" : language
write_grammar(File.read("spec/ast_node_memory_remains.#{ext}.propane"))
run_propane(language: language)
compile("spec/test_ast_node_memory_remains.#{language}", language: language)
results = run_test
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 AST mode" do
write_grammar <<EOF
<<
static void free_token(Token * token)
{
free(token->pvalue);
}
>>
ast;
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_ast_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
+5
View File
@@ -2,6 +2,10 @@ unless ENV["dist_specs"]
require "bundler/setup"
require "simplecov"
class MyFormatter
def format(*args)
end
end
SimpleCov.start do
add_filter "/spec/"
add_filter "/.bundle/"
@@ -12,6 +16,7 @@ unless ENV["dist_specs"]
end
project_name "Propane"
merge_timeout 3600
formatter(MyFormatter)
end
RSpec.configure do |config|
+6
View File
@@ -33,12 +33,16 @@ int main()
assert_eq(22, itemsmore->pItem->pToken1->pvalue);
assert(itemsmore->pItemsMore == NULL);
p_free_ast(start);
input = "";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
start = p_result(&context);
assert(start->pItems == NULL);
p_free_ast(start);
input = "2 1";
p_context_init(&context, (uint8_t const *)input, strlen(input));
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->pOne1 == NULL);
p_free_ast(start);
return 0;
}
+2
View File
@@ -15,5 +15,7 @@ int main()
assert_eq(TOKEN_b, start->second->pToken->token);
assert_eq(TOKEN_c, start->third->pToken->token);
p_free_ast(start);
return 0;
}
+8
View File
@@ -30,6 +30,8 @@ int main()
assert_eq(3, start->end_position.row);
assert_eq(8, start->end_position.col);
p_free_ast(start);
input = "a\nbb";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@@ -54,6 +56,8 @@ int main()
assert_eq(2, start->end_position.row);
assert_eq(2, start->end_position.col);
p_free_ast(start);
input = "a\nc\nc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@@ -78,6 +82,8 @@ int main()
assert_eq(3, start->end_position.row);
assert_eq(1, start->end_position.col);
p_free_ast(start);
input = "a";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@@ -98,5 +104,7 @@ int main()
assert_eq(1, start->end_position.row);
assert_eq(1, start->end_position.col);
p_free_ast(start);
return 0;
}
+4 -1
View File
@@ -386,7 +386,7 @@ int main(int argc, char * argv[])
}
pmis = pmis->pModuleItems;
}
pfds = malloc(n_pfds * sizeof(PModuleItems *));
pfds = (PFunctionDefinition **)malloc(n_pfds * sizeof(PModuleItems *));
pmis = pmod->pModuleItems;
size_t pfd_i = n_pfds;
while (pmis != NULL)
@@ -411,5 +411,8 @@ int main(int argc, char * argv[])
}
}
free(pfds);
p_free_ast(pmod);
return 0;
}
+6
View File
@@ -33,12 +33,16 @@ int main()
assert_eq(22, itemsmore->pItem->pToken1->pvalue);
assert(itemsmore->pItemsMore == NULL);
p_free_ast(start);
input = "";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
start = p_result(&context);
assert(start->pItems == NULL);
p_free_ast(start);
input = "2 1";
p_context_init(&context, (uint8_t const *)input, strlen(input));
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->pOne1 == NULL);
p_free_ast(start);
return 0;
}
+4
View File
@@ -43,6 +43,8 @@ int main()
assert_eq(1, start->end_position.row);
assert_eq(6, start->end_position.col);
p_free_ast(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);
@@ -80,5 +82,7 @@ int main()
assert_eq(7, start->end_position.row);
assert_eq(6, start->end_position.col);
p_free_ast(start);
return 0;
}
+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_ast(start);
}
@@ -17,6 +17,8 @@ int main()
assert(start->pR == NULL);
assert(start->r == NULL);
p_free_ast(start);
input = "abcd";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@@ -31,6 +33,8 @@ int main()
assert(start->pR == start->r);
assert_eq(TOKEN_c, start->pR->pToken1->token);
p_free_ast(start);
input = "bdc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@@ -40,6 +44,8 @@ int main()
assert(start->r != NULL);
assert_eq(TOKEN_d, start->pR->pToken1->token);
p_free_ast(start);
return 0;
}
+6
View File
@@ -16,6 +16,8 @@ int main()
assert(start->pR3 == NULL);
assert(start->pR == NULL);
p_free_ast(start);
input = "abcd";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@@ -28,6 +30,8 @@ int main()
assert(start->pR == start->pR3);
assert_eq(TOKEN_c, start->pR->pToken1->token);
p_free_ast(start);
input = "bdc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@@ -37,6 +41,8 @@ int main()
assert(start->pR != NULL);
assert_eq(TOKEN_d, start->pR->pToken1->token);
p_free_ast(start);
return 0;
}
+2
View File
@@ -13,5 +13,7 @@ int main()
assert(top->pToken != NULL);
assert_eq(TOKEN_hi, top->pToken->token);
p_free_ast(top);
return 0;
}
+2 -2
View File
@@ -17,7 +17,7 @@ void assert_eq_size_t_i(size_t expected, size_t actual, char const * file, size_
void str_init(str_t * str, char const * cs)
{
size_t length = strlen(cs);
str->cs = malloc(length + 1u);
str->cs = (char *)malloc(length + 1u);
strcpy(str->cs, cs);
}
@@ -25,7 +25,7 @@ void str_append(str_t * str, char const * cs)
{
size_t length = strlen(str->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);
strcpy(&new_cs[length], cs);
free(str->cs);