1use crate::corresponding::is_alphabetic;
7use crate::layout::{self, Kind, Layout, Resolved};
8use crate::picture::Notation;
9use numeric::Qualify;
10use rt::picture::Sym;
11use syntax::ast::*;
12use syntax::{Error, Pos};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct Udf {
17 pub name: String,
18 pub external: String,
19 pub params: Vec<Formal>,
20 pub result: Formal,
21 pub pos: Pos,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct Formal {
28 pub name: String,
29 pub by_value: bool,
30 pub kind: Kind,
31 pub size: u32,
32 pub scaling: u32,
33 pub alphabetic: bool,
34 pub edit: Option<(Vec<Sym>, String)>,
36 pub decimal_point_comma: bool,
37}
38
39impl Udf {
40 pub fn character_valued(&self) -> bool {
42 matches!(self.result.kind, Kind::Group | Kind::Alnum { .. } | Kind::AlnumEdited { .. } | Kind::National)
43 }
44}
45
46pub fn signature(p: &Prototype, qualify: Qualify) -> Result<Udf, Error> {
48 let layout = layout::build(&[], &[], &[], &p.linkage, &[], Notation::of(&p.environment), qualify, None)?;
49 let alphabetic: Vec<Pos> = p.linkage.iter().filter(|e| e.picture.as_deref().is_some_and(is_alphabetic)).map(|e| e.pos).collect();
50 let formal = |name: &str, by_value: bool| {
51 let root = layout.linkage_roots.iter().copied().find(|&i| layout.items[i].name.as_deref() == Some(name));
52 let item = &layout.items[root.ok_or_else(|| Error::at(p.pos, format!("FUNCTION-ID {}: {name} is not an 01 or 77 item of the LINKAGE SECTION", p.name)))?];
53 let edit = match item.kind {
54 Kind::NumericEdited { edit, .. } | Kind::AlnumEdited { edit } => Some((layout.edits[edit as usize].clone(), layout.currencies[edit as usize].clone())),
55 _ => None,
56 };
57 Ok(Formal {
58 name: name.to_owned(),
59 by_value,
60 kind: item.kind,
61 size: item.size,
62 scaling: item.scaling,
63 alphabetic: alphabetic.contains(&item.pos),
64 edit,
65 decimal_point_comma: p.environment.decimal_point_comma,
66 })
67 };
68 let returning = p.returning.as_deref().ok_or_else(|| Error::at(p.pos, format!("FUNCTION-ID {}: a user-defined function needs PROCEDURE DIVISION RETURNING", p.name)))?;
69 let params = p.using.iter().map(|u| formal(&u.name, u.by_value)).collect::<Result<_, _>>()?;
70 Ok(Udf { name: p.name.clone(), external: p.external.clone(), params, result: formal(returning, false)?, pos: p.pos })
71}
72
73pub fn functions(program: &Program, qualify: Qualify, errors: &mut Vec<Error>) -> Vec<Udf> {
76 let own = program.function.as_ref().map(|f| f.pos);
77 let mut out = Vec::new();
78 for p in &program.prototypes {
79 match signature(p, qualify) {
80 Ok(udf) => out.push(udf),
81 Err(e) if Some(p.pos) == own => errors.push(e),
82 Err(_) => {}
83 }
84 }
85 if let Some(own) = own {
86 definition_rules(program, &out, own, errors);
87 }
88 facilities(program, errors);
89 out
90}
91
92fn facilities(program: &Program, errors: &mut Vec<Error>) {
96 if program.function.is_none() && program.prototypes.is_empty() {
97 return;
98 }
99 let mut all = Vec::new();
100 program.paragraphs.iter().for_each(|p| crate::inner_statements(&p.statements, &mut all));
101 let statements = all.into_iter().filter_map(|s| match s {
102 Stmt::Exec(b) => Some(b.as_ref()),
103 _ => None,
104 });
105 if let Some(b) = program.exec_declarations.iter().chain(statements).find(|b| matches!(b.kind, ExecKind::Sql | ExecKind::Cics)) {
106 let kind = if b.kind == ExecKind::Sql { "SQL" } else { "CICS" };
107 errors.push(Error::at(b.pos, format!("EXEC {kind}: SQL and CICS cannot be used with user-defined functions, so neither in one nor in a program after one in its source (assumption C273)")));
108 }
109}
110
111fn definition_rules(program: &Program, functions: &[Udf], own: Pos, errors: &mut Vec<Error>) {
114 let Some(this) = functions.iter().find(|u| u.pos == own) else { return };
115 for f in this.params.iter().filter(|f| f.by_value) {
116 let one_character = matches!(f.kind, Kind::Alnum { .. }) && f.size == 1 || f.kind == Kind::National && f.size == 2;
117 if !(one_character || matches!(f.kind, Kind::Binary { .. } | Kind::Float(_) | Kind::Pointer | Kind::ProgramPointer)) {
118 errors.push(Error::at(own, format!("PROCEDURE DIVISION USING BY VALUE {}: a function's BY VALUE parameter is binary, floating-point, a pointer, or one alphanumeric or national character", f.name)));
119 }
120 }
121 for other in functions.iter().filter(|u| u.name == this.name && u.pos != own) {
122 if let Some(why) = disagreement(this, other) {
123 errors.push(Error::at(own, format!("FUNCTION-ID {}: {why} from the prototype at line {}", program.id, other.pos.line)));
124 }
125 }
126}
127
128fn disagreement(a: &Udf, b: &Udf) -> Option<String> {
129 if a.external != b.external {
130 return Some(format!("the external name {} differs", a.external));
131 }
132 if a.params.len() != b.params.len() {
133 return Some(format!("{} parameters differ in number", a.params.len()));
134 }
135 let same = |x: &Formal, y: &Formal| (x.by_value, x.kind, x.size, x.scaling, x.alphabetic, &x.edit) == (y.by_value, y.kind, y.size, y.scaling, y.alphabetic, &y.edit);
136 if let Some(k) = (0..a.params.len()).find(|&k| !same(&a.params[k], &b.params[k])) {
137 return Some(format!("parameter {} ({}) differs", k + 1, a.params[k].name));
138 }
139 (!same(&a.result, &b.result)).then(|| format!("the RETURNING item {} differs", a.result.name))
140}
141
142pub fn conformance(layout: &Layout, item: usize, alphabetic: bool, decimal_point_comma: bool, formal: &Formal) -> Option<String> {
146 let it = &layout.items[item];
147 if formal.by_value {
148 if it.kind == Kind::Group {
149 return Some("a group is never passed BY VALUE".into());
150 }
151 if arithmetic(formal.kind) && !arithmetic(it.kind) {
152 return Some(format!("BY VALUE {} is numeric, and takes an argument COMPUTE could send it", formal.name));
153 }
154 return None;
155 }
156 if it.kind == Kind::Group || formal.kind == Kind::Group {
157 return (it.size < formal.size).then(|| format!("{} bytes cannot be passed BY REFERENCE to the {}-byte {}", it.size, formal.size, formal.name));
158 }
159 let edit = match it.kind {
160 Kind::NumericEdited { edit, .. } | Kind::AlnumEdited { edit } => Some((layout.edits[edit as usize].clone(), layout.currencies[edit as usize].clone())),
161 _ => None,
162 };
163 let conforms = match (it.kind, formal.kind) {
164 (Kind::Alnum { justified: a }, Kind::Alnum { justified: b }) => a == b && alphabetic == formal.alphabetic && it.size >= formal.size,
165 (Kind::National, Kind::National) => it.size >= formal.size,
166 (a, b) => a == b && it.size == formal.size && it.scaling == formal.scaling && edit == formal.edit && (edit.is_none() || decimal_point_comma == formal.decimal_point_comma),
167 };
168 (!conforms).then(|| format!("{} is passed BY REFERENCE, so its PICTURE, USAGE, SIGN, JUSTIFIED and BLANK WHEN ZERO must be the argument's", formal.name))
169}
170
171pub fn check_invocation(udf: &Udf, f: &FunctionCall, layout: &Layout, alphabetic: &[Pos], decimal_point_comma: bool, errors: &mut Vec<Error>) {
174 let name = &f.name;
175 if f.args.len() != udf.params.len() {
176 errors.push(Error::at(f.pos, format!("FUNCTION {name} takes {} arguments, not {}", udf.params.len(), f.args.len())));
177 return;
178 }
179 if f.modifier.is_some() || !f.all_subscripts.is_empty() {
180 errors.push(Error::at(f.pos, format!("FUNCTION {name}: a user-defined function's argument is an identifier, a literal or an arithmetic expression")));
181 }
182 if f.refmod.is_some() && !udf.character_valued() {
183 errors.push(Error::at(f.pos, format!("FUNCTION {name}: only an alphanumeric or national function's value can be reference-modified")));
184 }
185 for (k, (arg, formal)) in f.args.iter().zip(&udf.params).enumerate() {
186 let r = match arg {
187 Expr::Operand(Operand::Literal(Literal::Figurative(_) | Literal::All(_))) => {
188 errors.push(Error::at(f.pos, format!("FUNCTION {name} argument {}: a function's argument is not a figurative constant", k + 1)));
189 continue;
190 }
191 Expr::Operand(Operand::Literal(Literal::Alnum(_) | Literal::Hex(_) | Literal::National(_))) if arithmetic(formal.kind) => {
192 errors.push(Error::at(f.pos, format!("FUNCTION {name} argument {}: {} is numeric, and takes an argument COMPUTE could send it (assumption C272)", k + 1, formal.name)));
193 continue;
194 }
195 Expr::Operand(Operand::Ref(r)) if r.refmod.is_none() => r,
196 _ => continue,
197 };
198 if let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos)
199 && let Some(why) = conformance(layout, i, alphabetic.contains(&layout.items[i].pos), decimal_point_comma, formal)
200 {
201 errors.push(Error::at(r.pos, format!("FUNCTION {name} argument {} ({}): {why}", k + 1, r.name)));
202 }
203 }
204}
205
206fn arithmetic(kind: Kind) -> bool {
209 matches!(kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_))
210}