Skip to content

Commit

Permalink
Move all the signature param parsing logic out of perly.y into a help…
Browse files Browse the repository at this point in the history
…er API

Provide a subsignature_*() API

Added:
 * subsignature_start()
 * subsignature_append_slurpy()
 * subsignature_append_positional()
 * subsignature_finish()

Call these from code blocks in perly.y

Make the actual parser signature struct opaque, hidden in toke.c. This
gives it much more robustness against future modifications.
  • Loading branch information
leonerd committed Feb 7, 2025
1 parent b41ec6c commit 8d1931e
Show file tree
Hide file tree
Showing 12 changed files with 1,669 additions and 1,583 deletions.
10 changes: 10 additions & 0 deletions embed.fnc
Original file line number Diff line number Diff line change
Expand Up @@ -3090,6 +3090,16 @@ ATdmp |bool |strict_utf8_to_uv \
CRp |NV |str_to_version |NN SV *sv
: Used in pp_ctl.c
p |void |sub_crush_depth|NN CV *cv
: Used in perly.y
p |void |subsignature_append_positional \
|NULLOK OP *varop \
|OPCODE defmode \
|NULLOK OP *defexpr
p |void |subsignature_append_slurpy \
|I32 sigil \
|NULLOK OP *varop
p |OP * |subsignature_finish
p |void |subsignature_start
Adp |void |suspend_compcv |NN struct suspended_compcv *buffer
ATdip |void |SvAMAGIC_off |NN SV *sv
ATdip |void |SvAMAGIC_on |NN SV *sv
Expand Down
4 changes: 4 additions & 0 deletions embed.h
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,10 @@
# define sighandler1 Perl_sighandler1
# define sighandler3 Perl_sighandler3
# define sub_crush_depth(a) Perl_sub_crush_depth(aTHX_ a)
# define subsignature_append_positional(a,b,c) Perl_subsignature_append_positional(aTHX_ a,b,c)
# define subsignature_append_slurpy(a,b) Perl_subsignature_append_slurpy(aTHX_ a,b)
# define subsignature_finish() Perl_subsignature_finish(aTHX)
# define subsignature_start() Perl_subsignature_start(aTHX)
# define sv_2num(a) Perl_sv_2num(aTHX_ a)
# define sv_clean_all() Perl_sv_clean_all(aTHX)
# define sv_clean_objs() Perl_sv_clean_objs(aTHX)
Expand Down
219 changes: 219 additions & 0 deletions op.c
Original file line number Diff line number Diff line change
Expand Up @@ -16190,6 +16190,225 @@ Perl_rcpv_copy(pTHX_ char *pv) {
return pv;
}

/* Subroutine signature parsing */

struct yy_parser_signature {
UV elems; /* number of signature elements seen so far */
UV optelems; /* number of optional signature elems seen */
char slurpy; /* the sigil of the slurpy var (or null) */
OP *elemops; /* NULL, or an OP_LINESEQ of individual element ops */
};

static void
destroy_subsignature_context(pTHX_ void *p)
{
yy_parser_signature *signature = (yy_parser_signature *)p;

if(signature->elemops)
op_free(signature->elemops);

Safefree(signature);
}

/* Called from perly.y on encountering the '(' of a subroutine signature.
* Does not return anything useful, but sets up the memory structure in
* `PL_parser->signature` that the following functions make use of.
*/

void
Perl_subsignature_start(pTHX)
{
PERL_ARGS_ASSERT_SUBSIGNATURE_START;
assert(PL_parser);

yy_parser_signature *signature;
Newx(signature, 1, yy_parser_signature);
SAVEDESTRUCTOR_X(&destroy_subsignature_context, signature);

signature->elems = 0;
signature->optelems = 0;
signature->slurpy = 0;

signature->elemops = NULL;

SAVEVPTR(PL_parser->signature);
PL_parser->signature = signature;
}

/* Appends another positional scalar parameter to the accumulated set of
* subroutine params. `varop` may be NULL, but if not it must be an OP_ARGELEM
* whose op_targ refers to an already-declared pad lexical. That lexical must
* be a scalar. It is not necessary to set the argument index in the op_aux
* field; that will be filled in by this function.
* If `defexpr` is not NULL, it gives a defaulting expression to be evaluated
* if required, according to `defmode` - one of zero, `OP_DORASSIGN` or
* `OP_ORASSIGN`.
*/

void
Perl_subsignature_append_positional(pTHX_ OP *varop, OPCODE defmode, OP *defexpr)
{
PERL_ARGS_ASSERT_SUBSIGNATURE_APPEND_POSITIONAL;
assert(PL_parser);
yy_parser_signature *signature = PL_parser->signature;
assert(signature);

if(signature->slurpy)
yyerror("Slurpy parameter not last");

UV argix = signature->elems;

if(varop) {
assert(varop->op_type == OP_ARGELEM);
assert((varop->op_private & OPpARGELEM_MASK) == OPpARGELEM_SV);
assert(varop->op_targ);
assert(PadnamePV(PadnamelistARRAY(PL_comppad_name)[varop->op_targ])[0] == '$');

/* Now fill in the argix */
cUNOP_AUXx(varop)->op_aux = INT2PTR(UNOP_AUX_item *, argix);
}

signature->elems++;

if(defexpr) {
signature->optelems++;

I32 flags = 0;
if(defmode == OP_DORASSIGN)
flags |= OPpARG_IF_UNDEF << 8;
if(defmode == OP_ORASSIGN)
flags |= OPpARG_IF_FALSE << 8;

if(defexpr->op_type == OP_NULL && !(defexpr->op_flags & OPf_KIDS))
{
/* handle '$=' special case */
if(varop)
yyerror("Optional parameter lacks default expression");
}
else {
/* a normal '=default' expression */
OP *defop = newARGDEFELEMOP(flags, defexpr, argix);

if(varop) {
varop->op_flags |= OPf_STACKED;
(void)op_sibling_splice(varop, NULL, 0, defop);
scalar(defop);
}
else
varop = newUNOP(OP_NULL, 0, defop);

LINKLIST(varop);
/* NB: normally the first child of a logop is executed before the
* logop, and it pushes a boolean result ready for the logop. For
* ARGDEFELEM, the op itself does the boolean calculation, so set
* the first op to it instead.
*/
varop->op_next = defop;
defexpr->op_next = varop;
}
}
else
if(signature->optelems)
yyerror("Mandatory parameter follows optional parameter");

if(varop) {
signature->elemops = op_append_list(OP_LINESEQ, signature->elemops,
newSTATEOP(0, NULL, varop));
}
}

