Skip to content

Commit 9f81b54

Browse files
committed
Merge branch 'feat-match' into devel
2 parents 4e37321 + d07ec1e commit 9f81b54

14 files changed

Lines changed: 782 additions & 5 deletions

File tree

compiler/globals.pas

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ interface
6868
m_property,m_default_inline,m_except,m_multiline_strings];
6969
unleashedmodeswitches = objfpcmodeswitches+[m_default_ansistring,m_underscoreisseparator,m_duplicate_names,
7070
m_advanced_records,m_array_operators,m_anonymous_functions,m_function_references,
71-
m_statement_expressions,m_array_equality,m_inline_var,m_tuples,m_multiline_strings,
71+
m_statement_expressions,m_array_equality,m_inline_var,m_tuples,m_match,m_multiline_strings,
7272
m_multi_var_init,m_unleashed];
7373
tpmodeswitches =
7474
[m_tp7,m_tp_procvar,m_duplicate_names];

compiler/globtype.pas

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,8 @@ ttargetswitchinfo = record
563563
m_no_rtti, { hides RTTI ASCII text }
564564
m_inline_var, { allow inline variable declarations inside statement blocks }
565565
m_multi_var_init, { allow initializing multiple variables in one declaration }
566-
m_tuples { allow anonymous tuple types as function return types and related literals }
566+
m_tuples, { allow anonymous tuple types as function return types and related literals }
567+
m_match { match statement with first-match and fallthrough modes }
567568
);
568569
tmodeswitches = set of tmodeswitch;
569570

@@ -777,7 +778,8 @@ ttargetswitchinfo = record
777778
'NORTTI',
778779
'INLINEVARS',
779780
'MULTIVARINIT',
780-
'TUPLES'
781+
'TUPLES',
782+
'MATCH'
781783
);
782784

783785

compiler/pexpr.pas

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ (current_procinfo.procdef.returndef.typ=recorddef)
496496

497497
in_leave :
498498
begin
499-
if m_mac in current_settings.modeswitches then
499+
if [m_mac,m_match]*current_settings.modeswitches<>[] then
500500
statement_syssym:=cbreaknode.create
501501
else
502502
begin

compiler/pstatmnt.pas

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,285 @@ implementation
407407
end;
408408

409409

