refactoring
[pspp] / src / language / data-io / file-handle.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-2000, 2006, 2010-2013, 2016 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "data/file-handle-def.h"
20
21 #include <limits.h>
22 #include <errno.h>
23 #include <stdlib.h>
24
25 #include "data/file-name.h"
26 #include "data/session.h"
27 #include "data/variable.h"
28 #include "language/command.h"
29 #include "language/data-io/file-handle.h"
30 #include "language/lexer/lexer.h"
31 #include "libpspp/assertion.h"
32 #include "libpspp/cast.h"
33 #include "libpspp/message.h"
34 #include "libpspp/str.h"
35
36 #include "gl/xalloc.h"
37
38 #include "gettext.h"
39 #define _(msgid) gettext (msgid)
40
41 int
42 cmd_file_handle (struct lexer *lexer, struct dataset *ds UNUSED)
43 {
44   enum cmd_result result = CMD_CASCADING_FAILURE;
45   char *handle_name = NULL;
46   char *file_name = NULL;
47   int lrecl = 0;
48   int tabwidth = -1;
49   enum { MODE_DEFAULT, MODE_CHARACTER, MODE_BINARY, MODE_IMAGE, MODE_360 }
50       mode = MODE_DEFAULT;
51   int ends = -1;
52   enum { RECFORM_FIXED = 1, RECFORM_VARIABLE, RECFORM_SPANNED } recform = 0;
53   char *encoding = NULL;
54
55   if (!lex_force_id (lexer))
56     goto exit;
57
58   handle_name = xstrdup (lex_tokcstr (lexer));
59   if (fh_from_id (handle_name))
60     {
61       msg (SE, _("File handle %s is already defined.  "
62                  "Use %s before redefining a file handle."),
63            handle_name, "CLOSE FILE HANDLE");
64       goto exit;
65     }
66
67   lex_get (lexer);
68   if (!lex_force_match (lexer, T_SLASH))
69     goto exit;
70
71   while (lex_token (lexer) != T_ENDCMD)
72     {
73       if (lex_match_id (lexer, "NAME"))
74         {
75           if (file_name)
76             {
77               lex_sbc_only_once ("NAME");
78               goto exit;
79             }
80
81           lex_match (lexer, T_EQUALS);
82           if (!lex_force_string (lexer))
83             goto exit;
84           free (file_name);
85           file_name = ss_xstrdup (lex_tokss (lexer));
86           lex_get (lexer);
87         }
88       else if (lex_match_id (lexer, "LRECL"))
89         {
90           if (lrecl)
91             {
92               lex_sbc_only_once ("LRECL");
93               goto exit;
94             }
95
96           lex_match (lexer, T_EQUALS);
97           if (!lex_force_int_range (lexer, "LRECL", 1, (1UL << 31) - 1))
98             goto exit;
99           lrecl = lex_integer (lexer);
100           lex_get (lexer);
101         }
102       else if (lex_match_id (lexer, "TABWIDTH"))
103         {
104           if (tabwidth >= 0)
105             {
106               lex_sbc_only_once ("TABWIDTH");
107               goto exit;
108             }
109           lex_match (lexer, T_EQUALS);
110
111           if (!lex_force_int_range (lexer, "TABWIDTH", 1, INT_MAX))
112             goto exit;
113           tabwidth = lex_integer (lexer);
114           lex_get (lexer);
115         }
116       else if (lex_match_id (lexer, "MODE"))
117         {
118           if (mode != MODE_DEFAULT)
119             {
120               lex_sbc_only_once ("MODE");
121               goto exit;
122             }
123           lex_match (lexer, T_EQUALS);
124
125           if (lex_match_id (lexer, "CHARACTER"))
126             mode = MODE_CHARACTER;
127           else if (lex_match_id (lexer, "BINARY"))
128             mode = MODE_BINARY;
129           else if (lex_match_id (lexer, "IMAGE"))
130             mode = MODE_IMAGE;
131           else if (lex_match_int (lexer, 360))
132             mode = MODE_360;
133           else
134             {
135               lex_error (lexer, NULL);
136               goto exit;
137             }
138         }
139       else if (lex_match_id (lexer, "ENDS"))
140         {
141           if (ends >= 0)
142             {
143               lex_sbc_only_once ("ENDS");
144               goto exit;
145             }
146           lex_match (lexer, T_EQUALS);
147
148           if (lex_match_id (lexer, "LF"))
149             ends = FH_END_LF;
150           else if (lex_match_id (lexer, "CRLF"))
151             ends = FH_END_CRLF;
152           else
153             {
154               lex_error (lexer, NULL);
155               goto exit;
156             }
157         }
158       else if (lex_match_id (lexer, "RECFORM"))
159         {
160           if (recform)
161             {
162               lex_sbc_only_once ("RECFORM");
163               goto exit;
164             }
165           lex_match (lexer, T_EQUALS);
166           if (lex_match_id (lexer, "FIXED") || lex_match_id (lexer, "F"))
167             recform = RECFORM_FIXED;
168           else if (lex_match_id (lexer, "VARIABLE")
169                    || lex_match_id (lexer, "V"))
170             recform = RECFORM_VARIABLE;
171           else if (lex_match_id (lexer, "SPANNED")
172                    || lex_match_id (lexer, "VS"))
173             recform = RECFORM_SPANNED;
174           else
175             {
176               lex_error (lexer, NULL);
177               goto exit;
178             }
179         }
180       else if (lex_match_id (lexer, "ENCODING"))
181         {
182           if (encoding)
183             {
184               lex_sbc_only_once ("ENCODING");
185               goto exit;
186             }
187
188           lex_match (lexer, T_EQUALS);
189           if (!lex_force_string (lexer))
190             goto exit;
191           free (encoding);
192           encoding = ss_xstrdup (lex_tokss (lexer));
193           lex_get (lexer);
194         }
195       if (!lex_match (lexer, T_SLASH))
196         break;
197     }
198
199   if (lex_end_of_command (lexer) != CMD_SUCCESS)
200     goto exit;
201
202   struct fh_properties properties = *fh_default_properties ();
203   if (file_name == NULL)
204     {
205       lex_sbc_missing ("NAME");
206       goto exit;
207     }
208
209   switch (mode)
210     {
211     case MODE_DEFAULT:
212     case MODE_CHARACTER:
213       properties.mode = FH_MODE_TEXT;
214       if (tabwidth >= 0)
215         properties.tab_width = tabwidth;
216       if (ends)
217         properties.line_ends = ends;
218       break;
219     case MODE_IMAGE:
220       properties.mode = FH_MODE_FIXED;
221       break;
222     case MODE_BINARY:
223       properties.mode = FH_MODE_VARIABLE;
224       break;
225     case MODE_360:
226       properties.encoding = CONST_CAST (char *, "EBCDIC-US");
227       if (recform == RECFORM_FIXED)
228         properties.mode = FH_MODE_FIXED;
229       else if (recform == RECFORM_VARIABLE)
230         {
231           properties.mode = FH_MODE_360_VARIABLE;
232           properties.record_width = 8192;
233         }
234       else if (recform == RECFORM_SPANNED)
235         {
236           properties.mode = FH_MODE_360_SPANNED;
237           properties.record_width = 8192;
238         }
239       else
240         {
241           msg (SE, _("%s must be specified with %s."), "RECFORM", "MODE=360");
242           goto exit;
243         }
244       break;
245     default:
246       NOT_REACHED ();
247     }
248
249   if (properties.mode == FH_MODE_FIXED || lrecl)
250     {
251       if (!lrecl)
252         msg (SE, _("The specified file mode requires LRECL.  "
253                    "Assuming %zu-character records."),
254              properties.record_width);
255       else
256         properties.record_width = lrecl;
257     }
258
259   if (encoding)
260     properties.encoding = encoding;
261
262   fh_create_file (handle_name, file_name, lex_get_encoding (lexer),
263                   &properties);
264
265   result = CMD_SUCCESS;
266
267 exit:
268   free (handle_name);
269   free (file_name);
270   free (encoding);
271   return result;
272 }
273
274 int
275 cmd_close_file_handle (struct lexer *lexer, struct dataset *ds UNUSED)
276 {
277   struct file_handle *handle;
278
279   if (!lex_force_id (lexer))
280     return CMD_CASCADING_FAILURE;
281   handle = fh_from_id (lex_tokcstr (lexer));
282   if (handle == NULL)
283     return CMD_CASCADING_FAILURE;
284
285   fh_unname (handle);
286   return CMD_SUCCESS;
287 }
288
289 /* Returns the name for REFERENT. */
290 static const char *
291 referent_name (enum fh_referent referent)
292 {
293   switch (referent)
294     {
295     case FH_REF_FILE:
296       return _("file");
297     case FH_REF_INLINE:
298       return _("inline file");
299     case FH_REF_DATASET:
300       return _("dataset");
301     default:
302       NOT_REACHED ();
303     }
304 }
305
306 /* Parses a file handle name:
307
308       - If SESSION is nonnull, then the parsed syntax may be the name of a
309         dataset within SESSION.  Dataset names take precedence over file handle
310         names.
311
312       - If REFERENT_MASK includes FH_REF_FILE, the parsed syntax may be a file
313         name as a string or a file handle name as an identifier.
314
315       - If REFERENT_MASK includes FH_REF_INLINE, the parsed syntax may be the
316         identifier INLINE to represent inline data.
317
318    Returns the file handle when successful, a null pointer on failure.
319
320    The caller is responsible for fh_unref()'ing the returned file handle when
321    it is no longer needed. */
322 struct file_handle *
323 fh_parse (struct lexer *lexer, enum fh_referent referent_mask,
324           struct session *session)
325 {
326   struct file_handle *handle;
327
328   if (session != NULL && lex_token (lexer) == T_ID)
329     {
330       struct dataset *ds;
331
332       ds = session_lookup_dataset (session, lex_tokcstr (lexer));
333       if (ds != NULL)
334         {
335           lex_get (lexer);
336           return fh_create_dataset (ds);
337         }
338     }
339
340   if (lex_match_id (lexer, "INLINE"))
341     handle = fh_inline_file ();
342   else
343     {
344       if (lex_token (lexer) != T_ID && !lex_is_string (lexer))
345         {
346           lex_error (lexer, _("expecting a file name or handle name"));
347           return NULL;
348         }
349
350       handle = NULL;
351       if (lex_token (lexer) == T_ID)
352         handle = fh_from_id (lex_tokcstr (lexer));
353       if (handle == NULL)
354         handle = fh_create_file (NULL, lex_tokcstr (lexer), lex_get_encoding (lexer),
355                                      fh_default_properties ());
356       lex_get (lexer);
357     }
358
359   if (!(fh_get_referent (handle) & referent_mask))
360     {
361       msg (SE, _("Handle for %s not allowed here."),
362            referent_name (fh_get_referent (handle)));
363       fh_unref (handle);
364       return NULL;
365     }
366
367   return handle;
368 }