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 pub any_length: bool,
39}
40
41impl Udf {
42 pub fn character_valued(&self) -> bool {
44 matches!(self.result.kind, Kind::Group | Kind::Alnum { .. } | Kind::AlnumEdited { .. } | Kind::National)
45 }
46}
47
48pub fn signature(p: &Prototype, qualify: Qualify) -> Result<Udf, Error> {
50 let layout = layout::build(&[], &[], &[], &p.linkage, p.linkage.len(), &[], Notation::of(&p.environment), qualify, None)?;
51 let alphabetic: Vec<Pos> = p.linkage.iter().filter(|e| e.picture.as_deref().is_some_and(is_alphabetic)).map(|e| e.pos).collect();
52 let formal = |name: &str, by_value: bool| -> Result<Formal, Error> {
53 let root = layout.linkage_roots.iter().copied().find(|&i| layout.items[i].name.as_deref() == Some(name));
54 let item = &layout.items[root.ok_or_else(|| syntax::messages::IWC0016.at(p.pos, format!("FUNCTION-ID {}: {name} is not an 01 or 77 item of the LINKAGE SECTION", p.name)))?];
55 let edit = match item.kind {
56 Kind::NumericEdited { edit, .. } | Kind::AlnumEdited { edit } => Some((layout.edits[edit as usize].clone(), layout.currencies[edit as usize].clone())),
57 _ => None,
58 };
59 Ok(Formal {
60 name: name.to_owned(),
61 by_value,
62 kind: item.kind,
63 size: item.size,
64 scaling: item.scaling,
65 alphabetic: alphabetic.contains(&item.pos),
66 edit,
67 decimal_point_comma: p.environment.decimal_point_comma,
68 any_length: p.linkage.iter().any(|e| e.any_length && e.name.as_deref() == Some(name)),
69 })
70 };
71 let returning = p.returning.as_deref().ok_or_else(|| syntax::messages::IWC0017.at(p.pos, format!("FUNCTION-ID {}: a user-defined function needs PROCEDURE DIVISION RETURNING", p.name)))?;
72 let params = p.using.iter().map(|u| formal(&u.name, u.by_value)).collect::<Result<_, _>>()?;
73 Ok(Udf { name: p.name.clone(), external: p.external.clone(), params, result: formal(returning, false)?, pos: p.pos })
74}
75
76pub fn functions(program: &Program, qualify: Qualify, errors: &mut Vec<Error>) -> Vec<Udf> {
81 let own = program.function.as_ref().map(|f| f.pos);
82 let mut out = Vec::new();
83 for p in &program.prototypes {
84 match signature(p, qualify) {
85 Ok(udf) => out.push(udf),
86 Err(e) if Some(p.pos) == own => errors.push(e),
87 Err(_) => {}
88 }
89 }
90 if let Some(own) = own {
91 definition_rules(program, &out, own, errors);
92 }
93 facilities(program, errors);
94 out.retain(|u| !program.intrinsic(&u.name));
95 out
96}
97
98fn facilities(program: &Program, errors: &mut Vec<Error>) {
102 if program.function.is_none() && program.prototypes.is_empty() {
103 return;
104 }
105 let mut all = Vec::new();
106 program.paragraphs.iter().for_each(|p| crate::inner_statements(&p.statements, &mut all));
107 let statements = all.into_iter().filter_map(|s| match s {
108 Stmt::Exec(b) => Some(b.as_ref()),
109 _ => None,
110 });
111 if let Some(b) = program.exec_declarations.iter().chain(statements).find(|b| matches!(b.kind, ExecKind::Sql | ExecKind::Cics)) {
112 let kind = if b.kind == ExecKind::Sql { "SQL" } else { "CICS" };
113 errors.push(syntax::messages::IWC0018.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)")));
114 }
115}
116
117fn definition_rules(program: &Program, functions: &[Udf], own: Pos, errors: &mut Vec<Error>) {
120 let Some(this) = functions.iter().find(|u| u.pos == own) else { return };
121 for f in this.params.iter().filter(|f| f.by_value) {
122 let one_character = matches!(f.kind, Kind::Alnum { .. }) && f.size == 1 || f.kind == Kind::National && f.size == 2;
123 if !(one_character || matches!(f.kind, Kind::Binary { .. } | Kind::Float(_) | Kind::Pointer | Kind::ProgramPointer)) {
124 errors.push(syntax::messages::IWC0019.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)));
125 }
126 }
127 for other in functions.iter().filter(|u| u.name == this.name && u.pos != own) {
128 if let Some(why) = disagreement(this, other) {
129 errors.push(syntax::messages::IWC0020.at(own, format!("FUNCTION-ID {}: {why} from the prototype at line {}", program.id, other.pos.line)));
130 }
131 }
132}
133
134fn disagreement(a: &Udf, b: &Udf) -> Option<String> {
135 if a.external != b.external {
136 return Some(format!("the external name {} differs", a.external));
137 }
138 if a.params.len() != b.params.len() {
139 return Some(format!("{} parameters differ in number", a.params.len()));
140 }
141 let same = |x: &Formal, y: &Formal| (x.by_value, x.kind, x.size, x.scaling, x.alphabetic, &x.edit, x.any_length) == (y.by_value, y.kind, y.size, y.scaling, y.alphabetic, &y.edit, y.any_length);
142 if let Some(k) = (0..a.params.len()).find(|&k| !same(&a.params[k], &b.params[k])) {
143 return Some(format!("parameter {} ({}) differs", k + 1, a.params[k].name));
144 }
145 (!same(&a.result, &b.result)).then(|| format!("the RETURNING item {} differs", a.result.name))
146}
147
148pub fn conformance(layout: &Layout, item: usize, alphabetic: bool, decimal_point_comma: bool, formal: &Formal) -> Option<String> {
152 if formal.any_length {
153 return None;
154 }
155 let it = &layout.items[item];
156 if formal.by_value {
157 if it.kind == Kind::Group {
158 return Some("a group is never passed BY VALUE".into());
159 }
160 if arithmetic(formal.kind) && !arithmetic(it.kind) {
161 return Some(format!("BY VALUE {} is numeric, and takes an argument COMPUTE could send it", formal.name));
162 }
163 return None;
164 }
165 if it.kind == Kind::Group || formal.kind == Kind::Group {
166 return (it.size < formal.size).then(|| format!("{} bytes cannot be passed BY REFERENCE to the {}-byte {}", it.size, formal.size, formal.name));
167 }
168 let edit = match it.kind {
169 Kind::NumericEdited { edit, .. } | Kind::AlnumEdited { edit } => Some((layout.edits[edit as usize].clone(), layout.currencies[edit as usize].clone())),
170 _ => None,
171 };
172 let conforms = match (it.kind, formal.kind) {
173 (Kind::Alnum { justified: a }, Kind::Alnum { justified: b }) => a == b && alphabetic == formal.alphabetic && it.size >= formal.size,
174 (Kind::National, Kind::National) => it.size >= formal.size,
175 (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),
176 };
177 (!conforms).then(|| format!("{} is passed BY REFERENCE, so its PICTURE, USAGE, SIGN, JUSTIFIED and BLANK WHEN ZERO must be the argument's", formal.name))
178}
179
180pub fn check_invocation(udf: &Udf, f: &FunctionCall, layout: &Layout, alphabetic: &[Pos], decimal_point_comma: bool, errors: &mut Vec<Error>) {
183 let name = &f.name;
184 if f.args.len() != udf.params.len() {
185 errors.push(syntax::messages::IWC0021.at(f.pos, format!("FUNCTION {name} takes {} arguments, not {}", udf.params.len(), f.args.len())));
186 return;
187 }
188 if f.modifier.is_some() || !f.all_subscripts.is_empty() {
189 errors.push(syntax::messages::IWC0022.at(f.pos, format!("FUNCTION {name}: a user-defined function's argument is an identifier, a literal or an arithmetic expression")));
190 }
191 if f.refmod.is_some() && !udf.character_valued() {
192 errors.push(syntax::messages::IWC0023.at(f.pos, format!("FUNCTION {name}: only an alphanumeric or national function's value can be reference-modified")));
193 }
194 for (k, (arg, formal)) in f.args.iter().zip(&udf.params).enumerate() {
195 let r = match arg {
196 Expr::Operand(Operand::Literal(Literal::Figurative(_) | Literal::All(_))) => {
197 errors.push(syntax::messages::IWC0024.at(f.pos, format!("FUNCTION {name} argument {}: a function's argument is not a figurative constant", k + 1)));
198 continue;
199 }
200 Expr::Operand(Operand::Literal(Literal::Alnum(_) | Literal::Hex(_) | Literal::National(_))) if arithmetic(formal.kind) => {
201 errors.push(syntax::messages::IWC0025.at(f.pos, format!("FUNCTION {name} argument {}: {} is numeric, and takes an argument COMPUTE could send it (assumption C272)", k + 1, formal.name)));
202 continue;
203 }
204 Expr::Operand(Operand::Ref(r)) if r.refmod.is_none() => r,
205 Expr::Operand(Operand::Ref(_)) => continue,
206 _ if formal.any_length => {
207 errors.push(syntax::messages::IWR0076.at(f.pos, format!("ANY LENGTH on {}: ironwork reads it on an alphanumeric 01 or 77 parameter, and FUNCTION {name} argument {} is not a data item, whose length it would take", formal.name, k + 1)));
208 continue;
209 }
210 _ => continue,
211 };
212 if let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos)
213 && let Some(why) = conformance(layout, i, alphabetic.contains(&layout.items[i].pos), decimal_point_comma, formal)
214 {
215 errors.push(syntax::messages::IWC0026.at(r.pos, format!("FUNCTION {name} argument {} ({}): {why}", k + 1, r.name)));
216 }
217 }
218}
219
220fn arithmetic(kind: Kind) -> bool {
223 matches!(kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_))
224}