410+
function match_statement(is_expr:boolean=false) : tnode;
411+
{ Match statement: first-match (if-elseif) or fallthrough (match all).
412+
Subject-based: match EXPR of pat: stmt; end;
413+
Condition-based: match cond: stmt; end; }
414+
415+
function is_wildcard_underscore : boolean; inline;
416+
begin
417+
result:=(current_scanner.token=_ID) and (current_scanner.pattern='_');
418+
end;
419+
420+
procedure append_else(var ifchain:tnode;elseblock:tnode);
421+
var
422+
tailnode : tnode;
423+
begin
424+
if ifchain=nil then
425+
ifchain:=elseblock
426+
else
427+
begin
428+
tailnode:=ifchain;
429+
while assigned(tifnode(tailnode).t1) do
430+
tailnode:=tifnode(tailnode).t1;
431+
tifnode(tailnode).t1:=elseblock;
432+
end;
433+
end;
434+
435+
function parse_branch_cond(has_subject:boolean;subject:tnode) : tnode;
436+
{ Parse pattern(s) for a branch. Subject mode supports comma-separated
437+
patterns (OR'd) and tuple patterns with _ wildcards. }
438+
var
439+
pat,cond : tnode;
440+
fields : array of tnode;
441+
fieldcount,i,symidx : integer;
442+
sym : tsym;
443+
recdef : trecorddef;
444+
begin
445+
{ tuple pattern with potential _ wildcards }
446+
if has_subject and (current_scanner.token=_LKLAMMER) and
447+
assigned(subject.resultdef) and (subject.resultdef.typ=recorddef) and
448+
(df_tuple in subject.resultdef.defoptions) then
449+
begin
450+
consume(_LKLAMMER);
451+
fieldcount:=0;
452+
setlength(fields,8);
453+
repeat
454+
if fieldcount>=length(fields) then
455+
setlength(fields,fieldcount*2);
456+
if (current_scanner.token=_ID) and (current_scanner.pattern='_') then
457+
begin
458+
fields[fieldcount]:=nil;
459+
consume(_ID);
460+
end
461+
else
462+
begin
463+
fields[fieldcount]:=comp_expr([ef_accept_equal]);
464+
do_typecheckpass(fields[fieldcount]);
465+
end;
466+
inc(fieldcount);
467+
until not try_to_consume(_COMMA);
468+
{ single expression in parens = parenthesized expr, not tuple }
469+
if fieldcount=1 then
470+
begin
471+
consume(_RKLAMMER);
472+
if fields[0]=nil then
473+
result:=cordconstnode.create(1,pasbool1type,false)
474+
else
475+
result:=caddnode.create(equaln,subject.getcopy,fields[0]);
476+
exit;
477+
end;
478+
consume(_RKLAMMER);
479+
{ build per-field AND chain, skipping wildcards }
480+
recdef:=trecorddef(subject.resultdef);
481+
cond:=nil;
482+
i:=0;
483+
for symidx:=0 to recdef.symtable.symlist.count-1 do
484+
begin
485+
sym:=tsym(recdef.symtable.symlist[symidx]);
486+
if sym.typ<>fieldvarsym then
487+
continue;
488+
if i>=fieldcount then
489+
break;
490+
if fields[i]<>nil then
491+
begin
492+
pat:=caddnode.create(equaln,
493+
csubscriptnode.create(tfieldvarsym(sym),subject.getcopy),
494+
fields[i]);
495+
if cond=nil then
496+
cond:=pat
497+
else
498+
cond:=caddnode.create(andn,cond,pat);
499+
end;
500+
inc(i);
501+
end;
502+
if cond=nil then
503+
cond:=cordconstnode.create(1,pasbool1type,false);
504+
result:=cond;
505+
end
506+
else
507+
begin
508+
{ normal pattern with optional comma-separated OR }
509+
pat:=comp_expr([ef_accept_equal]);
510+
do_typecheckpass(pat);
511+
if has_subject then
512+
begin
513+
result:=caddnode.create(equaln,subject.getcopy,pat);
514+
while try_to_consume(_COMMA) do
515+
begin
516+
pat:=comp_expr([ef_accept_equal]);
517+
do_typecheckpass(pat);
518+
result:=caddnode.create(orn,result,
519+
caddnode.create(equaln,subject.getcopy,pat));
520+
end;
521+
end
522+
else
523+
result:=pat;
524+
end;
525+
end;
526+
527+
var
528+
subject,cond,stmt,ifchain,firstcond,walknode,stmtblock : tnode;
529+
fallthrough,has_subject,has_catchall : boolean;
530+
stmts,exprstatements : tstatementnode;
531+
resultdef : tdef;
532+
resultvar : ttempcreatenode;
533+
begin
534+
consume(_MATCH);
535+
{ check for 'all' (context-sensitive) }
536+
fallthrough:=(current_scanner.token=_ID) and (current_scanner.pattern='ALL');
537+
if fallthrough then
538+
consume(_ID);
539+
{ determine mode: subject-based (match X of) vs condition-based }
540+
has_subject:=false;
541+
subject:=nil;
542+
firstcond:=nil;
543+
if not is_wildcard_underscore then
544+
begin
545+
firstcond:=comp_expr([ef_accept_equal]);
546+
do_typecheckpass(firstcond);
547+
if current_scanner.token=_OF then
548+
begin
549+
has_subject:=true;
550+
subject:=firstcond;
551+
set_varstate(subject,vs_read,[vsf_must_be_valid]);
552+
consume(_OF);
553+
firstcond:=nil;
554+
end;
555+
end;
556+
if fallthrough then
557+
begin
558+
{ fallthrough: independent if-statements in repeat..until true }
559+
stmtblock:=internalstatements(stmts);
560+
repeat
561+
if is_wildcard_underscore then
562+
begin
563+
consume(_ID);
564+
consume(_COLON);
565+
addstatement(stmts,statement);
566+
if not(current_scanner.token in [_END]) then
567+
consume(_SEMICOLON);
568+
break;
569+
end;
570+
if firstcond<>nil then
571+
begin
572+
cond:=firstcond;
573+
firstcond:=nil;
574+
end
575+
else
576+
cond:=parse_branch_cond(has_subject,subject);
577+
consume(_COLON);
578+
addstatement(stmts,cifnode.create(cond,statement,nil));
579+
if not(current_scanner.token in [_ELSE,_OTHERWISE,_END]) then
580+
consume(_SEMICOLON);
581+
until current_scanner.token in [_ELSE,_OTHERWISE,_END];
582+
if try_to_consume(_ELSE) or try_to_consume(_OTHERWISE) then
583+
addstatement(stmts,statements_til_end)
584+
else
585+
consume(_END);
586+
if has_subject then
587+
subject.free;
588+
result:=cwhilerepeatnode.create(
589+
cordconstnode.create(1,pasbool1type,false),
590+
stmtblock,false,true);
591+
end
592+
else
593+
begin
594+
{ first-match: if-elseif chain }
595+
resultdef:=nil;
596+
has_catchall:=false;
597+
ifchain:=nil;
598+
repeat
599+
if is_wildcard_underscore then
600+
begin
601+
has_catchall:=true;
602+
consume(_ID);
603+
consume(_COLON);
604+
if is_expr then
605+
begin
606+
stmt:=expr(true);
607+
resultdef:=branch_type(resultdef,stmt.resultdef);
608+
end
609+
else
610+
stmt:=statement;
611+
append_else(ifchain,stmt);
612+
if not(current_scanner.token in [_END]) then
613+
consume(_SEMICOLON);
614+
break;
615+
end;
616+
if firstcond<>nil then
617+
begin
618+
cond:=firstcond;
619+
firstcond:=nil;
620+
end
621+
else
622+
cond:=parse_branch_cond(has_subject,subject);
623+
consume(_COLON);
624+
if is_expr then
625+
begin
626+
stmt:=expr(true);
627+
resultdef:=branch_type(resultdef,stmt.resultdef);
628+
end
629+
else
630+
stmt:=statement;
631+
stmt:=cifnode.create(cond,stmt,nil);
632+
append_else(ifchain,stmt);
633+
if not(current_scanner.token in [_ELSE,_OTHERWISE,_END]) then
634+
consume(_SEMICOLON);
635+
until current_scanner.token in [_ELSE,_OTHERWISE,_END];
636+
if try_to_consume(_ELSE) or try_to_consume(_OTHERWISE) then
637+
begin
638+
has_catchall:=true;
639+
if is_expr then
640+
begin
641+
stmt:=expr(true);
642+
resultdef:=branch_type(resultdef,stmt.resultdef);
643+
end
644+
else
645+
stmt:=statements_til_end;
646+
append_else(ifchain,stmt);
647+
end
648+
else if is_expr and not has_catchall then
649+
consume(_ELSE)
650+
else
651+
consume(_END);
652+
if has_subject then
653+
subject.free;
654+
if not is_expr then
655+
result:=ifchain
656+
else
657+
begin
658+
{ expression mode: wrap branches in temp var assignments }
659+
result:=internalstatements(exprstatements);
660+
resultvar:=ctempcreatenode.create(resultdef,resultdef.size,tt_persistent,true);
661+
addstatement(exprstatements,resultvar);
662+
{ walk if-chain, wrap each branch value in assignment }
663+
walknode:=ifchain;
664+
while walknode.nodetype=ifn do
665+
begin
666+
tifnode(walknode).right:=cassignmentnode.create(
667+
ctemprefnode.create(resultvar),tifnode(walknode).right);
668+
if assigned(tifnode(walknode).t1) and (tifnode(walknode).t1.nodetype=ifn) then
669+
walknode:=tifnode(walknode).t1
670+
else
671+
begin
672+
if assigned(tifnode(walknode).t1) then
673+
tifnode(walknode).t1:=cassignmentnode.create(
674+
ctemprefnode.create(resultvar),tifnode(walknode).t1);
675+
break;
676+
end;
677+
end;
678+
if ifchain.nodetype<>ifn then
679+
{ single catch-all value }
680+
ifchain:=cassignmentnode.create(ctemprefnode.create(resultvar),ifchain);
681+
addstatement(exprstatements,ifchain);
682+
addstatement(exprstatements,ctempdeletenode.create_normal_temp(resultvar));
683+
addstatement(exprstatements,ctemprefnode.create(resultvar));
684+
end;
685+
end;
686+
end;
687+
688+
410689
function repeat_statement : tnode;
411690

412691
var
@@ -2966,6 +3245,8 @@ ((hdef.typ = arraydef) or (hdef.typ = recorddef)) then
29663245
code:=if_statement;
29673246
_CASE :
29683247
code:=case_statement;
3248+
_MATCH :
3249+
code:=match_statement;
29693250
_REPEAT :
29703251
code:=repeat_statement;
29713252
_WHILE :
@@ -3347,6 +3628,7 @@ ((hdef.typ = arraydef) or (hdef.typ = recorddef)) then
33473628
case current_scanner.token of
33483629
_IF: p1:=if_statement(true);
33493630
_CASE: p1:=case_statement(true);
3631+
_MATCH: p1:=match_statement(true);
33503632
_TRY: p1:=try_statement(true);
33513633
else
33523634
result:=false;

compiler/tokens.pas

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ interface
169169
_INDEX,
170170
_LABEL,
171171
_LOCAL,
172+
_MATCH,
172173
_RAISE,
173174
_UNTIL,
174175
_WHILE,
@@ -518,6 +519,7 @@ tokenidxrec=record
518519
(str:'INDEX' ;special:false;keyword:[m_none];op:NOTOKEN),
519520
(str:'LABEL' ;special:false;keyword:alllanguagemodes;op:NOTOKEN),
520521
(str:'LOCAL' ;special:false;keyword:[m_none];op:NOTOKEN),
522+
(str:'MATCH' ;special:false;keyword:[m_match];op:NOTOKEN),
521523
(str:'RAISE' ;special:false;keyword:[m_except];op:NOTOKEN),
522524
(str:'UNTIL' ;special:false;keyword:alllanguagemodes;op:NOTOKEN),
523525
(str:'WHILE' ;special:false;keyword:alllanguagemodes;op:NOTOKEN),

compiler/utils/ppuutils/ppudump.pp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2499,7 +2499,8 @@ (* tsettings = record
24992499
'm_no_rtti', { hides RTTI ASCII text }
25002500
'm_inline_var', { allow inline variable declarations inside statement blocks }
25012501
'm_multi_var_init', { allow initializing multiple variables in one declaration }
2502-
'm_tuples' { allow anonymous tuple types as function return types and related literals }
2502+
'm_tuples', { allow anonymous tuple types as function return types and related literals }
2503+
'm_match' { match statement with first-match and fallthrough modes }
25032504
);
25042505
{ optimizer }
25052506
optimizerswitchname : array[toptimizerswitch] of string[50] =

0 commit comments

Comments
 (0)