1use crate::error::ErrorCode;
8use crate::error::error;
9use crate::feature_flags::{Feature, FeatureFlags};
10use crate::lexer::{Lexer, Token, TokenPos};
11use crate::pic::PicClause;
12use crate::schema::{Field, FieldKind, Occurs, Schema, SignPlacement, SignSeparateInfo};
13use crate::utils::VecExt;
14use crate::{Error, Result};
15
16mod field;
17
18fn is_cobol_data_name(s: &str) -> bool {
20 let mut chars = s.chars();
21 match chars.next() {
22 Some(c) if c.is_ascii_alphabetic() => {}
23 _ => return false,
24 }
25 chars.all(|c| c.is_ascii_alphanumeric() || c == '-')
26}
27
28fn try_extract_data_name(token_pos: &TokenPos) -> Option<String> {
31 match &token_pos.token {
32 Token::Identifier(name) => Some(name.clone()),
33 Token::EditedPic(text) | Token::PicClause(text) if is_cobol_data_name(text) => {
34 Some(text.clone())
35 }
36 _ => None,
37 }
38}
39
40#[inline]
45#[must_use = "Handle the Result or propagate the error"]
46pub fn parse(text: &str) -> Result<Schema> {
47 parse_with_options(text, &ParseOptions::default())
48}
49
50#[inline]
55#[must_use = "Handle the Result or propagate the error"]
56pub fn parse_with_feature_flags(
57 text: &str,
58 options: &ParseOptions,
59 feature_flags: &FeatureFlags,
60) -> Result<Schema> {
61 parse_with_options_and_feature_flags(text, options, feature_flags)
62}
63
64#[inline]
69#[must_use = "Handle the Result or propagate the error"]
70pub fn parse_with_options_and_feature_flags(
71 text: &str,
72 options: &ParseOptions,
73 feature_flags: &FeatureFlags,
74) -> Result<Schema> {
75 if text.trim().is_empty() {
76 return Err(error!(ErrorCode::CBKP001_SYNTAX, "Empty copybook text"));
77 }
78
79 let tokens = Lexer::new_with_options(text, options).tokenize();
80 let mut parser = Parser::with_options(tokens, options.clone(), feature_flags.clone());
81 parser.parse_schema()
82}
83
84#[inline]
89#[must_use = "Handle the Result or propagate the error"]
90pub fn parse_with_options(text: &str, options: &ParseOptions) -> Result<Schema> {
91 parse_with_options_and_feature_flags(text, options, FeatureFlags::global())
92}
93
94#[derive(Debug, Clone)]
99#[allow(clippy::struct_excessive_bools)]
100pub struct ParseOptions {
101 pub emit_filler: bool,
103 pub codepage: String,
105 pub allow_inline_comments: bool,
107 pub strict: bool,
109 pub strict_comments: bool,
111 pub dialect: crate::dialect::Dialect,
113}
114
115impl Default for ParseOptions {
116 fn default() -> Self {
117 Self {
118 emit_filler: false,
119 codepage: "cp037".to_string(),
120 allow_inline_comments: true,
121 strict: false,
122 strict_comments: false,
123 dialect: crate::dialect::Dialect::Normative,
124 }
125 }
126}
127
128struct Parser {
130 tokens: Vec<TokenPos>,
131 current: usize,
132 options: ParseOptions,
133 feature_flags: FeatureFlags,
134}
135
136impl Parser {
137 fn with_options(
138 tokens: Vec<TokenPos>,
139 options: ParseOptions,
140 feature_flags: FeatureFlags,
141 ) -> Self {
142 Self {
143 tokens,
144 current: 0,
145 options,
146 feature_flags,
147 }
148 }
149
150 fn require_feature_enabled(
151 &self,
152 feature: Feature,
153 field_name: &str,
154 feature_name: &str,
155 syntax: &str,
156 ) -> Result<()> {
157 if self.feature_flags.is_enabled(feature) {
158 return Ok(());
159 }
160
161 Err(Error::new(
162 ErrorCode::CBKP011_UNSUPPORTED_CLAUSE,
163 format!(
164 "{syntax} is not supported for field '{field_name}' (enable with --enable-features {feature_name})"
165 ),
166 ))
167 }
168
169 fn parse_schema(&mut self) -> Result<Schema> {
171 self.skip_comments_and_newlines()?;
173
174 let mut flat_fields = Vec::new();
176 while !self.is_at_end() {
177 if let Some(field) = self.parse_field()? {
178 flat_fields.push(field);
179 }
180 self.skip_comments_and_newlines()?;
181 }
182
183 if flat_fields.is_empty() {
184 return Err(error!(
185 ErrorCode::CBKP001_SYNTAX,
186 "No valid field definitions found"
187 ));
188 }
189
190 let mut hierarchical_fields = self.build_hierarchy(flat_fields)?;
192
193 finalize_names_and_paths(&mut hierarchical_fields, None, self.options.emit_filler);
195
196 self.validate_structure(&hierarchical_fields)?;
198
199 let mut schema = Schema::from_fields(hierarchical_fields);
201
202 crate::layout::resolve_layout_with_feature_flags(
204 &mut schema,
205 self.options.dialect,
206 &self.feature_flags,
207 )?;
208
209 self.calculate_schema_fingerprint(&mut schema);
210
211 Ok(schema)
212 }
213
214 fn build_hierarchy(&mut self, mut flat_fields: Vec<Field>) -> Result<Vec<Field>> {
216 if flat_fields.is_empty() {
217 return Ok(Vec::new());
218 }
219
220 self.process_field_names(&mut flat_fields);
222
223 let mut stack: Vec<Field> = Vec::new();
225 let mut result: Vec<Field> = Vec::new();
226
227 for mut field in flat_fields {
228 field.path = field.name.clone();
230
231 if field.level == 88 {
233 if let Some(parent) = stack.last_mut() {
235 field.path = format!("{}.{}", parent.path, field.name);
236 parent.children.push(field);
237 } else {
238 return Err(Error::new(
239 ErrorCode::CBKP001_SYNTAX,
240 "Level-88 condition must follow a data field".to_string(),
241 ));
242 }
243 continue;
244 }
245
246 let is_renames = field.level == 66;
248
249 if is_renames {
251 while let Some(top) = stack.last() {
256 if top.level == 1 {
258 break;
259 }
260
261 let mut completed_field = stack.pop_or_cbkp_error(
263 ErrorCode::CBKP001_SYNTAX,
264 "Parser stack underflow while attaching RENAMES",
265 )?;
266
267 if completed_field
269 .children
270 .iter()
271 .any(|child| child.level != 88)
272 {
273 completed_field.kind = FieldKind::Group;
274 }
275
276 if let Some(parent) = stack.last_mut() {
278 completed_field.path = format!("{}.{}", parent.path, completed_field.name);
279 parent.children.push(completed_field);
280 } else {
281 return Err(Error::new(
282 ErrorCode::CBKP001_SYNTAX,
283 "Level-66 RENAMES must be within a record group".to_string(),
284 ));
285 }
286 }
287
288 let parent = stack.last_mut().ok_or_else(|| {
290 Error::new(
291 ErrorCode::CBKP001_SYNTAX,
292 "Level-66 RENAMES must be within a record group".to_string(),
293 )
294 })?;
295
296 field.path = format!("{}.{}", parent.path, field.name);
297 parent.children.push(field);
298 continue;
299 }
300
301 while let Some(top) = stack.last() {
303 if top.level >= field.level {
304 let mut completed_field = stack.pop_or_cbkp_error(
305 ErrorCode::CBKP001_SYNTAX,
306 "Parser stack underflow: expected field to pop but stack was empty",
307 )?;
308
309 if completed_field
311 .children
312 .iter()
313 .any(|child| child.level != 88)
314 {
315 completed_field.kind = FieldKind::Group;
316 }
317
318 if let Some(parent) = stack.last_mut() {
320 completed_field.path = format!("{}.{}", parent.path, completed_field.name);
322 parent.children.push(completed_field);
323 } else {
324 result.push(completed_field);
325 }
326 } else {
327 break;
328 }
329 }
330
331 if let Some(parent) = stack.last() {
333 field.path = format!("{}.{}", parent.path, field.name);
334 }
335
336 stack.push(field);
337 }
338
339 while let Some(mut field) = stack.pop() {
341 if field.children.iter().any(|child| child.level != 88) {
344 field.kind = FieldKind::Group;
345 }
346
347 if field.level == 77 {
349 result.push(field);
350 continue;
351 }
352
353 if let Some(parent) = stack.last_mut() {
355 parent.children.push(field);
356 } else {
357 result.push(field);
358 }
359 }
360
361 Ok(result)
362 }
363
364 fn process_field_names(&mut self, fields: &mut [Field]) {
366 for field in fields.iter_mut() {
367 if field.name.to_uppercase() == "FILLER" {
368 if self.options.emit_filler {
369 field.name = format!("_filler_{}", 0);
372 } else {
373 field.name = "FILLER".to_string();
375 }
376 }
377 }
378 }
379
380 fn validate_structure(&self, fields: &[Field]) -> Result<()> {
382 self.validate_redefines(fields)?;
384
385 self.validate_odo_constraints(fields)?;
387
388 Ok(())
389 }
390
391 fn validate_redefines(&self, fields: &[Field]) -> Result<()> {
393 let all_fields = Self::collect_all_fields(fields);
394
395 for field in &all_fields {
396 if let Some(ref target) = field.redefines_of {
397 let target_found = all_fields
399 .iter()
400 .any(|f| f.name == *target || f.path == *target);
401
402 if !target_found {
403 return Err(Error::new(
404 ErrorCode::CBKP001_SYNTAX,
405 format!(
406 "REDEFINES target '{}' not found for field '{}'",
407 target, field.name
408 ),
409 ));
410 }
411 }
412 }
413
414 Ok(())
415 }
416
417 fn validate_odo_constraints(&self, fields: &[Field]) -> Result<()> {
419 let all_fields = Self::collect_all_fields(fields);
421
422 for field in fields {
424 self.validate_odo_in_group(field, &all_fields, false)?;
425 }
426
427 Ok(())
428 }
429
430 fn is_inside_redefines(&self, field_path: &str, all_fields: &[&Field]) -> bool {
432 for ancestor in all_fields {
434 if field_path.starts_with(&format!("{}.", ancestor.path))
435 && ancestor.redefines_of.is_some()
436 {
437 return true;
438 }
439 }
440 false
441 }
442
443 fn validate_odo_in_group(
450 &self,
451 field: &Field,
452 all_fields: &[&Field],
453 inside_occurs: bool,
454 ) -> Result<()> {
455 if let Some(Occurs::ODO { counter_path, .. }) = &field.occurs {
457 if inside_occurs {
459 return Err(Error::new(
460 ErrorCode::CBKP022_NESTED_ODO,
461 format!(
462 "Nested ODO not supported: field '{}' has OCCURS DEPENDING ON inside another OCCURS/ODO array",
463 field.path
464 ),
465 ));
466 }
467
468 if self.is_inside_redefines(&field.path, all_fields) || field.redefines_of.is_some() {
470 return Err(Error::new(
471 ErrorCode::CBKP023_ODO_REDEFINES,
472 format!(
473 "ODO over REDEFINES not supported: field '{}' has OCCURS DEPENDING ON inside a REDEFINES region",
474 field.path
475 ),
476 ));
477 }
478 let counter_field = all_fields
480 .iter()
481 .find(|f| f.name == *counter_path || f.path == *counter_path);
482
483 if counter_field.is_none() {
484 return Err(Error::new(
485 ErrorCode::CBKS121_COUNTER_NOT_FOUND,
486 format!(
487 "ODO counter field '{}' not found for array '{}'",
488 counter_path, field.name
489 ),
490 ));
491 }
492
493 if let Some(counter) = counter_field {
495 if counter.redefines_of.is_some() {
496 return Err(Error::new(
497 ErrorCode::CBKS121_COUNTER_NOT_FOUND,
498 format!(
499 "ODO counter '{}' cannot be inside a REDEFINES region",
500 counter_path
501 ),
502 ));
503 }
504
505 if self.is_inside_redefines(&counter.path, all_fields) {
506 return Err(Error::new(
507 ErrorCode::CBKS121_COUNTER_NOT_FOUND,
508 format!(
509 "ODO counter '{}' cannot be inside a REDEFINES region",
510 counter_path
511 ),
512 ));
513 }
514
515 if counter.occurs.is_some() {
516 return Err(Error::new(
517 ErrorCode::CBKS121_COUNTER_NOT_FOUND,
518 format!(
519 "ODO counter '{}' cannot be inside an ODO region",
520 counter_path
521 ),
522 ));
523 }
524 }
525 }
526
527 let child_inside_occurs = inside_occurs || field.occurs.is_some();
529
530 for (i, child) in field.children.iter().enumerate() {
532 if child
534 .occurs
535 .as_ref()
536 .is_some_and(|o| matches!(o, Occurs::ODO { .. }))
537 && !self.is_odo_at_tail_sibling_based(child, &field.children, i)
538 {
539 return Err(Error::new(
540 ErrorCode::CBKP021_ODO_NOT_TAIL,
541 format!(
542 "ODO array '{}' must be last storage field under '{}'",
543 child.path, field.path
544 ),
545 ));
546 }
547
548 self.validate_odo_in_group(child, all_fields, child_inside_occurs)?;
550 }
551
552 Ok(())
553 }
554
555 fn is_odo_at_tail_sibling_based(
557 &self,
558 _odo_field: &Field,
559 siblings: &[Field],
560 odo_index: usize,
561 ) -> bool {
562 !siblings
564 .iter()
565 .skip(odo_index + 1)
566 .any(|sibling| self.is_storage_field(sibling))
567 }
569
570 fn is_storage_field(&self, field: &Field) -> bool {
572 if field.level == 88 {
574 return false;
575 }
576
577 match &field.kind {
579 FieldKind::Group => {
580 true
583 }
584 FieldKind::Alphanum { .. }
585 | FieldKind::ZonedDecimal { .. }
586 | FieldKind::BinaryInt { .. }
587 | FieldKind::PackedDecimal { .. }
588 | FieldKind::EditedNumeric { .. }
589 | FieldKind::FloatSingle
590 | FieldKind::FloatDouble => true,
591 FieldKind::Condition { .. } => false, FieldKind::Renames { .. } => false, }
594 }
595
596 fn collect_all_fields(fields: &[Field]) -> Vec<&Field> {
598 let mut result = Vec::new();
599 for field in fields {
600 result.push(field);
601 let children = Self::collect_all_fields(&field.children);
602 result.extend(children);
603 }
604 result
605 }
606
607 fn calculate_schema_fingerprint(&self, schema: &mut Schema) {
609 use sha2::{Digest, Sha256};
610
611 let canonical_json = schema.create_canonical_json();
613
614 let mut hasher = Sha256::new();
616 hasher.update(canonical_json.as_bytes());
617
618 hasher.update(self.options.codepage.as_bytes());
620 hasher.update([if self.options.emit_filler { 1 } else { 0 }]);
621
622 let result = hasher.finalize();
624 schema.fingerprint = format!("{:x}", result);
625 }
626
627 fn parse_pic_clause(&mut self, field: &mut Field) -> Result<()> {
629 let mut pic_parts = Vec::new();
631
632 let token_line = self.current_token().map_or(0, |t| t.line);
634
635 match self.current_token() {
637 Some(TokenPos {
638 token: Token::PicClause(pic),
639 ..
640 }) => {
641 pic_parts.push(pic.clone());
642 self.advance();
643 }
644 Some(TokenPos {
645 token: Token::EditedPic(pic),
646 ..
647 }) => {
648 pic_parts.push(pic.clone());
650 self.advance();
651 }
652 Some(TokenPos {
653 token: Token::Identifier(id),
654 ..
655 }) => {
656 pic_parts.push(id.clone());
658 self.advance();
659 }
660 Some(TokenPos {
661 token: Token::Number(n),
662 ..
663 }) => {
664 pic_parts.push(n.to_string());
666 self.advance();
667 }
668 _ => {
669 return Err(Error::new(
670 ErrorCode::CBKP001_SYNTAX,
671 "Expected PIC clause after PIC keyword",
672 ));
673 }
674 }
675
676 let mut paren_depth: i32 = 0;
678
679 while let Some(token) = self.current_token() {
681 match &token.token {
682 Token::LeftParen => {
683 paren_depth += 1;
684 pic_parts.push("(".to_string());
685 self.advance();
686 }
687 Token::Number(n) if paren_depth > 0 => {
688 pic_parts.push(n.to_string());
689 self.advance();
690 }
691 Token::RightParen if paren_depth > 0 => {
692 paren_depth -= 1;
693 pic_parts.push(")".to_string());
694 self.advance();
695 }
696 Token::Comma => {
697 pic_parts.push(",".to_string());
699 self.advance();
700 }
701 Token::EditedPic(ep) => {
702 pic_parts.push(ep.clone());
704 self.advance();
705 }
706 Token::Period => {
707 let t = token.clone();
713 let last_is_rparen = pic_parts
714 .last()
715 .is_some_and(|s| s == ")" || s.ends_with(')'));
716 if !last_is_rparen
717 && t.line == token_line
718 && let Some(next) = self.peek_next()
719 {
720 let is_pic_continuation = matches!(
721 next.token,
722 Token::Number(_)
723 | Token::PicClause(_)
724 | Token::EditedPic(_)
725 | Token::Identifier(_)
726 );
727 if next.line == token_line && is_pic_continuation {
728 pic_parts.push(".".to_string());
729 self.advance();
730 continue;
731 }
732 }
733 break;
734 }
735 Token::Number(n) => {
736 let t = token.clone();
738 if t.line == token_line {
739 pic_parts.push(n.to_string());
740 self.advance();
741 } else {
742 break;
743 }
744 }
745 Token::Identifier(id) if id.starts_with('V') || id.starts_with('v') => {
746 pic_parts.push(id.clone());
747 self.advance();
748 }
749 Token::Identifier(id) => {
751 let id_upper = id.to_ascii_uppercase();
752 if id_upper == "CR" || id_upper == "DB" {
753 pic_parts.push(id.clone());
754 self.advance();
755 } else {
756 break;
757 }
758 }
759 _ => break,
760 }
761 }
762
763 let pic_str = pic_parts.join("");
764 let pic = PicClause::parse(&pic_str)?;
765
766 field.kind = match pic.kind {
767 crate::pic::PicKind::Alphanumeric => FieldKind::Alphanum {
768 len: pic.digits as u32,
769 },
770 crate::pic::PicKind::NumericDisplay => FieldKind::ZonedDecimal {
771 digits: pic.digits,
772 scale: pic.scale,
773 signed: pic.signed,
774 sign_separate: None,
775 },
776 crate::pic::PicKind::Edited => {
777 FieldKind::EditedNumeric {
779 pic_string: pic_str.clone(),
780 width: pic.digits,
781 scale: pic.scale as u16,
782 signed: pic.signed,
783 }
784 }
785 };
786
787 Ok(())
788 }
789
790 fn parse_usage_clause(&mut self, field: &mut Field) -> Result<()> {
792 match self.current_token() {
793 Some(TokenPos {
794 token: Token::Display,
795 ..
796 }) => {
797 self.advance();
798 }
800 Some(TokenPos {
801 token: Token::Comp, ..
802 }) => {
803 self.advance();
804 self.convert_to_binary_field(field)?;
805 }
806 Some(TokenPos {
807 token: Token::Comp3,
808 ..
809 }) => {
810 self.advance();
811 self.convert_to_packed_field(field)?;
812 }
813 Some(TokenPos {
814 token: Token::Comp1,
815 ..
816 }) => {
817 self.advance();
818 self.require_feature_enabled(
819 Feature::Comp1,
820 &field.name,
821 "comp_1",
822 "USAGE COMP-1",
823 )?;
824 field.kind = FieldKind::FloatSingle;
825 }
826 Some(TokenPos {
827 token: Token::Comp2,
828 ..
829 }) => {
830 self.advance();
831 self.require_feature_enabled(
832 Feature::Comp2,
833 &field.name,
834 "comp_2",
835 "USAGE COMP-2",
836 )?;
837 field.kind = FieldKind::FloatDouble;
838 }
839 Some(TokenPos {
840 token: Token::Binary,
841 ..
842 }) => {
843 self.advance();
844 self.convert_to_binary_field(field)?;
845 }
846 _ => {
847 return Err(Error::new(
848 ErrorCode::CBKP001_SYNTAX,
849 "Expected USAGE type after USAGE keyword",
850 ));
851 }
852 }
853 Ok(())
854 }
855
856 fn parse_redefines_clause(&mut self, field: &mut Field) -> Result<()> {
858 let target = match self.current_token().and_then(try_extract_data_name) {
859 Some(n) => {
860 self.advance();
861 n
862 }
863 _ => {
864 return Err(Error::new(
865 ErrorCode::CBKP001_SYNTAX,
866 "Expected field name after REDEFINES",
867 ));
868 }
869 };
870
871 field.redefines_of = Some(target);
872 Ok(())
873 }
874
875 fn parse_occurs_clause(&mut self, field: &mut Field) -> Result<()> {
877 let min = match self.current_token() {
878 Some(TokenPos {
879 token: Token::Number(n),
880 ..
881 }) => {
882 let min = *n;
883 self.advance();
884 min
885 }
886 Some(TokenPos {
888 token: Token::Level(n),
889 ..
890 }) => {
891 let min = u32::from(*n);
892 self.advance();
893 min
894 }
895 _ => {
896 return Err(Error::new(
897 ErrorCode::CBKP001_SYNTAX,
898 "Expected number after OCCURS",
899 ));
900 }
901 };
902
903 let max = if self.check(&Token::To) {
905 self.advance(); match self.current_token() {
907 Some(TokenPos {
908 token: Token::Number(n),
909 ..
910 }) => {
911 let max = *n;
912 self.advance();
913 max
914 }
915 Some(TokenPos {
917 token: Token::Level(n),
918 ..
919 }) => {
920 let max = u32::from(*n);
921 self.advance();
922 max
923 }
924 _ => {
925 return Err(Error::new(
926 ErrorCode::CBKP001_SYNTAX,
927 "Expected number after TO in OCCURS clause",
928 ));
929 }
930 }
931 } else {
932 min };
934
935 if self.check(&Token::Times) {
937 self.advance();
938 }
939
940 let depending_pos = self.find_depending_in_clause();
942
943 if let Some(depending_idx) = depending_pos {
944 self.current = depending_idx;
946 self.advance(); if !self.consume(&Token::On) {
949 return Err(Error::new(
950 ErrorCode::CBKP001_SYNTAX,
951 "Expected ON after DEPENDING",
952 ));
953 }
954
955 let counter_field = match self.current_token().and_then(try_extract_data_name) {
956 Some(n) => {
957 self.advance();
958 n
959 }
960 _ => {
961 return Err(Error::new(
962 ErrorCode::CBKP001_SYNTAX,
963 "Expected field name after DEPENDING ON",
964 ));
965 }
966 };
967
968 field.occurs = Some(Occurs::ODO {
969 min,
970 max,
971 counter_path: counter_field,
972 });
973 } else {
974 if min != max {
976 return Err(Error::new(
977 ErrorCode::CBKP001_SYNTAX,
978 "Range syntax (min TO max) requires DEPENDING ON clause",
979 ));
980 }
981 field.occurs = Some(Occurs::Fixed { count: min });
982 }
983
984 Ok(())
985 }
986
987 fn find_depending_in_clause(&self) -> Option<usize> {
989 for i in self.current..self.tokens.len() {
990 match &self.tokens[i].token {
991 Token::Depending => return Some(i),
992 Token::Level(_) | Token::Level66 | Token::Level77 | Token::Level88 => break,
994 _ => {}
995 }
996 }
997 None
998 }
999
1000 fn parse_blank_when_zero_clause(&mut self, field: &mut Field) -> Result<()> {
1002 if !self.consume(&Token::When) {
1003 return Err(Error::new(
1004 ErrorCode::CBKP001_SYNTAX,
1005 "Expected WHEN after BLANK",
1006 ));
1007 }
1008
1009 if !self.consume(&Token::Zero) {
1010 return Err(Error::new(
1011 ErrorCode::CBKP001_SYNTAX,
1012 "Expected ZERO after BLANK WHEN",
1013 ));
1014 }
1015
1016 field.blank_when_zero = true;
1017 Ok(())
1018 }
1019
1020 fn parse_sign_clause(&mut self, field: &mut Field) -> Result<()> {
1023 self.consume(&Token::Is);
1025
1026 let mut placement = SignPlacement::Trailing;
1031 let mut saw_placement = false;
1032 let mut saw_separate = false;
1033
1034 for _ in 0..2 {
1035 if self.consume(&Token::Separate) {
1036 if saw_separate {
1037 return Err(Error::new(
1038 ErrorCode::CBKP001_SYNTAX,
1039 "Invalid SIGN clause syntax: duplicate SEPARATE",
1040 ));
1041 }
1042 saw_separate = true;
1043 continue;
1044 }
1045
1046 if self.consume(&Token::Leading) {
1047 if saw_placement {
1048 return Err(Error::new(
1049 ErrorCode::CBKP001_SYNTAX,
1050 "Invalid SIGN clause syntax: duplicate LEADING/TRAILING",
1051 ));
1052 }
1053 placement = SignPlacement::Leading;
1054 saw_placement = true;
1055 continue;
1056 }
1057
1058 if self.consume(&Token::Trailing) {
1059 if saw_placement {
1060 return Err(Error::new(
1061 ErrorCode::CBKP001_SYNTAX,
1062 "Invalid SIGN clause syntax: duplicate LEADING/TRAILING",
1063 ));
1064 }
1065 placement = SignPlacement::Trailing;
1066 saw_placement = true;
1067 continue;
1068 }
1069
1070 break;
1071 }
1072
1073 if !saw_separate {
1074 return Err(Error::new(
1077 ErrorCode::CBKP001_SYNTAX,
1078 "SIGN clause without SEPARATE is not supported (use S in PIC clause for overpunching)",
1079 ));
1080 }
1081
1082 if let Some(next) = self.current_token() {
1085 let next_is_boundary = matches!(
1086 &next.token,
1087 Token::Period
1088 | Token::Newline
1089 | Token::InlineComment(_)
1090 | Token::Pic
1091 | Token::Usage
1092 | Token::Redefines
1093 | Token::Renames
1094 | Token::Occurs
1095 | Token::Synchronized
1096 | Token::Value
1097 | Token::Blank
1098 | Token::Sign
1099 | Token::Comp
1100 | Token::Comp3
1101 | Token::Comp1
1102 | Token::Comp2
1103 | Token::Binary
1104 );
1105
1106 if !next_is_boundary {
1107 return Err(Error::new(
1108 ErrorCode::CBKP001_SYNTAX,
1109 "Invalid SIGN clause syntax. Expected SEPARATE with optional LEADING/TRAILING",
1110 ));
1111 }
1112 }
1113
1114 match &mut field.kind {
1116 FieldKind::ZonedDecimal {
1117 digits,
1118 scale,
1119 signed,
1120 sign_separate: _,
1121 } => {
1122 *signed = true;
1123 let sign_separate_info = SignSeparateInfo { placement };
1125 field.kind = FieldKind::ZonedDecimal {
1127 digits: *digits,
1128 scale: *scale,
1129 signed: true,
1130 sign_separate: Some(sign_separate_info),
1131 };
1132 }
1133 _ => {
1134 return Err(Error::new(
1135 ErrorCode::CBKP001_SYNTAX,
1136 format!(
1137 "SIGN SEPARATE clause can only be used with numeric display fields (PIC 9 or PIC S9), not field '{}'",
1138 field.name
1139 ),
1140 ));
1141 }
1142 }
1143
1144 if field.blank_when_zero {
1146 return Err(Error::new(
1147 ErrorCode::CBKP001_SYNTAX,
1148 "SIGN SEPARATE clause cannot be combined with BLANK WHEN ZERO",
1149 ));
1150 }
1151
1152 Ok(())
1153 }
1154
1155 fn parse_level88_value_clause(&mut self, field: &mut Field) -> Result<()> {
1157 let mut values = Vec::new();
1158
1159 loop {
1161 match self.current_token() {
1162 Some(TokenPos {
1163 token: Token::StringLiteral(s),
1164 ..
1165 }) => {
1166 values.push(s.clone());
1167 self.advance();
1168 }
1169 Some(TokenPos {
1170 token: Token::Number(n),
1171 ..
1172 }) => {
1173 values.push(n.to_string());
1174 self.advance();
1175 }
1176 Some(TokenPos {
1177 token: Token::Identifier(id),
1178 ..
1179 }) if id.to_uppercase() == "ZEROS" || id.to_uppercase() == "ZEROES" => {
1180 values.push("ZEROS".to_string());
1181 self.advance();
1182 }
1183 Some(TokenPos {
1184 token: Token::Identifier(id),
1185 ..
1186 }) if id.to_uppercase() == "SPACES" => {
1187 values.push("SPACES".to_string());
1188 self.advance();
1189 }
1190 _ => break,
1191 }
1192
1193 let range_keyword = match self.current_token() {
1195 Some(TokenPos {
1196 token: Token::Through,
1197 ..
1198 }) => {
1199 self.advance();
1200 "THROUGH"
1201 }
1202 Some(TokenPos {
1203 token: Token::Thru, ..
1204 }) => {
1205 self.advance();
1206 "THRU"
1207 }
1208 _ => {
1209 if let Some(TokenPos {
1211 token: Token::Comma,
1212 ..
1213 }) = self.current_token()
1214 {
1215 self.advance();
1216 }
1217 continue;
1218 }
1219 };
1220
1221 match self.current_token() {
1223 Some(TokenPos {
1224 token: Token::StringLiteral(s),
1225 ..
1226 }) => {
1227 if let Some(last) = values.last_mut() {
1229 *last = format!("{} {} {}", last, range_keyword, s);
1230 }
1231 self.advance();
1232 }
1233 Some(TokenPos {
1234 token: Token::Number(n),
1235 ..
1236 }) => {
1237 if let Some(last) = values.last_mut() {
1238 *last = format!("{} {} {}", last, range_keyword, n);
1239 }
1240 self.advance();
1241 }
1242 _ => break,
1243 }
1244
1245 self.consume(&Token::Comma);
1247 }
1248
1249 if values.is_empty() {
1250 return Err(Error::new(
1251 ErrorCode::CBKP001_SYNTAX,
1252 "Level-88 VALUE clause requires at least one value",
1253 ));
1254 }
1255
1256 field.kind = FieldKind::Condition { values };
1258
1259 Ok(())
1260 }
1261
1262 fn parse_qualified_name(&mut self) -> Result<String> {
1264 let mut parts = Vec::new();
1265
1266 match self.current_token().and_then(try_extract_data_name) {
1268 Some(n) => {
1269 parts.push(n);
1270 self.advance();
1271 }
1272 _ => {
1273 return Err(Error::new(
1274 ErrorCode::CBKP001_SYNTAX,
1275 "Expected identifier in qualified name",
1276 ));
1277 }
1278 }
1279
1280 loop {
1282 match self.current_token() {
1283 Some(TokenPos {
1284 token: Token::Identifier(name),
1285 ..
1286 }) if name.eq_ignore_ascii_case("OF") => {
1287 self.advance(); match self.current_token().and_then(try_extract_data_name) {
1289 Some(n) => {
1290 parts.push("OF".to_string());
1291 parts.push(n);
1292 self.advance();
1293 }
1294 _ => {
1295 return Err(Error::new(
1296 ErrorCode::CBKP001_SYNTAX,
1297 "Expected identifier after OF in qualified name",
1298 ));
1299 }
1300 }
1301 }
1302 _ => break,
1303 }
1304 }
1305
1306 Ok(parts.join(" "))
1307 }
1308
1309 fn parse_renames(&mut self, field: &mut Field) -> Result<()> {
1314 match self.current_token() {
1316 Some(TokenPos {
1317 token: Token::Renames,
1318 ..
1319 }) => {
1320 self.advance();
1321 }
1322 _ => {
1323 return Err(Error::new(
1324 ErrorCode::CBKP001_SYNTAX,
1325 "Expected RENAMES keyword after level-66 field name",
1326 ));
1327 }
1328 }
1329
1330 let from_field = self.parse_qualified_name()?;
1332
1333 let thru_field = if self.check(&Token::Through) || self.check(&Token::Thru) {
1334 self.advance();
1335 self.parse_qualified_name()?
1336 } else {
1337 from_field.clone()
1339 };
1340
1341 field.kind = FieldKind::Renames {
1343 from_field,
1344 thru_field,
1345 };
1346
1347 Ok(())
1348 }
1349
1350 fn skip_value_clause(&mut self) -> Result<()> {
1352 self.advance(); while !self.is_at_end() && !self.check(&Token::Period) {
1356 if self.is_keyword() {
1357 break;
1358 }
1359 self.advance();
1360 }
1361
1362 Ok(())
1363 }
1364
1365 fn convert_to_binary_field(&mut self, field: &mut Field) -> Result<()> {
1367 match &field.kind {
1368 FieldKind::ZonedDecimal { digits, signed, .. } => {
1369 let bits = match digits {
1370 1..=4 => 16,
1371 5..=9 => 32,
1372 10..=18 => 64,
1373 _ => {
1374 return Err(Error::new(
1375 ErrorCode::CBKP001_SYNTAX,
1376 format!("Binary field with {} digits not supported", digits),
1377 ));
1378 }
1379 };
1380
1381 field.kind = FieldKind::BinaryInt {
1382 bits,
1383 signed: *signed,
1384 };
1385 }
1386 _ => {
1387 return Err(Error::new(
1388 ErrorCode::CBKP001_SYNTAX,
1389 "USAGE COMP/BINARY can only be applied to numeric fields",
1390 ));
1391 }
1392 }
1393 Ok(())
1394 }
1395
1396 fn convert_to_packed_field(&mut self, field: &mut Field) -> Result<()> {
1398 match &field.kind {
1399 FieldKind::ZonedDecimal {
1400 digits,
1401 scale,
1402 signed,
1403 ..
1404 } => {
1405 field.kind = FieldKind::PackedDecimal {
1406 digits: *digits,
1407 scale: *scale,
1408 signed: *signed,
1409 };
1410 }
1411 _ => {
1412 return Err(Error::new(
1413 ErrorCode::CBKP001_SYNTAX,
1414 "USAGE COMP-3 can only be applied to numeric fields",
1415 ));
1416 }
1417 }
1418 Ok(())
1419 }
1420
1421 fn skip_comments_and_newlines(&mut self) -> Result<()> {
1423 while let Some(token) = self.current_token() {
1424 match &token.token {
1425 Token::InlineComment(_) => {
1426 if self.options.strict_comments {
1427 return Err(Error::new(
1428 ErrorCode::CBKP001_SYNTAX,
1429 "Inline comments (*>) are not allowed in strict mode",
1430 ));
1431 }
1432 self.advance();
1433 }
1434 Token::Newline => {
1435 self.advance();
1436 }
1437 _ => break,
1438 }
1439 }
1440 Ok(())
1441 }
1442
1443 fn is_keyword(&self) -> bool {
1445 matches!(
1446 self.current_token().map(|t| &t.token),
1447 Some(
1448 Token::Pic
1449 | Token::Usage
1450 | Token::Redefines
1451 | Token::Occurs
1452 | Token::Synchronized
1453 | Token::Value
1454 | Token::Blank
1455 | Token::Sign
1456 )
1457 )
1458 }
1459
1460 fn current_token(&self) -> Option<&TokenPos> {
1462 self.tokens.get(self.current)
1463 }
1464
1465 fn peek_next(&self) -> Option<&TokenPos> {
1467 self.tokens.get(self.current + 1)
1468 }
1469
1470 fn advance(&mut self) {
1472 if !self.is_at_end() {
1473 self.current += 1;
1474 }
1475 }
1476
1477 fn is_at_end(&self) -> bool {
1479 matches!(
1480 self.current_token().map(|t| &t.token),
1481 Some(Token::Eof) | None
1482 )
1483 }
1484
1485 fn check(&self, token: &Token) -> bool {
1487 if let Some(current) = self.current_token() {
1488 std::mem::discriminant(¤t.token) == std::mem::discriminant(token)
1489 } else {
1490 false
1491 }
1492 }
1493
1494 fn consume(&mut self, token: &Token) -> bool {
1496 if self.check(token) {
1497 self.advance();
1498 true
1499 } else {
1500 false
1501 }
1502 }
1503}
1504
1505fn finalize_names_and_paths(fields: &mut [Field], parent_path: Option<&str>, emit_filler: bool) {
1511 use std::collections::HashMap;
1512
1513 let mut counts: HashMap<String, u32> = HashMap::new();
1514
1515 for field in fields.iter_mut() {
1516 if field.name == "FILLER" && !emit_filler {
1518 field.path = match parent_path {
1519 Some(parent) => format!("{parent}.{}", field.name),
1520 None => field.name.clone(),
1521 };
1522 continue;
1523 }
1524
1525 let base = field.name.clone();
1526 let count = counts.entry(base.clone()).or_insert(0);
1527 *count += 1;
1528 if *count > 1 {
1529 field.name = format!("{base}__dup{count}");
1530 }
1531
1532 field.path = match parent_path {
1533 Some(parent) => format!("{parent}.{}", field.name),
1534 None => field.name.clone(),
1535 };
1536 }
1537
1538 for field in fields.iter_mut() {
1539 let parent = field.path.clone();
1540 finalize_names_and_paths(&mut field.children, Some(&parent), emit_filler);
1541 }
1542}
1543
1544#[cfg(test)]
1545#[allow(clippy::expect_used)]
1546#[allow(clippy::unwrap_used)]
1547#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
1548mod tests {
1549 use super::*;
1550
1551 #[test]
1552 fn test_simple_field_parsing() {
1553 let input = "01 CUSTOMER-ID PIC X(10).";
1554 let schema = parse(input)
1555 .map_err(|e| {
1556 Error::new(
1557 ErrorCode::CBKS141_RECORD_TOO_LARGE,
1558 format!("Failed to parse copybook: {}", e),
1559 )
1560 })
1561 .expect("Failed to parse copybook");
1562
1563 assert_eq!(schema.fields.len(), 1);
1564 let field = &schema.fields[0];
1565 assert_eq!(field.name, "CUSTOMER-ID");
1566 assert_eq!(field.level, 1);
1567 assert!(matches!(field.kind, FieldKind::Alphanum { len: 10 }));
1568 }
1569
1570 #[test]
1571 fn test_numeric_field_parsing() {
1572 let input = "01 AMOUNT PIC S9(7)V99.";
1573
1574 let mut lexer = crate::lexer::Lexer::new(input);
1576 let tokens = lexer.tokenize();
1577 for (i, token) in tokens.iter().enumerate() {
1578 println!("Token {}: {:?}", i, token.token);
1579 }
1580
1581 let pic_result = crate::pic::PicClause::parse("S9(7)V99");
1583 println!("PIC parse result: {:?}", pic_result);
1584
1585 let schema = parse(input).unwrap();
1586
1587 assert_eq!(schema.fields.len(), 1);
1588 let field = &schema.fields[0];
1589 assert_eq!(field.name, "AMOUNT");
1590
1591 println!("Field kind: {:?}", field.kind);
1593
1594 assert!(matches!(
1595 field.kind,
1596 FieldKind::ZonedDecimal {
1597 digits: 9,
1598 scale: 2,
1599 signed: true,
1600 ..
1601 }
1602 ));
1603 }
1604
1605 #[test]
1606 fn test_binary_field_parsing() {
1607 let input = "01 COUNT PIC 9(5) USAGE COMP.";
1608 let schema = parse(input).unwrap();
1609
1610 assert_eq!(schema.fields.len(), 1);
1611 let field = &schema.fields[0];
1612 assert!(matches!(
1613 field.kind,
1614 FieldKind::BinaryInt {
1615 bits: 32,
1616 signed: false
1617 }
1618 ));
1619 }
1620
1621 #[test]
1622 fn test_occurs_parsing() {
1623 let input = "01 ARRAY-FIELD PIC X(10) OCCURS 5 TIMES.";
1624 let schema = parse(input).unwrap();
1625
1626 assert_eq!(schema.fields.len(), 1);
1627 let field = &schema.fields[0];
1628 assert!(matches!(field.occurs, Some(Occurs::Fixed { count: 5 })));
1629 }
1630
1631 #[test]
1632 fn test_redefines_parsing() {
1633 let input = r"
163401 FIELD-A PIC X(10).
163501 FIELD-B REDEFINES FIELD-A PIC 9(10).
1636";
1637 let schema = parse(input).unwrap();
1638
1639 assert_eq!(schema.fields.len(), 2);
1640 let field_b = &schema.fields[1];
1641 assert_eq!(field_b.redefines_of, Some("FIELD-A".to_string()));
1642 }
1643
1644 #[test]
1645 fn test_edited_pic_acceptance() {
1646 let input = "01 AMOUNT PIC ZZZZ.";
1649 let result = parse(input);
1650
1651 assert!(result.is_ok(), "Edited PIC should parse successfully");
1652 let schema = result.unwrap();
1653 assert_eq!(schema.fields.len(), 1);
1654 assert!(matches!(
1655 schema.fields[0].kind,
1656 FieldKind::EditedNumeric { .. }
1657 ));
1658 }
1659
1660 #[test]
1661 fn test_sign_clause_without_separate_rejected() {
1662 let flags = FeatureFlags::from_env();
1665 if !flags.is_enabled(Feature::SignSeparate) {
1666 return;
1667 }
1668 let input = "01 AMOUNT PIC S9(5) SIGN LEADING.";
1669 let result = parse(input);
1670
1671 assert!(
1672 result.is_err(),
1673 "SIGN clause without SEPARATE should be rejected"
1674 );
1675 let error = result.unwrap_err();
1676 assert_eq!(error.code, ErrorCode::CBKP001_SYNTAX);
1678 }
1679
1680 #[test]
1681 fn test_sign_leading_separate_accepted() {
1682 let flags = FeatureFlags::from_env();
1685 if !flags.is_enabled(Feature::SignSeparate) {
1686 return;
1687 }
1688 let input = "01 AMOUNT PIC S9(5) SIGN IS LEADING SEPARATE.";
1689 let result = parse(input);
1690 assert!(
1691 result.is_ok(),
1692 "SIGN IS LEADING SEPARATE should be accepted"
1693 );
1694 }
1695
1696 #[test]
1697 fn test_sign_trailing_separate_accepted() {
1698 let flags = FeatureFlags::from_env();
1701 if !flags.is_enabled(Feature::SignSeparate) {
1702 return;
1703 }
1704 let input = "01 AMOUNT PIC S9(5) SIGN TRAILING SEPARATE.";
1705 let result = parse(input);
1706 assert!(result.is_ok(), "SIGN TRAILING SEPARATE should be accepted");
1707 }
1708
1709 #[test]
1710 fn test_schema_fingerprint() {
1711 let input = "01 CUSTOMER-ID PIC X(10).";
1712 let schema = parse(input).unwrap();
1713
1714 assert!(!schema.fingerprint.is_empty());
1716 assert_ne!(schema.fingerprint, "placeholder");
1717
1718 let schema2 = parse(input).unwrap();
1720 assert_eq!(schema.fingerprint, schema2.fingerprint);
1721 }
1722
1723 #[test]
1724 fn test_duplicate_name_handling() {
1725 let input = r"
172601 RECORD-A.
1727 05 FIELD-NAME PIC X(10).
1728 05 FIELD-NAME PIC 9(5).
1729";
1730 let schema = parse(input).unwrap();
1731
1732 assert_eq!(schema.fields.len(), 1);
1734 let root = &schema.fields[0];
1735 assert_eq!(root.name, "RECORD-A");
1736 assert!(matches!(root.kind, FieldKind::Group));
1737
1738 assert_eq!(root.children.len(), 2);
1740 assert_eq!(root.children[0].name, "FIELD-NAME");
1741 assert_eq!(root.children[1].name, "FIELD-NAME__dup2");
1742 }
1743
1744 #[test]
1745 fn test_parent_child_same_name_does_not_get_dup_suffix() {
1746 let input = "01 U-J.\n 05 U-J PIC X(5).";
1747 let schema = parse(input).unwrap();
1748 let group = &schema.fields[0];
1749 let child = &group.children[0];
1750 assert_eq!(child.name, "U-J");
1751 assert_eq!(child.path, "U-J.U-J");
1752 }
1753
1754 #[test]
1755 fn test_odo_validation() {
1756 let input = r"
175701 COUNTER PIC 9(3).
175801 ARRAY-FIELD PIC X(10) OCCURS 5 TIMES DEPENDING ON COUNTER.
1759";
1760 let result = parse(input);
1761
1762 assert!(result.is_ok());
1764 let schema = result.unwrap();
1765 assert_eq!(schema.fields.len(), 2);
1766
1767 let odo_field = &schema.fields[1];
1769 if let Some(Occurs::ODO {
1770 max, counter_path, ..
1771 }) = &odo_field.occurs
1772 {
1773 assert_eq!(*max, 5);
1774 assert_eq!(counter_path, "COUNTER");
1775 } else {
1776 panic!("Expected ODO occurs, got {:?}", odo_field.occurs);
1777 }
1778 }
1779
1780 #[test]
1781 fn test_redefines_validation() {
1782 let input = r"
178301 FIELD-A PIC X(10).
178401 FIELD-B REDEFINES FIELD-A PIC 9(10).
1785";
1786 let result = parse(input);
1787
1788 assert!(result.is_ok());
1790 let schema = result.unwrap();
1791 assert_eq!(schema.fields.len(), 2);
1792
1793 let field_b = &schema.fields[1];
1794 assert_eq!(field_b.redefines_of, Some("FIELD-A".to_string()));
1795 }
1796
1797 #[test]
1798 fn test_hierarchical_structure() {
1799 let input = r"
180001 CUSTOMER-RECORD.
1801 05 CUSTOMER-ID PIC X(10).
1802 05 CUSTOMER-NAME.
1803 10 FIRST-NAME PIC X(20).
1804 10 LAST-NAME PIC X(30).
1805 05 CUSTOMER-ADDRESS.
1806 10 STREET PIC X(38).
1807 10 CITY PIC X(30).
1808";
1809 let schema = parse(input).unwrap();
1810
1811 assert_eq!(schema.fields.len(), 1);
1813 let root = &schema.fields[0];
1814 assert_eq!(root.name, "CUSTOMER-RECORD");
1815 assert!(matches!(root.kind, FieldKind::Group));
1816
1817 assert_eq!(root.children.len(), 3);
1819
1820 let customer_id = &root.children[0];
1822 assert_eq!(customer_id.name, "CUSTOMER-ID");
1823 assert_eq!(customer_id.path, "CUSTOMER-RECORD.CUSTOMER-ID");
1824 assert!(matches!(customer_id.kind, FieldKind::Alphanum { len: 10 }));
1825
1826 let customer_name = &root.children[1];
1828 assert_eq!(customer_name.name, "CUSTOMER-NAME");
1829 assert_eq!(customer_name.path, "CUSTOMER-RECORD.CUSTOMER-NAME");
1830 assert!(matches!(customer_name.kind, FieldKind::Group));
1831 assert_eq!(customer_name.children.len(), 2);
1832
1833 let first_name = &customer_name.children[0];
1835 assert_eq!(first_name.name, "FIRST-NAME");
1836 assert_eq!(first_name.path, "CUSTOMER-RECORD.CUSTOMER-NAME.FIRST-NAME");
1837 }
1838
1839 #[test]
1840 fn test_sha256_fingerprint() {
1841 let input = "01 CUSTOMER-ID PIC X(10).";
1842 let schema = parse(input).unwrap();
1843
1844 assert_eq!(schema.fingerprint.len(), 64);
1846 assert!(schema.fingerprint.chars().all(|c| c.is_ascii_hexdigit()));
1847
1848 let schema2 = parse(input).unwrap();
1850 assert_eq!(schema.fingerprint, schema2.fingerprint);
1851
1852 let input2 = "01 CUSTOMER-ID PIC X(20).";
1854 let schema3 = parse(input2).unwrap();
1855 assert_ne!(schema.fingerprint, schema3.fingerprint);
1856 }
1857
1858 #[test]
1859 fn test_invalid_redefines_target() {
1860 let input = r"
186101 FIELD-A PIC X(10).
186201 FIELD-B REDEFINES NONEXISTENT PIC 9(10).
1863";
1864 let result = parse(input);
1865
1866 assert!(result.is_err());
1868 assert!(matches!(
1869 result.unwrap_err().code,
1870 ErrorCode::CBKP001_SYNTAX
1871 ));
1872 }
1873
1874 #[test]
1875 fn test_blank_when_zero() {
1876 let input = "01 AMOUNT PIC 9(5) BLANK WHEN ZERO.";
1877 let schema = parse(input).unwrap();
1878
1879 assert_eq!(schema.fields.len(), 1);
1880 let field = &schema.fields[0];
1881 assert!(field.blank_when_zero);
1882 }
1883
1884 #[test]
1885 fn test_synchronized_field() {
1886 let input = "01 BINARY-FIELD PIC 9(5) USAGE COMP SYNCHRONIZED.";
1887 let schema = parse(input).unwrap();
1888
1889 assert_eq!(schema.fields.len(), 1);
1890 let field = &schema.fields[0];
1891 assert!(field.synchronized);
1892 assert!(matches!(field.kind, FieldKind::BinaryInt { .. }));
1893 }
1894
1895 #[test]
1896 fn test_edited_pic_ambiguous_field_name() {
1897 let input = "01 Z-Z PIC X(10).";
1899 let schema = parse(input).expect("should parse Z-Z as field name");
1900 assert_eq!(schema.fields.len(), 1);
1901 assert_eq!(schema.fields[0].name, "Z-Z");
1902 }
1903
1904 #[test]
1905 fn test_pic_clause_ambiguous_field_name() {
1906 let input = "01 ZZ PIC X(5).";
1908 let schema = parse(input).expect("should parse ZZ as field name");
1909 assert_eq!(schema.fields.len(), 1);
1910 assert_eq!(schema.fields[0].name, "ZZ");
1911 }
1912
1913 #[test]
1914 fn test_redefines_target_ambiguous_name() {
1915 let input = "01 Z-Z PIC X(10).\n01 ALT REDEFINES Z-Z PIC X(10).";
1916 let schema = parse(input).expect("should parse REDEFINES Z-Z");
1917 assert_eq!(schema.fields[1].redefines_of.as_deref(), Some("Z-Z"));
1918 }
1919
1920 #[test]
1921 fn test_depending_on_ambiguous_name() {
1922 let input = "01 Z-Z PIC 9(3).\n01 ITEMS PIC X(5) OCCURS 5 TIMES DEPENDING ON Z-Z.";
1923 let schema = parse(input).expect("should parse DEPENDING ON Z-Z");
1924 let items = &schema.fields[1];
1925 match &items.occurs {
1926 Some(Occurs::ODO { counter_path, .. }) => {
1927 assert_eq!(counter_path, "Z-Z");
1928 }
1929 other => panic!("Expected ODO occurs, got {:?}", other),
1930 }
1931 }
1932}