Skip to content

Commit 0f8800e

Browse files
author
Krusty/Benediction
committed
[copilot edited] Allow macro calls to use line return around parenthesis
1 parent 61f6374 commit 0f8800e

3 files changed

Lines changed: 148 additions & 44 deletions

File tree

cpclib-asm/src/parser/common.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,9 +616,14 @@ pub fn parse_line_component_standard(
616616

617617
// otherwise this is a normal stuff
618618

619-
// we must have an instruction if label is missing; otherwise it is optional
619+
// We speculatively parse an instruction first; on failure, restore input so
620+
// the macro-call fallback sees the original span (e.g. NAME(args)).
621+
let before_instruction = input.checkpoint();
620622
let instruction =
621623
opt(alt((parse_z80_directive_with_block, parse_single_token))).parse_next(input)?;
624+
if instruction.is_none() {
625+
input.reset(&before_instruction);
626+
}
622627

623628
if label.is_some() && instruction.is_none() {
624629
if let Ok(call) = parse_macro_or_struct_call_inner(false, label.take().unwrap()) // label is eaten

cpclib-asm/src/parser/directives.rs

Lines changed: 128 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ use cpclib_tokens::macro_segment::tokenize_macro_body;
2727
use cpclib_tokens::{Expr, ExprFormat, FormattedExpr};
2828

2929
use super::common::{
30-
inner_code, inner_code_with_state, my_line_ending, my_many0_nocollect, my_space0, my_space1,
31-
parse_argname_and_value, parse_comma, parse_comment, parse_convertible_word,
30+
inner_code, inner_code_with_state, my_line_ending, my_many0_nocollect, my_space0,
31+
my_space0_with_newlines, my_space1, parse_argname_and_value, parse_comma,
32+
parse_comma_multiline, parse_comment, parse_convertible_word,
3233
parse_optional_argname_and_value, parse_word
3334
};
3435
use super::context;
@@ -2926,63 +2927,112 @@ pub fn parse_macro_or_struct_call_inner(
29262927

29272928
let has_parenthesis = opt((
29282929
'(',
2929-
my_space0,
2930-
not(alt((("void", my_space0).value(()), ')'.value(()))))
2930+
my_space0_with_newlines,
2931+
not(alt((("void", my_space0_with_newlines).value(()), ')'.value(()))))
29312932
))
29322933
.parse_next(input)?
29332934
.is_some();
2934-
let args: Vec<(LocatedMacroParam, &[u8])> = if peek(alt((
2935-
eof.value(()),
2936-
parse_comment.value(()),
2937-
line_ending.value(()),
2938-
':'.value(())
2939-
)))
2940-
.parse_next(input)
2941-
.is_ok()
2935+
let args: Vec<(LocatedMacroParam, &[u8])> = if !has_parenthesis
2936+
&& peek(alt((
2937+
eof.value(()),
2938+
parse_comment.value(()),
2939+
line_ending.value(()),
2940+
':'.value(())
2941+
)))
2942+
.parse_next(input)
2943+
.is_ok()
2944+
|| (has_parenthesis
2945+
&& peek(alt((eof.value(()), parse_comment.value(()), ':'.value(()))))
2946+
.parse_next(input)
2947+
.is_ok())
29422948
{
29432949
vec![]
29442950
}
29452951
else {
2946-
cut_err(
2947-
alt((
2948-
delimited(
2949-
my_space0,
2952+
if has_parenthesis {
2953+
cut_err(
2954+
alt((
2955+
delimited(
2956+
my_space0,
2957+
alt((
2958+
"()".value(()),
2959+
Caseless("(void)").value(()),
2960+
parse_comment.value(())
2961+
)),
2962+
my_space0
2963+
)
2964+
.value(Default::default()),
29502965
alt((
2951-
"()".value(()),
2952-
Caseless("(void)").value(()),
2953-
parse_comment.value(())
2954-
)),
2955-
my_space0
2956-
)
2957-
.value(Default::default()),
2966+
alt((Caseless("(void)"), "()")).value(Vec::new()),
2967+
delimited(
2968+
my_space0_with_newlines,
2969+
separated(
2970+
1..,
2971+
alt((
2972+
parse_macro_arg.with_taken(),
2973+
my_space1
2974+
.map(|space: InnerZ80Span| {
2975+
LocatedMacroParam::RawArgument(space.into())
2976+
})
2977+
.with_taken()
2978+
)),
2979+
parse_comma_multiline
2980+
),
2981+
my_space0_with_newlines
2982+
)
2983+
))
2984+
))
2985+
.context(if for_struct {
2986+
"STRUCT: error in arguments list"
2987+
}
2988+
else {
2989+
"MACRO or STRUCT: forbidden name"
2990+
})
2991+
)
2992+
.parse_next(input)?
2993+
}
2994+
else {
2995+
cut_err(
29582996
alt((
2959-
alt((Caseless("(void)"), "()")).value(Vec::new()),
2960-
separated(
2961-
1..,
2997+
delimited(
2998+
my_space0,
29622999
alt((
2963-
parse_macro_arg.with_taken(),
2964-
my_space1
2965-
.map(|space: InnerZ80Span| {
2966-
LocatedMacroParam::RawArgument(space.into())
2967-
})
2968-
.with_taken()
3000+
"()".value(()),
3001+
Caseless("(void)").value(()),
3002+
parse_comment.value(())
29693003
)),
2970-
parse_comma
3004+
my_space0
29713005
)
3006+
.value(Default::default()),
3007+
alt((
3008+
alt((Caseless("(void)"), "()")).value(Vec::new()),
3009+
separated(
3010+
1..,
3011+
alt((
3012+
parse_macro_arg.with_taken(),
3013+
my_space1
3014+
.map(|space: InnerZ80Span| {
3015+
LocatedMacroParam::RawArgument(space.into())
3016+
})
3017+
.with_taken()
3018+
)),
3019+
parse_comma
3020+
)
3021+
))
29723022
))
2973-
))
2974-
.context(if for_struct {
2975-
"STRUCT: error in arguments list"
2976-
}
2977-
else {
2978-
"MACRO or STRUCT: forbidden name"
2979-
})
2980-
)
2981-
.parse_next(input)?
3023+
.context(if for_struct {
3024+
"STRUCT: error in arguments list"
3025+
}
3026+
else {
3027+
"MACRO or STRUCT: forbidden name"
3028+
})
3029+
)
3030+
.parse_next(input)?
3031+
}
29823032
};
29833033

29843034
if has_parenthesis {
2985-
(my_space0, ')', my_space0).parse_next(input)?;
3035+
(my_space0_with_newlines, ')', my_space0).parse_next(input)?;
29863036
}
29873037

29883038
if args.len() == 1 && args.first().unwrap().0.is_empty() {
@@ -3251,4 +3301,39 @@ mod tests {
32513301
assert!(res.res.is_ok(), "Should parse successfully: {}", input);
32523302
}
32533303
}
3304+
3305+
3306+
#[test]
3307+
fn test_parse_macro_call() {
3308+
let cases = [
3309+
("MACRO1", "MACRO1()", 0),
3310+
("MACRO2", "MACRO2(void)", 0),
3311+
("MACRO3", "MACRO3(42)", 1),
3312+
("MACRO4", "MACRO4(arg1, arg2, arg3)", 3),
3313+
("MACRO5", "MACRO5([nested, list], {eval} evaluated_arg, raw_arg)", 3),
3314+
("SWITCH_VALUES", "SWITCH_VALUES(scroller_configuration_table.current_generating_code_table,scroller_configuration_table.next_generating_code_table)", 2),
3315+
("SWITCH_VALUES", "SWITCH_VALUES(\nscroller_configuration_table.current_generating_code_table,\nscroller_configuration_table.next_generating_code_table\n)", 2),
3316+
("add_to_a", "add_to_a(10)", 1)
3317+
];
3318+
3319+
for (name, call, expected_args) in cases {
3320+
let res = dbg!(parse_test(parse_macro_or_struct_call(false, false), call));
3321+
assert!(
3322+
res.res.is_ok(),
3323+
"Should parse successfully: {} (error: {:?})",
3324+
call,
3325+
res.res.err()
3326+
);
3327+
3328+
let token = dbg!(res.res.unwrap().inner.left().unwrap());
3329+
if let LocatedTokenInner::MacroCall(call_name, args) = token {
3330+
assert_eq!(call_name.as_bstr(), name.as_bytes(), "Macro name should match");
3331+
assert_eq!(args.len(), expected_args, "Expected {} arguments, got {}", expected_args, args.len());
3332+
println!("Parsed macro call: {} with args: {:?}", call_name, args);
3333+
}
3334+
else {
3335+
panic!("Expected a MacroCall token");
3336+
}
3337+
}
3338+
}
32543339
}

cpclib-asm/src/parser/parser.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,20 @@ MEND";
987987
#[test]
988988
fn test_parse_macro_call() {
989989
assert!(dbg!(parse_test(parse_line_component, "empty (void)")).is_ok());
990+
assert!(dbg!(parse_test(parse_line_component, "add_to_a(10)")).is_ok());
991+
assert!(dbg!(parse_test(
992+
inner_code,
993+
"\
994+
macro add_to_a value\n\
995+
add a, {value}\n\
996+
endm\n\
997+
\n\
998+
ld a, 5\n\
999+
add_to_a(10)\n\
1000+
add_to_a(3)\n\
1001+
"
1002+
))
1003+
.is_ok());
9901004

9911005
let res = dbg!(parse_test(
9921006
(parse_line_component, ':', parse_line_component),

0 commit comments

Comments
 (0)