This repository has been archived on 2024-07-15. You can view files and clone it, but cannot push or open issues or pull requests.
hedera/sql/parser/gram.y

764 lines
19 KiB
Plaintext

%include
{
/*
* Copyright (C) 2012 - Juan Ferrer Toribio
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <assert.h>
#include <sql/sql.h>
#include "gram.h"
typedef struct
{
SqlObject * object;
gchar * string;
gchar * current;
gboolean failed;
GError ** error;
}
ParseState;
typedef enum
{
SQL_PARSER_ERROR_EMPTY_QUERY = 1 << 0,
SQL_PARSER_ERROR_PARSING = 1 << 1,
SQL_PARSER_ERROR_STACK_OVERFLOW = 1 << 2,
}
SqlParserError;
}
%token_type { gchar * }
%token_destructor { g_free ($$); }
%token_prefix SQL_PARSER_
// Return pointer
%extra_argument { ParseState * state }
// Error treatment
%syntax_error
{
gchar * pref = ".", * err_str = NULL;
if (state->current)
{
err_str = state->current - 15 >= state->string ?
state->current - 10 : state->string;
pref = err_str == state->string ? " near: " : " near: [...]";
}
g_set_error (state->error, SQL_PARSER_LOG_DOMAIN, SQL_PARSER_ERROR_PARSING,
"Parsing error%s%s", pref, err_str);
state->failed = TRUE;
}
%stack_overflow
{
g_set_error (state->error, SQL_PARSER_LOG_DOMAIN, SQL_PARSER_ERROR_STACK_OVERFLOW,
"Parser stack overflow. Parsing terminated.\n");
state->failed = TRUE;
}
%parse_accept {}
// Operator precedence
%left UNION EXCEPT.
%left INSERSECT.
%left OR.
%left XOR.
%left AND.
%right NOT.
%right EQ NE.
%left GT GE LT LE.
%left LIKE.
%left IS.
%left PLUS MINUS.
%left STAR DIV MOD.
%right SIGN.
// Start symbol:
%start_symbol start
%type start {SqlObject *}
start ::= multi_stmt (A).
{
state->object = SQL_OBJECT (A);
}
// SqlMultiStmt
%type multi_stmt {SqlStmt *}
multi_stmt(multi) ::= stmt_list(stmt).
{
gint len = g_slist_length (stmt);
if (len > 1)
{
multi = SQL_STMT (sql_multi_stmt_new ());
GSList * n;
for (n = stmt; n; n = n->next)
sql_multi_stmt_add_stmt (SQL_MULTI_STMT (multi), n->data);
}
else if (len != 0)
multi = SQL_STMT (stmt->data);
else
multi = NULL;
g_slist_free (stmt);
}
%type stmt_list {GSList *}
%destructor stmt_list {g_slist_free ($$);}
stmt_list(A) ::= stmt(X) SC stmt_list(B). { A = g_slist_prepend (B, X); }
stmt_list(A) ::= stmt(X). { A = g_slist_prepend (A, X); }
stmt_list(A) ::= stmt(X) SC. { A = g_slist_prepend (A, X); }
// SqlStmt
%type stmt {SqlStmt *}
// SqlSelect
stmt(stmt) ::= multi_select(select). { stmt = SQL_STMT (select); }
%type multi_select {SqlSelect *}
multi_select(A) ::= multi_select(B) set_op(type) select_stmt(X).
{
sql_select_set_next (B, X, type);
A = B;
}
multi_select(A) ::= select_stmt(X). { A = X; }
%type set_op {SqlSelectType}
set_op(A) ::= UNION ANY. { A = SQL_SELECT_UNION_ANY; }
set_op(A) ::= UNION ALL. { A = SQL_SELECT_UNION_ALL; }
set_op(A) ::= UNION. { A = SQL_SELECT_UNION_ALL; }
set_op(A) ::= INTERSECT. { A = SQL_SELECT_INTERSECT; }
set_op(A) ::= EXCEPT. { A = SQL_SELECT_EXCEPT; }
%type select_stmt {SqlSelect *}
select_stmt(A) ::= LP select_stmt(X) RP. { A = X; }
select_stmt(select) ::= SELECT distinct(distinct) select_expr(expr_l)
from(target_l)
where(where)
group(group_l) having(having)
order(order_l) orderway(way)
limit(limit).
{
GSList * n;
select = sql_select_new ();
sql_select_set_distinct (select, distinct);
for (n = expr_l; n; n = n->next)
{
SelectExpr * se = n->data;
sql_select_add_expr (select, se->expr);
if (se->alias)
{
sql_select_set_alias (select, se->expr, se->alias);
g_free (se->alias);
}
g_free (se);
}
g_slist_free (expr_l);
for (n = target_l; n; n = n->next)
sql_dml_add_target (SQL_DML (select), n->data);
g_slist_free (target_l);
if (where)
sql_dml_set_where (SQL_DML (select), where);
if (group_l)
{
for (n = group_l; n; n = n->next)
sql_select_add_group (select, n->data);
g_slist_free (group_l);
if (having)
sql_select_set_having (select, having);
}
if (order_l)
{
for (n = order_l; n; n = n->next)
sql_select_add_order (select, n->data, way);
g_slist_free (order_l);
}
if (limit)
{
sql_select_set_limit (select
,limit[0] ? (guint) atoi (limit[0]): 0
,limit[1] ? (guint) atoi (limit[1]): 0
);
g_strfreev (limit);
}
}
%type distinct {gboolean}
distinct(A) ::= DISTINCT. { A = TRUE; }
distinct(A) ::= ALL. { A = FALSE; }
distinct(A) ::= . { A = FALSE; }
// Expressions with optional alias, only found in the SELECT clause
%type select_expr {GSList *}
%include
{
typedef struct
{
SqlExpr * expr;
gchar * alias;
}
SelectExpr;
static inline GSList * sql_parser_alias_expr_add
(GSList * list, SqlExpr * expr, gchar * alias)
{
SelectExpr * sexpr = g_new (SelectExpr, 1);
sexpr->expr = expr;
sexpr->alias = alias;
return g_slist_append (list, sexpr);
}
}
%destructor select_expr {g_slist_free_full ($$, (GDestroyNotify) g_free);}
select_expr(A) ::= select_expr(B) CM expr(X).
{ A = sql_parser_alias_expr_add (B, X, NULL); }
select_expr(A) ::= select_expr(B) CM expr(X) alias(Y).
{ A = sql_parser_alias_expr_add (B, X, Y); }
select_expr(A) ::= expr(X).
{ A = sql_parser_alias_expr_add (A, X, NULL); }
select_expr(A) ::= expr(X) alias(Y).
{ A = sql_parser_alias_expr_add (A, X, Y); }
%type from {GSList *}
%destructor from {g_slist_free ($$);}
from(A) ::= FROM target_list(X). { A = X; }
from(A) ::= . { A = NULL; }
%type group {GSList *}
%destructor group {g_slist_free ($$);}
group(A) ::= GROUP expr_list(X). { A = X; }
group(A) ::= . { A = NULL; }
%type having {SqlExpr *}
having(A) ::= HAVING expr(X). { A = X; }
having(A) ::= . { A = NULL; }
%type order {GSList *}
%destructor order {g_slist_free ($$);}
order(A) ::= ORDER expr_list (X). { A = X; }
order(A) ::= . { A = NULL; }
%type orderway {SqlOrderWay}
orderway(A) ::= ASC. { A = SQL_ORDER_ASC; }
orderway(A) ::= DESC. { A = SQL_ORDER_DESC; }
orderway(A) ::= . { A = SQL_ORDER_ASC; }
%type limit {gchar **}
%include
{
gchar ** sql_parser_limit (gchar * limit, gchar * offset)
{
gchar ** r = g_new (gchar *, 3);
r[0] = limit;
r[1] = offset;
r[2] = NULL;
return r;
}
}
limit(A) ::= LIMIT INTEGER(X). { A = sql_parser_limit (X, NULL); }
limit(A) ::= OFFSET INTEGER(Y). { A = sql_parser_limit (NULL, Y); }
limit(A) ::= LIMIT INTEGER(X)
OFFSET INTEGER(Y). { A = sql_parser_limit (X, Y); }
limit(A) ::= LIMIT INTEGER(X)
CM INTEGER(Y). { A = sql_parser_limit (Y, X); }
limit(A) ::= . { A = NULL; }
// SqlDelete
stmt(stmt) ::= delete_stmt(delete). { stmt = SQL_STMT (delete); }
%type delete_stmt {SqlDelete *}
delete_stmt(delete) ::= DELETE table_list(target) del_using(using) where(where).
{
GSList * n;
delete = sql_delete_new ();
for (n = target; n; n = n->next)
sql_dml_add_target (SQL_DML (delete), n->data);
g_slist_free (target);
for (n = using; n; n = n->next)
sql_delete_add_table (delete, n->data);
g_slist_free (using);
if (where)
sql_dml_set_where (SQL_DML (delete), where);
}
%type table_list {GSList *}
%destructor table_list {g_slist_free ($$);}
table_list(A) ::= table_list(B) CM table(X). { A = g_slist_append (B, X); }
table_list(A) ::= table(X). { A = g_slist_append (A, X); }
%type del_using {GSList *}
del_using(A) ::= USING target_list(X). { A = X; }
del_using(A) ::= . { A = NULL; }
// SqlUpdate
stmt(stmt) ::= update_stmt(update). { stmt = SQL_STMT (update); }
%type update_stmt {SqlUpdate *}
update_stmt(update) ::= UPDATE table(table) SET update_list(u_list) where(where).
{
GSList * n;
update = sql_update_new ();
sql_dml_add_target (SQL_DML (update), table);
for (n = u_list; n; n = n->next)
{
sql_update_add_set (update
,((SqlUpdateSet *) n->data)->field
,((SqlUpdateSet *) n->data)->expr
);
g_free (n->data);
}
g_slist_free (u_list);
if (where)
sql_dml_set_where (SQL_DML (update), where);
}
%type update_list {GSList *}
%destructor update_list {g_slist_free ($$);}
update_list(A) ::= update_list(B) CM update_set(X). { A = g_slist_append (B, X); }
update_list(A) ::= update_set(X). { A = g_slist_append (A, X); }
%type update_set {SqlUpdateSet *}
update_set(A) ::= field(X) EQ expr(Y).
{
A = g_new (SqlUpdateSet, 1);
A->field = SQL_FIELD (X);
A->expr = Y;
}
update_set(A) ::= field(X) EQ DEFAULT.
{
A = g_new (SqlUpdateSet, 1);
A->field = SQL_FIELD (X);
A->expr = NULL;
}
// Common to SELECT, DELETE and UPDATE
%type where {SqlExpr *}
where(A) ::= WHERE expr(X). { A = SQL_EXPR (X); }
where(A) ::= . { A = NULL; }
// SqlInsert
stmt(stmt) ::= insert_stmt(insert). { stmt = SQL_STMT (insert); }
%type insert_stmt {SqlInsert *}
insert_stmt(insert) ::= INSERT table(table) ins_fields(field) ins_values(value).
{
GSList * ll, * n;
insert = sql_insert_new ();
sql_insert_set_table (insert, SQL_TABLE (table));
for (n = field; n; n = n->next)
sql_insert_add_field (insert, n->data);
g_slist_free (field);
for (ll = value; ll; ll = ll->next)
{
sql_insert_add_row (insert);
for (n = ll->data; n; n = n->next)
sql_insert_add_expr (insert, n->data);
g_slist_free (ll->data);
}
g_slist_free (ll);
}
%type ins_fields {GSList *}
%destructor ins_fields {g_slist_free ($$);}
ins_fields(A) ::= LP field_list(X) RP. { A = X; }
ins_fields(A) ::= . { A = NULL; }
%type ins_values {GSList *}
ins_values ::= DEFAULT VALUES.
ins_values(A) ::= VALUES list_list(X). { A = X; }
//ins_values ::= multi_select.// not supported by the parsetree;
%type list_list {GSList *}
%destructor list_list {g_slist_free ($$);}
list_list(A) ::= list_list(B) CM LP def_expr_list(X) RP. { A = g_slist_append (B, X); }
list_list(A) ::= LP def_expr_list(X) RP. { A = g_slist_append (A, X); }
%type def_expr_list {GSList *}
%destructor def_expr_list {g_slist_free ($$);}
def_expr_list(A) ::= def_expr_list(B) CM def_expr(X). { A = g_slist_append (B, X); }
def_expr_list(A) ::= def_expr(X). { A = g_slist_append (A, X); }
%type def_expr {SqlExpr *}
def_expr(A) ::= DEFAULT. { A = NULL; }
def_expr(A) ::= expr(X). { A = X; }
// Common to all the statements
// SqlTarget
%type target {SqlTarget *}
//target(A) ::= LP target(B) RP. { A = B; }
target(A) ::= target(B) alias(X).
{
sql_target_set_alias (B, X);
g_free (X);
A = B;
}
%type target_list {GSList *}
%destructor target_list {g_slist_free ($$);}
target_list(A) ::= target_list(B) CM target(X). { A = g_slist_append (B, X); }
target_list(A) ::= target(X). { A = g_slist_append (A, X); }
target(A) ::= join(X). { A = X; }
target(A) ::= subquery(X). { A = X; }
target(A) ::= table(X). { A = X; }
// SqlJoin
%type join {SqlTarget *}
join(join) ::= target(left) join_type(jtype) target(right) join_cond(condition).
{
join = sql_join_new (left, right, jtype);
if (condition)
{
if (SQL_IS_EXPR (condition))
sql_join_set_condition (SQL_JOIN (join), condition);
else // USING list (GSList of SqlField)
{
SqlOperation * op = NULL;
GSList * n, * cond_list = condition;
gboolean use_cond = cond_list && cond_list->next ? TRUE : FALSE;
SqlOperation * cond = use_cond ?
sql_operation_new (SQL_OPERATION_TYPE_AND) :
NULL;
for (n = condition; n; n = n->next)
{
gchar * target = left->alias ?
left->alias : SQL_TABLE (left)->name;
gchar * schema = SQL_IS_TABLE(left) && SQL_TABLE (left)->schema ?
SQL_TABLE (left)->schema : NULL;
op = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
g_object_set (n->data, "target", target, "schema", schema, NULL);
sql_operation_add_expr (op, n->data);
target = right->alias ? right->alias : SQL_TABLE (right)->name;
schema = SQL_IS_TABLE (right) && SQL_TABLE (right)->schema ?
SQL_TABLE (right)->schema : NULL;
sql_operation_add_expr (op,
sql_field_new (SQL_FIELD (n->data)->name, target, schema));
if (use_cond)
sql_operation_add_expr (cond, SQL_EXPR (op));
}
g_slist_free (condition);
sql_join_set_condition (SQL_JOIN (join),
use_cond ? SQL_EXPR (cond) : SQL_EXPR (op));
}
}
}
%type join_type {SqlJoinType}
join_type(A) ::= INNER JOIN. { A = SQL_JOIN_TYPE_INNER; }
join_type(A) ::= JOIN. { A = SQL_JOIN_TYPE_INNER; }
join_type(A) ::= LEFT JOIN. { A = SQL_JOIN_TYPE_LEFT; }
join_type(A) ::= RIGHT JOIN. { A = SQL_JOIN_TYPE_RIGHT; }
%type join_cond {gpointer}
join_cond(A) ::= ON expr(X). { A = X; }// A : SqlExpr
join_cond(A) ::= USING LP field_list(X) RP. { A = X; }// A : GSList
// SqlSubquery
%type subquery {SqlTarget *}
subquery(sub) ::= LP multi_select(stmt) RP.
{ sub = SQL_TARGET (sql_subquery_new (stmt)); }
//SqlTable
%type table {SqlTarget *}
table(A) ::= identifier(X).
{
A = sql_table_new (X);
g_free (X);
}
table(A) ::= identifier(Y) DOT identifier(X).
{
A = sql_table_new (X);
g_object_set (A, "schema", Y, NULL);
g_free (Y);
g_free (X);
}
// SqlExpr
%type expr {SqlExpr *}
expr(A) ::= LP expr(X) RP. { A = X; }
%type expr_list {GSList *}
%destructor expr_list {g_slist_free ($$);}
expr_list(A) ::= expr_list(B) CM expr(X). { A = g_slist_append (B, X); }
expr_list(A) ::= expr(X). { A = g_slist_append (A, X); }
expr(A) ::= field(X). { A = X; }
expr(A) ::= function(X). { A = X; }
expr(A) ::= operation(X). { A = SQL_EXPR (X); }
expr(A) ::= value(X). { A = X; }
// SqlField
%type field {SqlExpr *}
field(A) ::= identifier(X).
{
A = sql_field_new (X, NULL, NULL);
g_free (X);
}
field(A) ::= STAR.
{
A = sql_field_new ("*", NULL, NULL);
}
field(A) ::= identifier(Y) DOT identifier(X).
{
A = sql_field_new (X, Y, NULL);
g_free (X);
g_free (Y);
}
field(A) ::= identifier(Y) DOT STAR.
{
A = sql_field_new ("*", Y, NULL);
g_free (Y);
}
field(A) ::= identifier(Z) DOT identifier(Y) DOT identifier(X).
{
A = sql_field_new (X, Y, Z);
g_free (X);
g_free (Y);
g_free (Z);
}
field(A) ::= identifier(Z) DOT identifier(Y) DOT STAR.
{
A = sql_field_new ("*", Y, Z);
g_free (Y);
g_free (Z);
}
%type field_list {GSList *}
%destructor field_list {g_slist_free ($$);}
field_list(A) ::= field_list(B) CM field(X). { A = g_slist_append (B, X); }
field_list(A) ::= field(X). { A = g_slist_append (A, X); }
// SqlFunction
%type function {SqlExpr *}
function(A) ::= identifier(Y) DOT identifier(X) LP RP.
{
A = SQL_EXPR (sql_function_new (X, Y));
g_free (X);
g_free (Y);
}
function(A) ::= identifier(X) LP RP.
{
A = SQL_EXPR (sql_function_new (X, NULL));
g_free (X);
}
function(A) ::= identifier(Z) DOT identifier(X) LP expr_list(Y) RP.
{
GSList * n = NULL;
A = SQL_EXPR (sql_function_new (X, Z));
for ( n = Y; n; n = n->next)
sql_function_add_param (SQL_FUNCTION (A), n->data);
g_slist_free (Y);
g_free (X);
}
function(A) ::= identifier(X) LP expr_list(Y) RP.
{
GSList * n = NULL;
A = SQL_EXPR (sql_function_new (X, NULL));
for ( n = Y; n; n = n->next)
sql_function_add_param (SQL_FUNCTION (A), n->data);
g_slist_free (Y);
g_free (X);
}
function(A) ::= expr(X) MOD expr(Y).
{
A = SQL_EXPR (sql_function_new ("MOD", NULL));
sql_function_add_param (SQL_FUNCTION (A), SQL_EXPR (X));
sql_function_add_param (SQL_FUNCTION (A), SQL_EXPR (Y));
}
// SqlOperation
%type operation {SqlOperation *}
%include
{
static inline SqlOperation * sql_parser_create_operation
(const gpointer X, const gpointer Y, const SqlOperationType type)
{
SqlOperation * op = sql_operation_new (type);
sql_operation_add_expr (op, SQL_EXPR (X));
if (Y) // Y == NULL for unary operations
sql_operation_add_expr (op, SQL_EXPR (Y));
return op;
}
}
operation(A) ::= MINUS expr(X). [SIGN]
{
GValue value = G_VALUE_INIT;
SqlExpr * minus = sql_value_new ();
g_value_init (&value, G_TYPE_INT);
g_value_set_int (&value, -1);
sql_value_set_value (SQL_VALUE (minus), &value);
A = sql_parser_create_operation (X, minus, SQL_OPERATION_TYPE_MULTIPLICATION);
}
operation(A) ::= PLUS expr(X). [SIGN]
{ A = sql_parser_create_operation (X, NULL, SQL_OPERATION_TYPE_SUM); }
operation(A) ::= expr(X) PLUS expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_SUM); }
operation(A) ::= expr(X) MINUS expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_REST); }
operation(A) ::= expr(X) STAR expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_MULTIPLICATION); }
operation(A) ::= expr(X) DIV expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_DIVISION); }
operation(A) ::= expr(X) OR expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_OR); }
operation(A) ::= expr(X) XOR expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_XOR); }
operation(A) ::= expr(X) AND expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_AND); }
operation(A) ::= NOT expr(X).
{ A = sql_parser_create_operation (X, NULL, SQL_OPERATION_TYPE_NOT); }
operation(A) ::= expr(X) EQ expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_EQUAL); }
operation(A) ::= expr(X) NE expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_NOT_EQUAL); }
//XXX the following should be %nonassoc operators but are %left to avoid
// conflicts. The DB will warn about a bad use if used as %left.
operation(A) ::= expr(X) GT expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_GREATER); }
operation(A) ::= expr(X) GE expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_GREATER_EQUAL); }
operation(A) ::= expr(X) LT expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_LOWER); }
operation(A) ::= expr(X) LE expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_LOWER_EQUAL); }
operation(A) ::= expr(X) LIKE expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_LIKE); }
operation(A) ::= expr(X) IS expr(Y).
{ A = sql_parser_create_operation (X, Y, SQL_OPERATION_TYPE_IS); }
// SqlValue
%type value {SqlExpr *}
value(A) ::= INTEGER(X).
{
GValue value = G_VALUE_INIT;
SqlExpr * v = sql_value_new ();
g_value_set_int (g_value_init (&value, G_TYPE_INT), atoi (X));
sql_value_set_value (SQL_VALUE (v), &value);
g_free (X);
A = v;
}
value(A) ::= FLOAT(X).
{
GValue value = G_VALUE_INIT;
SqlExpr * v = sql_value_new ();
g_value_set_double
(g_value_init (&value, G_TYPE_DOUBLE), g_ascii_strtod (X, NULL));
sql_value_set_value (SQL_VALUE (v), &value);
g_free (X);
A = v;
}
value(A) ::= STRING(X).
{
GValue value = G_VALUE_INIT;
SqlExpr * v = sql_value_new ();
g_value_set_string (g_value_init (&value, G_TYPE_STRING), X);
sql_value_set_value (SQL_VALUE (v), &value);
g_free (X);
A = v;
}
value(A) ::= BOOLEAN(X).
{
GValue value = G_VALUE_INIT;
SqlExpr * v = sql_value_new ();
g_value_set_boolean (g_value_init (&value, G_TYPE_BOOLEAN)
,(!g_strcmp0 (X, "TRUE") || !g_strcmp0 (X, "true")) ? TRUE : FALSE);
sql_value_set_value (SQL_VALUE (v), &value);
g_free (X);
A = v;
}
// Alias
%type alias {gchar *}
%destructor alias {g_free ($$);}
alias(A) ::= AS identifier(X). { A = X; }
alias(A) ::= identifier(X). { A = X; }
identifier(A) ::= IDENTIFIER(X). { A = X; }
// Placeholders
%type placeholder {gchar *}
%destructor expr {g_free ($$);}
placeholder(A) ::= PLACEHOLDER(X). { A = X; }
stmt ::= placeholder.
target ::= placeholder.
expr ::= placeholder.