1use std::{
7 collections::BTreeMap,
8 collections::BTreeSet,
9 fmt::Debug,
10 ops::{BitOr, Range, Shl},
11};
12
13pub(crate) mod helpers;
14mod symex;
15#[cfg(test)]
16mod tests;
17
18use chumsky::error::Rich;
19use chumsky::extension::v1::Ext;
20use chumsky::extension::v1::ExtParser;
21use chumsky::extra::{Full, ParserExtra};
22use chumsky::input::InputRef;
23use chumsky::input::{Emitter, MapExtra, Stream, ValueInput};
24
25use chumsky::Boxed;
26use chumsky::Parser;
27use chumsky::prelude::{Input, IterParser, Recursive, SimpleSpan, choice, just, one_of, recursive};
28use chumsky::select;
29
30use crate::collections::OneOrMore;
31
32use super::ast::{
33 ArithmeticExpression, Atom, CommaDelimitedFragment, Commas, CommasOrInstruction, ConfigValue,
34 Equality, EqualityValue, FragmentWithHold, HoldBit, InstructionFragment, LiteralValue,
35 Operator, Origin, RegisterContaining, RegistersContaining, SignedAtom, SpannedSymbolOrLiteral,
36 SymbolOrLiteral, Tag, TaggedProgramInstruction, UntaggedProgramInstruction,
37};
38use super::lexer::{self};
39use super::manuscript::{
40 MacroBodyLine, MacroDefinition, MacroDummyParameters, MacroInvocation, MacroParameter,
41 MacroParameterBindings, MacroParameterValue, ManuscriptLine, ManuscriptMetaCommand, SourceFile,
42 manuscript_lines_to_source_file,
43};
44use super::span::{Span, Spanned, span};
45use super::state::{NumeralMode, State};
46use super::symbol::SymbolName;
47use base::charset::Script;
48use base::prelude::*;
49use helpers::Sign;
50use symex::SymexSyllableRule;
51
52pub(crate) type ExtraWithoutContext<'a> = Full<Rich<'a, lexer::Token>, State<'a>, ()>;
53
54use lexer::Token as Tok;
55
56fn maybe_sign<'a, I>(
57 script_required: Script,
58) -> impl Parser<'a, I, Option<(Sign, Span)>, ExtraWithoutContext<'a>> + Clone
59where
60 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
61{
62 choice((
63 just(Tok::Plus(script_required)).to(Sign::Plus),
64 just(Tok::Minus(script_required)).to(Sign::Minus),
65 ))
66 .map_with(|maybe_sign, extra| (maybe_sign, extra.span()))
67 .or_not()
68}
69
70#[derive(Debug, PartialEq, Eq, Clone)]
71enum BitDesignatorValidation {
72 Good(LiteralValue),
73 Suspect(u8, u8, LiteralValue),
74}
75
76fn make_bit_designator_literal(
77 script: Script,
78 quarter: u8,
79 bitnum: u8,
80 span: Span,
81) -> BitDesignatorValidation {
82 fn build(q: u64, b: u64) -> Unsigned36Bit {
83 let qmod4: u64 = q % 4_u64;
89 Unsigned36Bit::ZERO.bitor(qmod4.shl(4_u32).bitor(b))
90 }
91 let value = LiteralValue::from((span, script, build(quarter.into(), bitnum.into())));
99 match (quarter, bitnum) {
100 (1..=4, 1..=9) | (4, 10) => BitDesignatorValidation::Good(value),
101 _ => BitDesignatorValidation::Suspect(quarter, bitnum, value),
102 }
103}
104
105type MyEmitter<'a, I> = Emitter<
108 <chumsky::extra::Full<chumsky::error::Rich<'a, lexer::Token>, State<'a>, ()> as ParserExtra<
109 'a,
110 I,
111 >>::Error,
112>;
113
114fn warn_bad_bitpos<'src, I>(
115 validated: BitDesignatorValidation,
116 extra: &mut MapExtra<'src, '_, I, ExtraWithoutContext<'src>>,
117 emitter: &mut MyEmitter<'src, I>,
118) -> LiteralValue
119where
120 I: Input<'src, Token = Tok, Span = Span> + ValueInput<'src>,
121{
122 match validated {
123 BitDesignatorValidation::Suspect(q, b, literal) => {
127 emitter.emit(Rich::custom(
128 extra.span(),
129 format!("bit position {q}\u{00B7}{b} does not exist"),
130 ));
131 literal
132 }
133 BitDesignatorValidation::Good(literal) => literal,
134 }
135}
136
137fn bit_selector<'a, I>(
138 script_required: Script,
139) -> impl Parser<'a, I, LiteralValue, ExtraWithoutContext<'a>> + Clone
140where
141 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
142{
143 select! {
144 Tok::BitPosition(script, quarter, bits) if script == script_required => (quarter, bits)
145 }
146 .try_map_with(move |(quarter, bit), extra| {
147 match quarter.as_str().parse::<u8>() {
150 Err(_) => Err(Rich::custom(
151 extra.span(),
152 format!("quarter {quarter} is not a valid decimal number"),
153 )),
154 Ok(q) => match bit.as_str().parse::<u8>() {
155 Ok(bit) => Ok(make_bit_designator_literal(
156 script_required,
157 q,
158 bit,
159 extra.span(),
160 )),
161 Err(_) => Err(Rich::custom(
162 extra.span(),
163 format!("bit position {bit} is not a valid decimal number"),
164 )),
165 },
166 }
167 })
168 .validate(warn_bad_bitpos)
169}
170
171fn literal<'a, I>(
172 script_required: Script,
173) -> impl Parser<'a, I, LiteralValue, ExtraWithoutContext<'a>> + Clone
174where
175 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
176{
177 let plain_literal = {
178 let digits = select! {
179 Tok::Digits(script, n) if script == script_required => n,
180 };
181
182 digits.try_map_with(move |digits_token_payload, extra| {
183 let state: &State = extra.state();
184 let mode: NumeralMode = state.numeral_mode;
185 match digits_token_payload.make_num(mode) {
186 Ok(value) => Ok(LiteralValue::from((extra.span(), script_required, value))),
187 Err(e) => Err(Rich::custom(extra.span(), e.to_string())),
188 }
189 })
190 };
191 choice((bit_selector(script_required), plain_literal)).labelled("numeric literal")
192}
193
194fn here<'a, I>(
195 script_required: Script,
196) -> impl Parser<'a, I, SymbolOrLiteral, ExtraWithoutContext<'a>> + Clone
197where
198 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
199{
200 select! {
201 Tok::Hash(script) if script == script_required => (),
202 }
203 .map_with(move |(), extra| SymbolOrLiteral::Here(script_required, extra.span()))
204}
205
206fn opcode_code(s: &str) -> Option<(Unsigned5Bit, Unsigned6Bit)> {
207 match s {
208 "IOS" => Some((u5!(0o00), u6!(0o04))),
209 "JMP" => Some((u5!(0o00), u6!(0o05))),
210 "BRC" => Some((u5!(0o01), u6!(0o05))),
211 "JPS" => Some((u5!(0o02), u6!(0o05))),
212 "BRS" => Some((u5!(0o03), u6!(0o05))),
213 "JPQ" => Some((u5!(0o14), u6!(0o05))),
214 "BPQ" => Some((u5!(0o15), u6!(0o05))),
215 "JES" => Some((u5!(0o16), u6!(0o05))),
216 "JPD" => Some((u5!(0o20), u6!(0o05))),
217 "BRD" => Some((u5!(0o21), u6!(0o05))),
218 "JDS" => Some((u5!(0o22), u6!(0o05))),
219 "BDS" => Some((u5!(0o23), u6!(0o05))),
220 "JPX" => Some((u5!(0o00), u6!(0o06))),
221 "JNX" => Some((u5!(0o00), u6!(0o07))),
222 "AUX" => Some((u5!(0o00), u6!(0o10))),
223 "RSX" => Some((u5!(0o00), u6!(0o11))),
224 "SKX" | "REX" | "SEX" => Some((u5!(0o00), u6!(0o12))),
225 "INX" => Some((u5!(0o02), u6!(0o12))),
226 "DEX" => Some((u5!(0o03), u6!(0o12))),
227 "SXD" => Some((u5!(0o04), u6!(0o12))),
228 "SXL" => Some((u5!(0o06), u6!(0o12))),
229 "SXG" => Some((u5!(0o07), u6!(0o12))),
230 "RXF" => Some((u5!(0o10), u6!(0o12))),
231 "RXD" => Some((u5!(0o20), u6!(0o12))),
232 "RFD" => Some((u5!(0o30), u6!(0o12))),
233 "EXX" => Some((u5!(0o00), u6!(0o14))),
234 "ADX" => Some((u5!(0o00), u6!(0o15))),
235 "DPX" => Some((u5!(0o00), u6!(0o16))),
236 "SKM" => Some((u5!(0o00), u6!(0o17))),
237 "MKC" => Some((u5!(0o01), u6!(0o17))),
238 "MKZ" => Some((u5!(0o02), u6!(0o17))),
239 "MKN" => Some((u5!(0o03), u6!(0o17))),
240 "SKU" => Some((u5!(0o10), u6!(0o17))),
241 "SUC" => Some((u5!(0o11), u6!(0o17))),
242 "SUZ" => Some((u5!(0o12), u6!(0o17))),
243 "SUN" => Some((u5!(0o13), u6!(0o17))),
244 "SKZ" => Some((u5!(0o20), u6!(0o17))),
245 "SZC" => Some((u5!(0o21), u6!(0o17))),
246 "SZZ" => Some((u5!(0o22), u6!(0o17))),
247 "SZN" => Some((u5!(0o23), u6!(0o17))),
248 "SKN" => Some((u5!(0o30), u6!(0o17))),
249 "SNC" => Some((u5!(0o31), u6!(0o17))),
250 "SNZ" => Some((u5!(0o32), u6!(0o17))),
251 "SNN" => Some((u5!(0o33), u6!(0o17))),
252 "CYR" => Some((u5!(0o04), u6!(0o17))),
253 "MCR" => Some((u5!(0o05), u6!(0o17))),
254 "MZR" => Some((u5!(0o06), u6!(0o17))),
255 "MNR" => Some((u5!(0o07), u6!(0o17))),
256 "SNR" => Some((u5!(0o34), u6!(0o17))),
257 "SZR" => Some((u5!(0o24), u6!(0o17))),
258 "SUR" => Some((u5!(0o14), u6!(0o17))),
259 "LDE" => Some((u5!(0o00), u6!(0o20))),
260 "SPF" => Some((u5!(0o00), u6!(0o21))),
261 "SPG" => Some((u5!(0o00), u6!(0o22))),
262 "LDA" => Some((u5!(0o00), u6!(0o24))),
263 "LDB" => Some((u5!(0o00), u6!(0o25))),
264 "LDC" => Some((u5!(0o00), u6!(0o26))),
265 "LDD" => Some((u5!(0o00), u6!(0o27))),
266 "STE" => Some((u5!(0o00), u6!(0o30))),
267 "FLF" => Some((u5!(0o00), u6!(0o31))),
268 "FLG" => Some((u5!(0o00), u6!(0o32))),
269 "STA" => Some((u5!(0o00), u6!(0o34))),
270 "STB" => Some((u5!(0o00), u6!(0o35))),
271 "STC" => Some((u5!(0o00), u6!(0o36))),
272 "STD" => Some((u5!(0o00), u6!(0o37))),
273 "ITE" => Some((u5!(0o00), u6!(0o40))),
274 "ITA" => Some((u5!(0o00), u6!(0o41))),
275 "UNA" => Some((u5!(0o00), u6!(0o42))),
276 "SED" => Some((u5!(0o00), u6!(0o43))),
277 "JOV" => Some((u5!(0o00), u6!(0o45))),
278 "JPA" => Some((u5!(0o00), u6!(0o46))),
279 "JNA" => Some((u5!(0o00), u6!(0o47))),
280 "EXA" => Some((u5!(0o00), u6!(0o54))),
281 "INS" => Some((u5!(0o00), u6!(0o55))),
282 "COM" => Some((u5!(0o00), u6!(0o56))),
283 "TSD" => Some((u5!(0o00), u6!(0o57))),
284 "CYA" => Some((u5!(0o00), u6!(0o60))),
285 "CYB" => Some((u5!(0o00), u6!(0o61))),
286 "CAB" => Some((u5!(0o00), u6!(0o62))),
287 "NOA" => Some((u5!(0o00), u6!(0o64))),
288 "DSA" => Some((u5!(0o00), u6!(0o65))),
289 "NAB" => Some((u5!(0o00), u6!(0o66))),
290 "ADD" => Some((u5!(0o00), u6!(0o67))),
291 "SCA" => Some((u5!(0o00), u6!(0o70))),
292 "SCB" => Some((u5!(0o00), u6!(0o71))),
293 "SAB" => Some((u5!(0o00), u6!(0o72))),
294 "TLY" => Some((u5!(0o00), u6!(0o74))),
295 "DIV" => Some((u5!(0o00), u6!(0o75))),
296 "MUL" => Some((u5!(0o00), u6!(0o76))),
297 "SUB" => Some((u5!(0o00), u6!(0o77))),
298 _ => None,
299 }
300}
301
302pub(super) fn symbol_or_literal<'a, I>(
303 script_required: Script,
304) -> impl Parser<'a, I, SymbolOrLiteral, ExtraWithoutContext<'a>> + Clone
305where
306 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
307{
308 choice((
309 literal(script_required).map(SymbolOrLiteral::Literal),
310 symex::symex_syllable(script_required).map_with(move |name, extra| {
311 SymbolOrLiteral::Symbol(script_required, SymbolName::from(name), extra.span())
312 }),
313 ))
314 .labelled(match script_required {
315 Script::Super => "superscript single-syllable symbol or literal",
316 Script::Normal => "single-syllable symbol or literal",
317 Script::Sub => "subscript single-syllable symbol or literal",
318 })
319}
320
321fn opcode_to_literal(code: Unsigned6Bit, cfgbits: Unsigned5Bit, span: Span) -> LiteralValue {
322 let bits = Unsigned36Bit::ZERO
323 .bitor(u64::from(code).shl(24))
324 .bitor(u64::from(cfgbits).shl(30))
325 .bitor(helpers::opcode_auto_hold_bit(code));
326 LiteralValue::from((span, Script::Normal, bits))
327}
328
329pub(super) fn opcode<'a, I>() -> impl Parser<'a, I, LiteralValue, ExtraWithoutContext<'a>> + Clone
330where
331 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
332{
333 symex::symex_syllable(Script::Normal)
334 .filter(|mnemonic| opcode_code(mnemonic).is_some())
335 .try_map_with(|mnemonic, extra| match opcode_code(mnemonic.as_str()) {
336 Some((cfgbits, code)) => Ok(opcode_to_literal(code, cfgbits, extra.span())),
337 None => Err(Rich::custom(
338 extra.span(),
339 format!("'{mnemonic}' is not an opcode mnemonic"),
340 )),
341 })
342 .labelled("opcode")
343}
344
345fn named_symbol<'a, I>(
346 rule: SymexSyllableRule,
347 script_required: Script,
348) -> impl Parser<'a, I, SymbolName, ExtraWithoutContext<'a>> + Clone
349where
350 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
351{
352 symex::parse_symex(rule, script_required)
353}
354
355pub(super) fn operator<'a, I>(
356 script_required: Script,
357) -> impl Parser<'a, I, Operator, ExtraWithoutContext<'a>> + Clone
358where
359 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
360{
361 select! {
362 Tok::Solidus(script) if script_required == script => Operator::Divide,
365 Tok::Plus(Script::Normal) => Operator::Add,
366 Tok::Times(got) if got == script_required => Operator::Multiply,
367 Tok::LogicalOr(got) if got == script_required => Operator::LogicalOr,
368 Tok::LogicalAnd(got) if got == script_required => Operator::LogicalAnd,
369 Tok::Minus(got) if script_required == got => Operator::Subtract,
370 Tok::Plus(got) if script_required == got => Operator::Add,
371 }
372 .labelled("arithmetic operator")
373}
374
375fn asterisk_indirection_fragment<'srcbody, I>()
376-> impl Parser<'srcbody, I, InstructionFragment, ExtraWithoutContext<'srcbody>> + Clone
377where
378 I: Input<'srcbody, Token = Tok, Span = Span> + ValueInput<'srcbody>,
379{
380 just(Tok::Asterisk(Script::Normal))
381 .map_with(|_, extra| InstructionFragment::DeferredAddressing(extra.span()))
382}
383
384fn make_pipe_construct(
390 (p, (t, maybe_spanned_q)): (
391 SpannedSymbolOrLiteral,
392 (SpannedSymbolOrLiteral, Option<(InstructionFragment, Span)>),
393 ),
394) -> InstructionFragment {
395 let tqspan = span(
398 t.span.start..if let Some((_, q_span)) = maybe_spanned_q {
399 q_span.end
400 } else {
401 t.span.end
402 },
403 );
404
405 let t: CommaDelimitedFragment = CommaDelimitedFragment {
406 span: t.span,
407 leading_commas: None,
408 holdbit: HoldBit::Unspecified,
409 fragment: InstructionFragment::Arithmetic(ArithmeticExpression::from(Atom::from(t.item))),
410 trailing_commas: None,
411 };
412 let qt: UntaggedProgramInstruction = if let Some((q, q_span)) = maybe_spanned_q {
413 UntaggedProgramInstruction::from(OneOrMore::with_tail(
414 CommaDelimitedFragment {
415 span: q_span,
416 holdbit: HoldBit::Unspecified,
417 leading_commas: None,
418 fragment: q,
419 trailing_commas: None,
420 },
421 vec![t],
422 ))
423 } else {
424 UntaggedProgramInstruction::from(OneOrMore::new(t))
425 };
426
427 let rc_word_value: RegisterContaining = RegisterContaining::from(TaggedProgramInstruction {
428 span: tqspan,
429 tags: Vec::new(),
430 instruction: qt,
431 });
432 InstructionFragment::PipeConstruct {
433 index: p,
434 rc_word_span: tqspan,
435 rc_word_value,
436 }
437}
438
439fn macro_terminator<'a, I>() -> impl Parser<'a, I, Tok, ExtraWithoutContext<'a>>
441where
442 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
443{
444 choice((
465 just(Tok::Hand(Script::Normal)),
466 just(Tok::Comma(Script::Normal)),
467 just(Tok::Equals(Script::Normal)),
468 just(Tok::Arrow(Script::Normal)),
469 just(Tok::Pipe(Script::Normal)),
470 just(Tok::ProperSuperset(Script::Normal)),
471 just(Tok::IdenticalTo(Script::Normal)),
472 just(Tok::Tilde(Script::Normal)),
473 just(Tok::LessThan(Script::Normal)),
474 just(Tok::GreaterThan(Script::Normal)),
475 just(Tok::Intersection(Script::Normal)),
476 just(Tok::Union(Script::Normal)),
477 just(Tok::Solidus(Script::Normal)),
478 just(Tok::Times(Script::Normal)),
479 just(Tok::LogicalOr(Script::Normal)),
480 just(Tok::LogicalAnd(Script::Normal)),
481 ))
482 .labelled("macro terminator")
483}
484
485fn macro_definition_dummy_parameter<'a, I>()
487-> impl Parser<'a, I, MacroParameter, ExtraWithoutContext<'a>>
488where
489 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
490{
491 (macro_terminator().then(named_symbol(SymexSyllableRule::Multiple, Script::Normal))).map_with(
494 |(terminator, symbol), extra| MacroParameter {
495 name: symbol,
496 span: extra.span(),
497 preceding_terminator: terminator,
498 },
499 )
500}
501
502fn macro_definition_dummy_parameters<'a, I>()
503-> impl Parser<'a, I, MacroDummyParameters, ExtraWithoutContext<'a>>
504where
505 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
506{
507 choice((
508 macro_definition_dummy_parameter()
509 .repeated()
510 .at_least(1)
511 .collect::<Vec<_>>()
512 .map(MacroDummyParameters::OneOrMore),
513 macro_terminator().map(MacroDummyParameters::Zero),
514 ))
515}
516
517fn macro_definition<'a, 'b, I>(
519 grammar: &Grammar<'a, 'b, I>,
520) -> impl Parser<'a, I, MacroDefinition, ExtraWithoutContext<'a>> + use<'a, 'b, I>
521where
522 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
523{
524 named_metacommand(Metacommand::DefineMacro)
525 .ignore_then(
526 named_symbol(SymexSyllableRule::Multiple, Script::Normal).labelled("macro name"), )
528 .then(macro_definition_dummy_parameters())
529 .then_ignore(end_of_line())
530 .then(
531 (macro_body_line(grammar).then_ignore(end_of_line()))
532 .repeated()
533 .collect()
534 .labelled("macro body"),
535 )
536 .then_ignore(named_metacommand(Metacommand::EndMacroDefinition))
537 .map_with(|((name, args), body), extra| {
540 let definition = MacroDefinition {
541 name,
542 params: args,
543 body,
544 span: extra.span(),
545 };
546 extra.state().define_macro(definition.clone());
547 definition
548 })
549}
550
551fn macro_body_line<'a, 'b, I>(
552 grammar: &Grammar<'a, 'b, I>,
553) -> impl Parser<'a, I, MacroBodyLine, ExtraWithoutContext<'a>> + use<'a, 'b, I>
554where
555 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
556{
557 choice((
558 macro_invocation().map(MacroBodyLine::Expansion),
559 grammar.assignment.clone().map(MacroBodyLine::Equality),
560 grammar
561 .tagged_program_instruction
562 .clone()
563 .map(MacroBodyLine::Instruction),
564 ))
565}
566
567fn arithmetic_expression_in_any_script_allowing_spaces<'a, I>()
568-> impl Parser<'a, I, (Span, Script, ArithmeticExpression), ExtraWithoutContext<'a>>
569where
570 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
571{
572 let g = grammar();
573 choice((
574 g.normal_arithmetic_expression_allowing_spaces
575 .map_with(|expr, extra| (extra.span(), Script::Normal, expr)),
576 g.subscript_arithmetic_expression_allowing_spaces
577 .map_with(|expr, extra| (extra.span(), Script::Sub, expr)),
578 g.superscript_arithmetic_expression_allowing_spaces
579 .map_with(|expr, extra| (extra.span(), Script::Super, expr)),
580 ))
581}
582
583fn defined_macro_name<'src, I>() -> impl Parser<'src, I, MacroDefinition, ExtraWithoutContext<'src>>
584where
585 I: Input<'src, Token = Tok, Span = Span> + ValueInput<'src>,
586{
587 fn mapping<'a>(
588 name: &SymbolName,
589 state: &State,
590 span: Span,
591 ) -> Result<MacroDefinition, chumsky::error::Rich<'a, lexer::Token>> {
592 match state.get_macro_definition(name) {
593 None => Err(Rich::custom(span, format!("unknown macro {name}"))),
594 Some(macro_def) => Ok(macro_def.clone()),
595 }
596 }
597
598 symex::parse_symex(SymexSyllableRule::OneOnly, Script::Normal).try_map_with(|name, extra| {
599 let span: Span = extra.span();
600 let state: &State = extra.state();
601 mapping(&name, state, span)
602 })
603}
604
605type ParsedMacroArg = Option<(Script, ArithmeticExpression)>;
606
607#[derive(Clone)]
608struct MacroInvocationParser<'src, I>
609where
610 I: Input<'src, Token = Tok, Span = Span> + ValueInput<'src>,
611{
612 expr_parser: Boxed<'src, 'src, I, (Span, ParsedMacroArg), ExtraWithoutContext<'src>>,
613 defined_macro_name_parser: Boxed<'src, 'src, I, MacroDefinition, ExtraWithoutContext<'src>>,
614}
615
616impl<'src, I> Default for MacroInvocationParser<'src, I>
617where
618 I: Input<'src, Token = Tok, Span = Span> + ValueInput<'src>,
619{
620 fn default() -> Self {
621 Self {
622 expr_parser: arithmetic_expression_in_any_script_allowing_spaces()
623 .or_not()
624 .map_with(|got, extra| match got {
625 Some((span, script, expr)) => (span, Some((script, expr))),
626 None => (extra.span(), None),
627 })
628 .boxed(),
629 defined_macro_name_parser: defined_macro_name().boxed(),
630 }
631 }
632}
633
634fn macro_invocation<'src, I>() -> impl Parser<'src, I, MacroInvocation, ExtraWithoutContext<'src>>
635where
636 I: Input<'src, Token = Tok, Span = Span> + ValueInput<'src>,
637{
638 Ext(MacroInvocationParser::default())
639}
640
641impl<'src, I> ExtParser<'src, I, MacroInvocation, ExtraWithoutContext<'src>>
642 for MacroInvocationParser<'src, I>
643where
644 I: Input<'src, Token = Tok, Span = Span> + ValueInput<'src>,
645{
646 fn parse(
647 &self,
648 inp: &mut InputRef<'src, '_, I, ExtraWithoutContext<'src>>,
649 ) -> Result<
650 MacroInvocation,
651 <Full<Rich<'src, Tok>, State<'src>, ()> as ParserExtra<'src, I>>::Error,
652 > {
653 let before = inp.cursor();
654 let macro_def: MacroDefinition = inp.parse(&self.defined_macro_name_parser)?;
655 let param_defs: Vec<MacroParameter> = match macro_def.params {
656 MacroDummyParameters::Zero(ref expected) => {
657 if let Some(got) = inp.next_maybe().as_deref() {
658 if got == expected {
659 Vec::new()
660 } else {
661 return Err(Rich::custom(
662 inp.span_since(&before),
663 format!(
664 "expected macro name {} to be followed a terminator {} but got {}",
665 macro_def.name, expected, got
666 ),
667 ));
668 }
669 } else {
670 return Err(Rich::custom(
671 inp.span_since(&before),
672 format!(
673 "expected macro name {} to be followed a terminator {}",
674 macro_def.name, expected
675 ),
676 ));
677 }
678 }
679 MacroDummyParameters::OneOrMore(ref params) => params.clone(),
680 };
681 let mut param_values: MacroParameterBindings = Default::default();
682 for param_def in param_defs {
683 let before = inp.cursor();
684 if let Some(got) = inp.next_maybe().as_deref() {
685 let span = inp.span_since(&before);
686 if got == ¶m_def.preceding_terminator {
687 match inp.parse(&self.expr_parser)? {
688 (span, Some((script, expr))) => {
689 param_values.insert(
690 param_def.name,
691 span,
692 Some(MacroParameterValue::Value(script, expr)),
693 );
694 }
695 (span, None) => {
696 param_values.insert(param_def.name, span, None);
698 }
699 }
700 } else {
701 return Err(Rich::custom(
702 span,
703 format!(
704 "in invocation of macro {}, expected macro terminator {} before parameter {} but got {}",
705 macro_def.name, param_def.preceding_terminator, param_def.name, got
706 ),
707 ));
708 }
709 } else {
710 let span = inp.span_since(&before);
711 return Err(Rich::custom(
712 span,
713 format!(
714 "in invocation of macro {}, expected macro terminator {} before parameter {}",
715 macro_def.name, param_def.preceding_terminator, param_def.name
716 ),
717 ));
718 }
719 }
720 Ok(MacroInvocation {
721 macro_def,
722 param_values,
723 })
724 }
725}
726
727#[derive(Debug, PartialEq, Eq, Clone, Copy)]
728enum Metacommand {
729 Decimal,
730 Octal,
731 Punch,
733 DefineMacro,
734 EndMacroDefinition,
735}
736
737fn named_metacommand<'a, I>(which: Metacommand) -> impl Parser<'a, I, (), ExtraWithoutContext<'a>>
738where
739 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
740{
741 let name_match = move |actual: &str| -> bool {
742 match which {
743 Metacommand::Decimal => {
744 matches!(actual, "DECIMAL" | "DECIMA" | "DECIM" | "DECI" | "DEC")
745 }
746 Metacommand::Octal => matches!(actual, "OCTAL" | "OCTA" | "OCT" | "OC"),
747 Metacommand::Punch => matches!(actual, "PUNCH" | "PUNC" | "PUN" | "PU"),
748 Metacommand::DefineMacro => actual == "DEF",
749 Metacommand::EndMacroDefinition => matches!(actual, "EMD" | "EM"),
750 }
751 };
752
753 let matching_metacommand_name = select! {
754 Tok::SymexSyllable(Script::Normal, name) if name_match(name.as_str()) => (),
755 };
756
757 just([Tok::Hand(Script::Normal), Tok::Hand(Script::Normal)])
758 .ignored()
759 .then_ignore(matching_metacommand_name)
760}
761
762fn metacommand<'a, 'b, I>(
763 grammar: &Grammar<'a, 'b, I>,
764) -> impl Parser<'a, I, ManuscriptMetaCommand, ExtraWithoutContext<'a>> + use<'a, 'b, I>
765where
766 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
767{
768 fn punch<'a, I>() -> impl Parser<'a, I, ManuscriptMetaCommand, ExtraWithoutContext<'a>>
769 where
770 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
771 {
772 named_metacommand(Metacommand::Punch)
777 .ignore_then(literal(Script::Normal).or_not())
778 .try_map(|aa, span| match helpers::punch_address(aa) {
779 Ok(punch) => Ok(ManuscriptMetaCommand::Punch(punch)),
780 Err(msg) => Err(Rich::custom(span, msg)),
781 })
782 .labelled("PUNCH command")
783 }
784
785 fn base_change<'a, I>() -> impl Parser<'a, I, ManuscriptMetaCommand, ExtraWithoutContext<'a>>
786 where
787 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
788 {
789 choice((
790 named_metacommand(Metacommand::Decimal)
791 .to(ManuscriptMetaCommand::BaseChange(NumeralMode::Decimal)),
792 named_metacommand(Metacommand::Octal)
793 .to(ManuscriptMetaCommand::BaseChange(NumeralMode::Octal)),
794 ))
795 .labelled("base-change metacommand")
796 }
797
798 choice((
799 base_change(),
800 punch(),
801 macro_definition(grammar).map(ManuscriptMetaCommand::Macro),
802 ))
803 .labelled("metacommand")
804}
805
806pub(crate) fn instructions_with_comma_counts<I>(it: I) -> Vec<CommaDelimitedFragment>
807where
808 I: Iterator<Item = CommasOrInstruction>,
809{
810 fn fold_step(
817 mut acc: Vec<CommasOrInstruction>,
818 item: CommasOrInstruction,
819 ) -> Vec<CommasOrInstruction> {
820 fn null_instruction(span: Span) -> FragmentWithHold {
821 FragmentWithHold {
822 span,
823 holdbit: HoldBit::Unspecified,
824 fragment: InstructionFragment::Null(span),
825 }
826 }
827
828 match acc.last_mut() {
829 Some(CommasOrInstruction::C(tail_comma)) => match item {
830 CommasOrInstruction::C(maybe_commas) => {
831 if tail_comma.is_none() {
832 *tail_comma = maybe_commas;
833 } else {
834 let null_inst_span: Span = match (tail_comma, &maybe_commas) {
835 (_, Some(ic)) => span(ic.span().start..ic.span().start),
836 (Some(tc), _) => span(tc.span().start..tc.span().start),
837 (None, None) => {
838 unreachable!(
839 "should be no need to interpose a null instruction between two instances of zero commas"
840 );
841 }
842 };
843 acc.push(CommasOrInstruction::I(null_instruction(null_inst_span)));
844 acc.push(CommasOrInstruction::C(maybe_commas));
845 }
846 }
847 CommasOrInstruction::I(inst) => {
848 acc.push(CommasOrInstruction::I(inst));
849 acc.push(CommasOrInstruction::C(None));
850 }
851 },
852 Some(CommasOrInstruction::I(_)) => unreachable!("invariant was broken"),
853 None => unreachable!("invariant was not established"),
854 }
855 assert!(matches!(acc.first(), Some(CommasOrInstruction::C(_))));
856 assert!(matches!(acc.last(), Some(CommasOrInstruction::C(_))));
857 acc
858 }
859
860 let mut it = it.peekable();
861
862 let initial_accumulator: Vec<CommasOrInstruction> = vec![CommasOrInstruction::C({
863 match it.peek() {
864 None => {
865 return Vec::new();
866 }
867 Some(CommasOrInstruction::I(_)) => None,
868 Some(CommasOrInstruction::C(maybe_commas)) => {
869 let c = maybe_commas.clone();
870 it.next();
871 c
872 }
873 }
874 })];
875
876 let tmp = it.fold(initial_accumulator, fold_step);
877 let mut output: Vec<CommaDelimitedFragment> = Vec::with_capacity(tmp.len() / 2 + 1);
878 let mut it = tmp.into_iter().peekable();
879 loop {
880 let maybe_before_count = it.next();
881 let maybe_inst = it.next();
882 match (maybe_before_count, maybe_inst) {
883 (None, _) => {
884 break;
885 }
886 (Some(CommasOrInstruction::C(before_commas)), Some(CommasOrInstruction::I(inst))) => {
887 let after_commas: Option<Commas> = match it.peek() {
888 Some(CommasOrInstruction::C(commas)) => commas.clone(),
889 None => None,
890 Some(CommasOrInstruction::I(_)) => {
891 unreachable!("fold_step did not maintain its invariant")
892 }
893 };
894 output.push(CommaDelimitedFragment::new(
895 before_commas,
896 inst,
897 after_commas,
898 ));
899 }
900 (Some(CommasOrInstruction::C(_)), None) => {
901 break;
903 }
904 (Some(CommasOrInstruction::I(_)), _)
905 | (Some(CommasOrInstruction::C(_)), Some(CommasOrInstruction::C(_))) => {
906 unreachable!("fold_step did not maintain its invariant");
907 }
908 }
909 }
910 output
911}
912
913fn tag_definition<'a, I>() -> impl Parser<'a, I, Tag, ExtraWithoutContext<'a>> + Clone
914where
915 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
916{
917 named_symbol(SymexSyllableRule::Multiple, Script::Normal)
918 .map_with(|name, extra| Tag {
919 name,
920 span: extra.span(),
921 })
922 .then_ignore(just(Tok::Arrow(Script::Normal)))
923 .labelled("tag definition")
924}
925
926fn commas<'a, I>() -> impl Parser<'a, I, Commas, ExtraWithoutContext<'a>> + Clone
927where
928 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
929{
930 just(Tok::Comma(Script::Normal))
931 .repeated()
932 .at_least(1)
933 .at_most(3)
934 .count()
935 .map_with(|count, extra| {
936 let span = extra.span();
937 match count {
938 1 => Commas::One(span),
939 2 => Commas::Two(span),
940 3 => Commas::Three(span),
941 _ => unreachable!(),
942 }
943 })
944}
945
946fn maybe_hold<'a, I>() -> impl Parser<'a, I, Option<HoldBit>, ExtraWithoutContext<'a>> + Clone
947where
948 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
949{
950 choice((
951 one_of(Tok::Hold).to(HoldBit::Hold),
952 just(Tok::NotHold).to(HoldBit::NotHold),
953 ))
954 .or_not()
955 .labelled("instruction hold bit")
956}
957
958struct Grammar<'a, 'b, I>
959where
960 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
961{
962 assignment: Boxed<'a, 'b, I, Equality, ExtraWithoutContext<'a>>,
963 tagged_program_instruction: Boxed<'a, 'b, I, TaggedProgramInstruction, ExtraWithoutContext<'a>>,
964 normal_arithmetic_expression_allowing_spaces:
965 Boxed<'a, 'b, I, ArithmeticExpression, ExtraWithoutContext<'a>>,
966 subscript_arithmetic_expression_allowing_spaces:
967 Boxed<'a, 'b, I, ArithmeticExpression, ExtraWithoutContext<'a>>,
968 superscript_arithmetic_expression_allowing_spaces:
969 Boxed<'a, 'b, I, ArithmeticExpression, ExtraWithoutContext<'a>>,
970 #[cfg(test)]
971 instruction_fragment: Boxed<'a, 'b, I, InstructionFragment, ExtraWithoutContext<'a>>,
972}
973
974fn grammar<'a: 'b, 'b, I>() -> Grammar<'a, 'b, I>
975where
976 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
977{
978 const ALLOW_SPACES: bool = true;
979
980 let mut comma_delimited_instructions = Recursive::declare();
981 let tagged_program_instruction = (tag_definition()
982 .repeated()
983 .collect()
984 .then(comma_delimited_instructions.clone()))
985 .map_with(
986 |(tags, fragments): (Vec<Tag>, OneOrMore<CommaDelimitedFragment>), extra| {
987 let span: Span = extra.span();
988 if let Some(t) = tags.first() {
989 assert_eq!(t.span.start, span.start);
990 }
991 TaggedProgramInstruction {
992 span: extra.span(),
993 tags,
994 instruction: UntaggedProgramInstruction::from(fragments),
995 }
996 },
997 )
998 .labelled(
999 "optional tag definition followed by a (possibly comma-delimited) program instructions",
1000 );
1001
1002 let register_containing = tagged_program_instruction
1007 .clone()
1008 .delimited_by(
1009 just(Tok::LeftBrace(Script::Normal)),
1010 just(Tok::RightBrace(Script::Normal)),
1011 )
1012 .map_with(|tagged_instruction, extra| {
1013 Atom::RcRef(
1014 extra.span(),
1015 RegistersContaining::from_words(OneOrMore::new(RegisterContaining::from(
1016 tagged_instruction,
1017 ))),
1018 )
1019 })
1020 .labelled("RC-word");
1021
1022 let arith_expr = |allow_spaces: bool, script_required: Script| {
1023 {
1024 let symex_syllable_rule = if allow_spaces {
1025 SymexSyllableRule::Multiple
1026 } else {
1027 SymexSyllableRule::OneOnly
1028 };
1029 recursive(move |arithmetic_expr| {
1033 let parenthesised_arithmetic_expression = arithmetic_expr .clone()
1036 .delimited_by(
1037 just(Tok::LeftParen(script_required)),
1038 just(Tok::RightParen(script_required)),
1039 )
1040 .map_with(move |expr, extra| {
1041 Atom::Parens(extra.span(), script_required, Box::new(expr))
1042 })
1043 .labelled("parenthesised arithmetic expression");
1044
1045 let naked_atom = choice((
1047 literal(script_required).map(Atom::from),
1048 opcode().map(Atom::from),
1049 here(script_required).map(Atom::SymbolOrLiteral),
1050 named_symbol(symex_syllable_rule, script_required).map_with(
1051 move |symbol_name, extra| {
1052 Atom::SymbolOrLiteral(SymbolOrLiteral::Symbol(
1053 script_required,
1054 symbol_name,
1055 extra.span(),
1056 ))
1057 },
1058 ),
1059 register_containing,
1060 parenthesised_arithmetic_expression,
1061 ))
1062 .boxed();
1063
1064 let signed_atom = maybe_sign(script_required).then(naked_atom).map_with(
1065 |(possible_sign, magnitude), extra| SignedAtom {
1066 span: extra.span(),
1067 negated: matches!(possible_sign, Some((Sign::Minus, _))),
1068 magnitude,
1069 },
1070 );
1071
1072 let operator_with_signed_atom = operator(script_required).then(signed_atom.clone());
1074
1075 signed_atom
1078 .then(operator_with_signed_atom.repeated().collect())
1079 .map(|(head, tail)| ArithmeticExpression::with_tail(head, tail))
1080 })
1081 }
1082 .labelled("arithmetic expression")
1083 };
1084
1085 let program_instruction_fragment = recursive(|program_instruction_fragment| {
1097 let spanned_rc_word_fragment = symbol_or_literal(Script::Sub) .map_with(|p, extra| SpannedSymbolOrLiteral {
1115 item: p,
1116 span: extra.span(),
1117 })
1118 .boxed();
1119
1120 let q = program_instruction_fragment
1121 .clone()
1122 .map_with(|q, extra| (q, extra.span()));
1123 let tq = symbol_or_literal(Script::Sub) .map_with(|t, extra| SpannedSymbolOrLiteral {
1125 item: t,
1126 span: extra.span(),
1127 })
1128 .then(q.or_not())
1129 .boxed();
1130 let pipe_construct = spanned_rc_word_fragment
1131 .then_ignore(just(Tok::Pipe(Script::Sub)))
1132 .then(tq)
1133 .map(make_pipe_construct)
1134 .labelled("pipe construct");
1135
1136 let single_script_fragment = |script_required| {
1137 arith_expr.clone()(true, script_required).map(InstructionFragment::from)
1138 };
1139
1140 let config_value = choice((
1145 just(Tok::DoublePipe(Script::Normal)).ignore_then(
1146 arith_expr.clone()(false, Script::Normal).map(|expr| ConfigValue {
1147 already_superscript: false,
1148 expr,
1149 }),
1150 ),
1151 arith_expr.clone()(false, Script::Super).map(|expr| ConfigValue {
1152 already_superscript: true,
1153 expr,
1154 }),
1155 ))
1156 .try_map_with(|config_val, extra| {
1157 let span: Span = extra.span();
1158 let range: Range<usize> = span.into();
1159 let slice: &str = &extra.state().body[range];
1160
1161 if slice.contains(' ') {
1162 Err(Rich::custom(
1165 span,
1166 format!("configuration value '{slice}' should not contain spaces"),
1167 ))
1168 } else {
1169 Ok(InstructionFragment::Config(config_val))
1170 }
1171 })
1172 .labelled("configuration value");
1173
1174 choice((
1175 pipe_construct,
1176 single_script_fragment(Script::Normal),
1177 single_script_fragment(Script::Sub),
1178 asterisk_indirection_fragment(),
1179 config_value,
1180 ))
1181 .labelled("program instruction")
1182 });
1183
1184 comma_delimited_instructions.define({
1185 let untagged_program_instruction = maybe_hold()
1186 .then(program_instruction_fragment.clone())
1187 .map_with(
1188 |(maybe_hold, fragment): (Option<HoldBit>, InstructionFragment), extra| {
1189 FragmentWithHold {
1190 span: extra.span(),
1191 holdbit: maybe_hold.unwrap_or(HoldBit::Unspecified),
1192 fragment,
1193 }
1194 },
1195 );
1196
1197 choice((
1198 commas().map(|c| CommasOrInstruction::C(Some(c))),
1199 untagged_program_instruction
1200 .clone()
1201 .map(CommasOrInstruction::I),
1202 ))
1203 .repeated()
1204 .at_least(1)
1205 .collect::<Vec<CommasOrInstruction>>()
1206 .map(|ci_vec| instructions_with_comma_counts(ci_vec.into_iter()))
1207 .map(|cdfs| match OneOrMore::try_from_iter(cdfs.into_iter()) {
1208 Ok(cdfs) => cdfs,
1209 Err(_) => {
1210 unreachable!("instructions_with_comma_counts generated an empty output");
1211 }
1212 })
1213 });
1214
1215 let assignment = (symex::parse_symex(SymexSyllableRule::Multiple, Script::Normal)
1219 .then_ignore(just(Tok::Equals(Script::Normal)))
1220 .then(comma_delimited_instructions.clone().map_with(
1221 |val: OneOrMore<CommaDelimitedFragment>, extra| {
1222 EqualityValue::from((extra.span(), UntaggedProgramInstruction::from(val)))
1223 },
1224 )))
1225 .map_with(|(name, value), extra| Equality {
1226 span: extra.span(),
1227 name,
1228 value,
1229 })
1230 .labelled("equality (assignment)");
1231
1232 let tagged_program_instruction = tagged_program_instruction.clone();
1233
1234 Grammar {
1235 assignment: assignment.boxed(),
1236 tagged_program_instruction: tagged_program_instruction.boxed(),
1237 normal_arithmetic_expression_allowing_spaces: arith_expr.clone()(
1238 ALLOW_SPACES,
1239 Script::Normal,
1240 )
1241 .boxed(),
1242 superscript_arithmetic_expression_allowing_spaces: arith_expr.clone()(
1243 ALLOW_SPACES,
1244 Script::Super,
1245 )
1246 .boxed(),
1247 subscript_arithmetic_expression_allowing_spaces: arith_expr.clone()(
1248 ALLOW_SPACES,
1249 Script::Sub,
1250 )
1251 .boxed(),
1252 #[cfg(test)]
1253 instruction_fragment: program_instruction_fragment.boxed(),
1254 }
1255}
1256
1257#[cfg(test)]
1258fn tagged_instruction<'a, I>()
1259-> impl Parser<'a, I, TaggedProgramInstruction, ExtraWithoutContext<'a>>
1260where
1261 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1262{
1263 grammar().tagged_program_instruction
1264}
1265
1266fn manuscript_line<'a, I>() -> impl Parser<'a, I, ManuscriptLine, ExtraWithoutContext<'a>>
1267where
1268 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1269{
1270 fn execute_metacommand(state: &mut NumeralMode, cmd: &ManuscriptMetaCommand) {
1271 match cmd {
1272 ManuscriptMetaCommand::Punch(_) | ManuscriptMetaCommand::Macro(_) => {
1273 }
1277 ManuscriptMetaCommand::BaseChange(new_base) => state.set_numeral_mode(*new_base),
1278 }
1279 }
1280
1281 fn parse_and_execute_metacommand<'a, 'b, I>(
1282 grammar: &Grammar<'a, 'b, I>,
1283 ) -> impl Parser<'a, I, ManuscriptLine, ExtraWithoutContext<'a>> + use<'a, 'b, I>
1284 where
1285 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1286 {
1287 metacommand(grammar)
1288 .map_with(|cmd, extra| {
1289 execute_metacommand(&mut extra.state().numeral_mode, &cmd);
1290 ManuscriptLine::Meta(cmd)
1291 })
1292 .labelled("metacommand")
1293 }
1294
1295 fn build_code_line(
1296 (maybe_origin, statement): (Option<Origin>, TaggedProgramInstruction),
1297 ) -> ManuscriptLine {
1298 match maybe_origin {
1299 None => ManuscriptLine::StatementOnly(statement),
1300 Some(origin) => ManuscriptLine::OriginAndStatement(origin, statement),
1301 }
1302 }
1303
1304 fn line<'a, I>() -> impl Parser<'a, I, ManuscriptLine, ExtraWithoutContext<'a>>
1305 where
1306 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1307 {
1308 fn origin<'a, I>() -> impl Parser<'a, I, Origin, ExtraWithoutContext<'a>>
1309 where
1310 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1311 {
1312 fn literal_address_expression<'a, I>()
1317 -> impl Parser<'a, I, Origin, ExtraWithoutContext<'a>>
1318 where
1319 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1320 {
1321 literal(Script::Normal)
1322 .then_ignore(just(Tok::Pipe(Script::Normal)))
1323 .try_map(|lit, span| match Address::try_from(lit.value()) {
1324 Ok(addr) => Ok(Origin::Literal(span, addr)),
1325 Err(e) => Err(Rich::custom(span, format!("not a valid address: {e}"))),
1326 })
1327 .labelled("literal address expression")
1328 }
1329
1330 fn symbolic_address_expression<'a, I>()
1331 -> impl Parser<'a, I, Origin, ExtraWithoutContext<'a>>
1332 where
1333 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1334 {
1335 named_symbol(SymexSyllableRule::Multiple, Script::Normal)
1336 .then_ignore(just(Tok::Pipe(Script::Normal)))
1337 .map_with(|name, extra| Origin::Symbolic(extra.span(), name))
1338 .labelled("symbolic address expression")
1339 }
1340
1341 choice((literal_address_expression(), symbolic_address_expression()))
1344 .labelled("origin specification")
1345 }
1346
1347 let grammar = grammar();
1348
1349 let optional_origin_with_statement = origin()
1350 .or_not()
1351 .then(grammar.tagged_program_instruction.clone())
1352 .map(build_code_line)
1353 .labelled("statement with origin");
1354
1355 let origin_only = origin().map(ManuscriptLine::OriginOnly);
1357 let tags_only = tag_definition()
1358 .repeated()
1359 .at_least(1)
1360 .collect()
1361 .map(ManuscriptLine::TagsOnly);
1362 let equality = grammar.assignment.clone().map(ManuscriptLine::Eq);
1363
1364 choice((
1365 equality,
1369 macro_invocation().map(ManuscriptLine::Macro),
1370 parse_and_execute_metacommand(&grammar),
1372 optional_origin_with_statement,
1373 origin_only,
1377 tags_only,
1379 ))
1380 }
1381
1382 line()
1383}
1384
1385fn end_of_line<'a, I>() -> impl Parser<'a, I, (), ExtraWithoutContext<'a>>
1386where
1387 I: Input<'a, Token = Tok, Span = Span>,
1388{
1389 let one_end_of_line = just(Tok::Newline).labelled("end-of-line").ignored();
1390
1391 one_end_of_line
1392 .repeated()
1393 .at_least(1)
1394 .ignored()
1395 .labelled("comment or end-of-line")
1396}
1397
1398fn terminated_manuscript_line<'a, I>()
1399-> impl Parser<'a, I, Option<(Span, ManuscriptLine)>, ExtraWithoutContext<'a>>
1400where
1401 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1402{
1403 manuscript_line()
1407 .or_not()
1408 .map_with(|maybe_line, extra| maybe_line.map(|line| (extra.span(), line)))
1409 .then_ignore(end_of_line())
1410}
1411
1412pub(crate) fn source_file<'a, I>() -> impl Parser<'a, I, SourceFile, ExtraWithoutContext<'a>>
1413where
1414 I: Input<'a, Token = Tok, Span = Span> + ValueInput<'a>,
1415{
1416 fn inconsistency_error<'src>(
1417 span: Span,
1418 name: &SymbolName,
1419 what: &str,
1420 ) -> chumsky::error::Rich<'src, lexer::Token> {
1421 Rich::custom(
1422 span,
1423 format!("internal error: inconsistent parser state for macro {name}: {what}"),
1424 )
1425 }
1426
1427 fn check_consistent<'a>(
1428 macro_def_in_source_file: Option<&MacroDefinition>,
1429 macro_def_in_state: Option<&MacroDefinition>,
1430 ) -> Result<(), chumsky::error::Rich<'a, lexer::Token>> {
1431 match (macro_def_in_source_file, macro_def_in_state) {
1432 (None, None) => {
1433 panic!("all_name is incorrect");
1434 }
1435 (None, Some(state_def)) => Err(inconsistency_error(
1436 state_def.span,
1437 &state_def.name,
1438 "missing from SourceFile output",
1439 )),
1440 (Some(source_file_def), None) => Err(inconsistency_error(
1441 source_file_def.span,
1442 &source_file_def.name,
1443 "missing from State",
1444 )),
1445 (Some(source_file_def), Some(state_def)) => {
1446 if source_file_def == state_def {
1447 Ok(())
1448 } else {
1449 Err(inconsistency_error(
1450 source_file_def.span,
1451 &source_file_def.name,
1452 "inconsistently defined",
1453 ))
1454 }
1455 }
1456 }
1457 }
1458
1459 terminated_manuscript_line()
1460 .repeated()
1461 .collect()
1462 .try_map_with(|lines: Vec<Option<(Span, ManuscriptLine)>>, extra| {
1463 let lines: Vec<(Span, ManuscriptLine)> = lines.into_iter().flatten().collect();
1465 let source_file: SourceFile = manuscript_lines_to_source_file(lines)?;
1466 let state_macros: BTreeMap<SymbolName, MacroDefinition> =
1467 extra.state().macros().clone();
1468 let all_names: BTreeSet<&SymbolName> = source_file
1469 .macros
1470 .keys()
1471 .chain(state_macros.keys())
1472 .collect();
1473 for name in all_names {
1474 check_consistent(
1475 source_file.macros.get(name),
1476 extra.state().macros().get(name),
1477 )?;
1478 }
1479 Ok(source_file)
1480 })
1481}
1482
1483type Mig<I, O> = chumsky::input::MappedInput<
1484 Tok,
1485 SimpleSpan,
1486 chumsky::input::Stream<std::vec::IntoIter<(Tok, SimpleSpan)>>,
1487 fn(I) -> O,
1488>;
1489pub(crate) type Mi = Mig<(Tok, SimpleSpan), (Tok, SimpleSpan)>;
1490
1491pub(crate) fn tokenize_and_parse_with<'a, P, T, F>(
1492 input: &'a str,
1493 mut setup: F,
1494 parser: P,
1495) -> (Option<T>, Vec<Rich<'a, Tok>>)
1496where
1497 F: FnMut(&mut State),
1498 P: Parser<'a, Mi, T, ExtraWithoutContext<'a>>,
1499{
1500 let mut state = State::new(input, NumeralMode::default());
1501 setup(&mut state);
1502
1503 let scanner = lexer::Lexer::new(input).spanned();
1506 let tokens: Vec<(Tok, SimpleSpan)> = scanner
1507 .map(|item: (Tok, Range<usize>)| -> (Tok, Span) {
1508 match item {
1509 (Tok::Tab, span) => {
1510 (Tok::Error(lexer::ErrorTokenKind::Tab), span.into())
1527 }
1528 (tok, span) => {
1529 (tok, span.into())
1533 }
1534 }
1535 })
1536 .collect();
1537 let end_span: SimpleSpan = SimpleSpan::new(
1538 0,
1539 tokens.iter().map(|(_, span)| span.end).max().unwrap_or(0),
1540 );
1541 let token_stream: Mi = Stream::from_iter(tokens).map(end_span, |unchanged| unchanged);
1542 parser
1543 .parse_with_state(token_stream, &mut state)
1544 .into_output_errors()
1545}
1546
1547pub(crate) fn parse_source_file(
1548 source_file_body: &str,
1549 setup: fn(&mut State),
1550) -> (Option<SourceFile>, Vec<Rich<'_, Tok>>) {
1551 let parser = source_file();
1552 tokenize_and_parse_with(source_file_body, setup, parser)
1553}
1554
1555