/* Appends a final slurpy parameter to the accumulated set of subroutine
* params. `varop` may be NULL, but if not it must be an OP_ARGELEM whose
* op_targ refers to an already-declared pad lexical. That lexical must match
* the `sigil` parameter. It is not necessary to set the argument index in the
* op_aux field; that will be filled in by this function.
*/

void
Perl_subsignature_append_slurpy(pTHX_ I32 sigil, OP *varop)
{
PERL_ARGS_ASSERT_SUBSIGNATURE_APPEND_SLURPY;
assert(PL_parser);
yy_parser_signature *signature = PL_parser->signature;
assert(signature);
assert(sigil == '@' || sigil == '%');

if(signature->slurpy)
yyerror("Multiple slurpy parameters not allowed");

UV argix = signature->elems;

if(varop) {
assert(varop->op_type == OP_ARGELEM);
assert((varop->op_private & OPpARGELEM_MASK) ==
((sigil == '@') ? OPpARGELEM_AV : OPpARGELEM_HV));
assert(varop->op_targ);
assert(PadnamePV(PadnamelistARRAY(PL_comppad_name)[varop->op_targ])[0] == sigil);

/* Now fill in the argix */
cUNOP_AUXx(varop)->op_aux = INT2PTR(UNOP_AUX_item *, argix);
}

signature->slurpy = (char)sigil;

if(varop) {
/* TODO: assert() the sigil of the pad variable matches */
signature->elemops = op_append_list(OP_LINESEQ, signature->elemops,
newSTATEOP(0, NULL, varop));
}
}

/* Called from perly.y on encountering the closing `)` of a subroutine
* signature. This creates the optree fragment responsible for processing all
* the accumulated subroutine params, to be inserted at the start of the
* subroutine's optree.
*/

OP *
Perl_subsignature_finish(pTHX)
{
PERL_ARGS_ASSERT_SUBSIGNATURE_FINISH;
assert(PL_parser);
yy_parser_signature *signature = PL_parser->signature;
assert(signature);

OP *sigops = signature->elemops;
signature->elemops = NULL;

struct op_argcheck_aux *aux = (struct op_argcheck_aux *)
PerlMemShared_malloc( sizeof(struct op_argcheck_aux));

aux->params = signature->elems;
aux->opt_params = signature->optelems;
aux->slurpy = signature->slurpy;

OP *check = newUNOP_AUX(OP_ARGCHECK, 0, NULL, (UNOP_AUX_item *)aux);

sigops = op_prepend_elem(OP_LINESEQ,
check,
sigops);

/* a nextstate right at the beginning */
sigops = op_prepend_elem(OP_LINESEQ,
newSTATEOP(0, NULL, NULL),
sigops);

/* a nextstate at the end handles context correctly for an empty sub body */
sigops = op_append_elem(OP_LINESEQ, sigops,
newSTATEOP(0, NULL, NULL));

/* wrap the list of arg ops in a NULL aux op.
This serves two purposes. First, it makes the arg list a separate
subtree from the body of the sub, and secondly the null op may in future
be upgraded to an OP_SIGNATURE when implemented. For now leave it as
ex-argcheck */

OP *ret = newUNOP_AUX(OP_ARGCHECK, 0, sigops, NULL);
op_null(ret);

return ret;
}

/*
* ex: set ts=8 sts=4 sw=4 et:
*/
9 changes: 5 additions & 4 deletions parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ typedef struct yy_lexshared {
SV *re_eval_str; /* "(?{...})" text */
} LEXSHARED;

/* Opaque struct of data relevant during parsing and construction of a
* subroutine signature. Defined and used exclusively by op.c */
typedef struct yy_parser_signature yy_parser_signature;

typedef struct yy_parser {

/* parser state */
Expand Down Expand Up @@ -112,10 +116,7 @@ typedef struct yy_parser {
line_t herelines; /* number of lines in here-doc */
line_t preambling; /* line # when processing $ENV{PERL5DB} */

/* these are valid while parsing a subroutine signature */
UV sig_elems; /* number of signature elements seen so far */
UV sig_optelems; /* number of optional signature elems seen */
char sig_slurpy; /* the sigil of the slurpy var (or null) */
yy_parser_signature *signature; /* parser state of a subroutine signature */
bool sig_seen; /* the currently parsing sub has a signature */

bool recheck_charset_validity;
Expand Down
Loading

0 comments on commit 8d1931e

Please sign in to comment.