fixed issue #51

This commit is contained in:
CK Tan 2021-03-06 23:54:24 -08:00
parent 1cfa8e42c4
commit 715fa54d45
55 changed files with 747 additions and 366 deletions

1
stdex/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/*.out

15
stdex/RUN.sh Normal file
View File

@ -0,0 +1,15 @@
rm -f *.out
for i in *.toml; do
echo -n $i
../toml_cat $i >& $i.out
if [ -f $i.res ]; then
if $(diff $i.out $i.res >& /dev/null); then
echo " [OK]"
else
echo " [FAILED]"
fi
else
echo " [??]"
fi
done

3
stdex/comment.toml Normal file
View File

@ -0,0 +1,3 @@
# This is a full-line comment
key = "value" # This is a comment at the end of a line
another = "# This is not a comment"

4
stdex/comment.toml.res Normal file
View File

@ -0,0 +1,4 @@
{
key = "value",
another = "# This is not a comment",
}

4
stdex/keys00.toml Normal file
View File

@ -0,0 +1,4 @@
key = "value"
bare_key = "value"
bare-key = "value"
1234 = "value"

6
stdex/keys00.toml.res Normal file
View File

@ -0,0 +1,6 @@
{
key = "value",
bare_key = "value",
bare-key = "value",
1234 = "value",
}

5
stdex/keys01.toml Normal file
View File

@ -0,0 +1,5 @@
"127.0.0.1" = "value"
"character encoding" = "value"
"ʎǝʞ" = "value"
'key2' = "value"
'quoted "value"' = "value"

7
stdex/keys01.toml.res Normal file
View File

@ -0,0 +1,7 @@
{
127.0.0.1 = "value",
character encoding = "value",
ʎǝʞ = "value",
key2 = "value",
quoted "value" = "value",
}

1
stdex/keys02.toml Normal file
View File

@ -0,0 +1 @@
= "no key name" # INVALID

1
stdex/keys02.toml.res Normal file
View File

@ -0,0 +1 @@
ERROR: line 1: syntax error

1
stdex/keys03.toml Normal file
View File

@ -0,0 +1 @@
"" = "blank" # VALID but discouraged

3
stdex/keys03.toml.res Normal file
View File

@ -0,0 +1,3 @@
{
= "blank",
}

4
stdex/keys04.toml Normal file
View File

@ -0,0 +1,4 @@
name = "Orange"
physical.color = "orange"
physical.shape = "round"
site."google.com" = true

10
stdex/keys04.toml.res Normal file
View File

@ -0,0 +1,10 @@
{
name = "Orange",
physical = {
color = "orange",
shape = "round",
},
site = {
google.com = true,
},
}

3
stdex/keys05.toml Normal file
View File

@ -0,0 +1,3 @@
fruit.name = "banana" # this is best practice
fruit. color = "yellow" # same as fruit.color
fruit . flavor = "banana" # same as fruit.flavor

7
stdex/keys05.toml.res Normal file
View File

@ -0,0 +1,7 @@
{
fruit = {
name = "banana",
color = "yellow",
flavor = "banana",
},
}

3
stdex/keys06.toml Normal file
View File

@ -0,0 +1,3 @@
# DO NOT DO THIS
name = "Tom"
name = "Pradyun"

1
stdex/keys06.toml.res Normal file
View File

@ -0,0 +1 @@
ERROR: line 3: key exists

3
stdex/keys07.toml Normal file
View File

@ -0,0 +1,3 @@
# THIS WILL NOT WORK
spelling = "favorite"
"spelling" = "favourite"

1
stdex/keys07.toml.res Normal file
View File

@ -0,0 +1 @@
ERROR: line 3: key exists

5
stdex/keys08.toml Normal file
View File

@ -0,0 +1,5 @@
# This makes the key "fruit" into a table.
fruit.apple.smooth = true
# So then you can add to the table "fruit" like so:
fruit.orange = 2

8
stdex/keys08.toml.res Normal file
View File

@ -0,0 +1,8 @@
{
fruit = {
orange = 2,
apple = {
smooth = true,
},
},
}

8
stdex/keys09.toml Normal file
View File

@ -0,0 +1,8 @@
# THE FOLLOWING IS INVALID
# This defines the value of fruit.apple to be an integer.
fruit.apple = 1
# But then this treats fruit.apple like it's a table.
# You can't turn an integer into a table.
fruit.apple.smooth = true

1
stdex/keys09.toml.res Normal file
View File

@ -0,0 +1 @@
ERROR: line 8: key exists

10
stdex/keys10.toml Normal file
View File

@ -0,0 +1,10 @@
# VALID BUT DISCOURAGED
apple.type = "fruit"
orange.type = "fruit"
apple.skin = "thin"
orange.skin = "thick"
apple.color = "red"
orange.color = "orange"

12
stdex/keys10.toml.res Normal file
View File

@ -0,0 +1,12 @@
{
apple = {
type = "fruit",
skin = "thin",
color = "red",
},
orange = {
type = "fruit",
skin = "thick",
color = "orange",
},
}

9
stdex/keys11.toml Normal file
View File

@ -0,0 +1,9 @@
# RECOMMENDED
apple.type = "fruit"
apple.skin = "thin"
apple.color = "red"
orange.type = "fruit"
orange.skin = "thick"
orange.color = "orange"

12
stdex/keys11.toml.res Normal file
View File

@ -0,0 +1,12 @@
{
apple = {
type = "fruit",
skin = "thin",
color = "red",
},
orange = {
type = "fruit",
skin = "thick",
color = "orange",
},
}

1
stdex/keys12.toml Normal file
View File

@ -0,0 +1 @@
3.14159 = "pi"

5
stdex/keys12.toml.res Normal file
View File

@ -0,0 +1,5 @@
{
3 = {
14159 = "pi",
},
}

1
stdex/kvpair0.toml Normal file
View File

@ -0,0 +1 @@
key = "value"

3
stdex/kvpair0.toml.res Normal file
View File

@ -0,0 +1,3 @@
{
key = "value",
}

1
stdex/kvpair1.toml Normal file
View File

@ -0,0 +1 @@
key = # INVALID

1
stdex/kvpair1.toml.res Normal file
View File

@ -0,0 +1 @@
ERROR: line 1: syntax error

1
stdex/kvpair2.toml Normal file
View File

@ -0,0 +1 @@
first = "Tom" last = "Preston-Werner" # INVALID

1
stdex/kvpair2.toml.res Normal file
View File

@ -0,0 +1 @@
ERROR: line 1: extra chars after value

1
stdex/string0.toml Normal file
View File

@ -0,0 +1 @@
str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."

3
stdex/string0.toml.res Normal file
View File

@ -0,0 +1,3 @@
{
str = "I'm a string. \"You can quote me\". Name\tJos\0xc3\0xa9\nLocation\tSF.",
}

9
stdex/string1.toml Normal file
View File

@ -0,0 +1,9 @@
str1 = """
Roses are red
Violets are blue"""
# On a Unix system, the above multi-line string will most likely be the same as:
str2 = "Roses are red\nViolets are blue"
# On a Windows system, it will most likely be equivalent to:
str3 = "Roses are red\r\nViolets are blue"

5
stdex/string1.toml.res Normal file
View File

@ -0,0 +1,5 @@
{
str1 = "Roses are red\nViolets are blue",
str2 = "Roses are red\nViolets are blue",
str3 = "Roses are red\r\nViolets are blue",
}

15
stdex/string3.toml Normal file
View File

@ -0,0 +1,15 @@
# The following strings are byte-for-byte equivalent:
str1 = "The quick brown fox jumps over the lazy dog."
str2 = """
The quick brown \
fox jumps over \
the lazy dog."""
str3 = """\
The quick brown \
fox jumps over \
the lazy dog.\
"""

5
stdex/string3.toml.res Normal file
View File

@ -0,0 +1,5 @@
{
str1 = "The quick brown fox jumps over the lazy dog.",
str2 = "The quick brown fox jumps over the lazy dog.",
str3 = "The quick brown fox jumps over the lazy dog.",
}

7
stdex/string4.toml Normal file
View File

@ -0,0 +1,7 @@
str4 = """Here are two quotation marks: "". Simple enough."""
# str5 = """Here are three quotation marks: """.""" # INVALID
str5 = """Here are three quotation marks: ""\"."""
str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\"."""
# "This," she said, "is just a pointless statement."
str7 = """"This," she said, "is just a pointless statement.""""

6
stdex/string4.toml.res Normal file
View File

@ -0,0 +1,6 @@
{
str4 = "Here are two quotation marks: \"\". Simple enough.",
str5 = "Here are three quotation marks: \"\"\".",
str6 = "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\".",
str7 = "\"This,\" she said, \"is just a pointless statement.\"",
}

5
stdex/string5.toml Normal file
View File

@ -0,0 +1,5 @@
# What you see is what you get.
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Tom "Dubs" Preston-Werner'
regex = '<\i\c*\s*>'

6
stdex/string5.toml.res Normal file
View File

@ -0,0 +1,6 @@
{
winpath = "C:\\Users\\nodejs\\templates",
winpath2 = "\\\\ServerX\\admin$\\system32\\",
quoted = "Tom \"Dubs\" Preston-Werner",
regex = "<\\i\\c*\\s*>",
}

7
stdex/string6.toml Normal file
View File

@ -0,0 +1,7 @@
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''

4
stdex/string6.toml.res Normal file
View File

@ -0,0 +1,4 @@
{
regex2 = "I [dw]on't need \\d{2} apples",
lines = "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n",
}

4
stdex/string7.toml Normal file
View File

@ -0,0 +1,4 @@
quot15 = '''Here are fifteen quotation marks: """""""""""""""'''
# 'That,' she said, 'is still pointless.'
str = ''''That,' she said, 'is still pointless.''''

4
stdex/string7.toml.res Normal file
View File

@ -0,0 +1,4 @@
{
quot15 = "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"",
str = "'That,' she said, 'is still pointless.'",
}

3
stdex/string8.toml Normal file
View File

@ -0,0 +1,3 @@
# apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID
apos15 = "Here are fifteen apostrophes: '''''''''''''''"

1
stdex/string8.toml.res Normal file
View File

@ -0,0 +1 @@
ERROR: line 2: triple-s-quote inside string lit

511
toml.c

File diff suppressed because it is too large Load Diff

34
toml.h
View File

@ -1,19 +1,19 @@
/*
MIT License
Copyright (c) 2017 - 2019 CK Tan
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -41,14 +41,14 @@ typedef struct toml_table_t toml_table_t;
typedef struct toml_array_t toml_array_t;
typedef struct toml_datum_t toml_datum_t;
/* Parse a file. Return a table on success, or 0 otherwise.
/* Parse a file. Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
char* errbuf,
int errbufsz);
/* Parse a string containing the full config.
/* Parse a string containing the full config.
* Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
@ -56,14 +56,14 @@ TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
char* errbuf,
int errbufsz);
/* Free the table returned by toml_parse() or toml_parse_file(). Once
* this function is called, any handles accessed through this tab
/* Free the table returned by toml_parse() or toml_parse_file(). Once
* this function is called, any handles accessed through this tab
* directly or indirectly are no longer valid.
*/
TOML_EXTERN void toml_free(toml_table_t* tab);
/* Timestamp types. The year, month, day, hour, minute, second, z
/* Timestamp types. The year, month, day, hour, minute, second, z
* fields may be NULL if they are not relevant. e.g. In a DATE
* type, the hour, minute, second and z fields will be NULLs.
*/
@ -80,7 +80,7 @@ struct toml_timestamp_t {
/*-----------------------------------------------------------------
* Enhanced access methods
* Enhanced access methods
*/
struct toml_datum_t {
int ok;
@ -122,13 +122,13 @@ TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
const char* key);
/*-----------------------------------------------------------------
* lesser used
* lesser used
*/
/* Return the array kind: 't'able, 'a'rray, 'v'alue */
/* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */
TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
/* For array kind 'v'alue, return the type of values
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
/* For array kind 'v'alue, return the type of values
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed
0 if unknown
*/
TOML_EXTERN char toml_array_type(const toml_array_t* arr);
@ -149,7 +149,7 @@ TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
/*--------------------------------------------------------------
* misc
* misc
*/
TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
@ -158,7 +158,7 @@ TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
/*--------------------------------------------------------------
* deprecated
* deprecated
*/
/* A raw value, must be processed by toml_rto* before using. */
typedef const char* toml_raw_t;

View File

@ -1,26 +1,26 @@
/*
MIT License
MIT License
Copyright (c) 2017 CK Tan
https://github.com/cktan/tomlc99
Copyright (c) 2017 CK Tan
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifdef NDEBUG
@ -33,6 +33,8 @@ SOFTWARE.
#include <errno.h>
#include <stdint.h>
#include <assert.h>
#include <inttypes.h>
#include <ctype.h>
#include "toml.h"
typedef struct node_t node_t;
@ -43,128 +45,229 @@ struct node_t {
node_t stack[20];
int stacktop = 0;
int indent = 0;
static void print_table_title(const char* arrname)
static void prindent()
{
int i;
printf("%s", arrname ? "[[" : "[");
for (i = 1; i < stacktop; i++) {
printf("%s", stack[i].key);
if (i + 1 < stacktop)
printf(".");
}
if (arrname)
printf(".%s]]\n", arrname);
else
printf("]\n");
for (int i = 0; i < indent; i++) printf(" ");
}
static void print_string(const char* s)
{
int ok = 1;
for (const char* p = s; *p && ok; p++) {
int ch = *p;
ok = isprint(ch) && ch != '"' && ch != '\\';
}
if (ok) {
printf("\"%s\"", s);
return;
}
int len = strlen(s);
printf("\"");
for ( ; len; len--, s++) {
int ch = *s;
if (isprint(ch) && ch != '"' && ch != '\\') {
putchar(ch);
continue;
}
switch (ch) {
case 0x8: printf("\\b"); continue;
case 0x9: printf("\\t"); continue;
case 0xa: printf("\\n"); continue;
case 0xc: printf("\\f"); continue;
case 0xd: printf("\\r"); continue;
case '"': printf("\\\""); continue;
case '\\': printf("\\\\"); continue;
default: printf("\\0x%02x", ch & 0xff); continue;
}
}
printf("\"");
}
static void print_array_of_tables(toml_array_t* arr, const char* key);
static void print_array(toml_array_t* arr);
static void print_table(toml_table_t* curtab)
{
toml_datum_t d;
int i;
const char* key;
const char* raw;
toml_array_t* arr;
toml_table_t* tab;
for (i = 0; 0 != (key = toml_key_in(curtab, i)); i++) {
if (0 != (raw = toml_raw_in(curtab, key))) {
printf("%s = %s\n", key, raw);
} else if (0 != (arr = toml_array_in(curtab, key))) {
if (toml_array_kind(arr) == 't') {
print_array_of_tables(arr, key);
}
else {
printf("%s = [\n", key);
print_array(arr);
printf(" ]\n");
}
} else if (0 != (tab = toml_table_in(curtab, key))) {
stack[stacktop].key = key;
stack[stacktop].tab = tab;
stacktop++;
print_table_title(0);
print_table(tab);
stacktop--;
} else {
abort();
}
}
}
static void print_array_of_tables(toml_array_t* arr, const char* key)
{
int i;
toml_table_t* tab;
printf("\n");
for (i = 0; 0 != (tab = toml_table_at(arr, i)); i++) {
print_table_title(key);
print_table(tab);
printf("\n");
if (0 != (arr = toml_array_in(curtab, key))) {
prindent();
printf("%s = [\n", key);
indent++;
print_array(arr);
indent--;
prindent();
printf("],\n");
continue;
}
if (0 != (tab = toml_table_in(curtab, key))) {
stack[stacktop].key = key;
stack[stacktop].tab = tab;
stacktop++;
prindent();
printf("%s = {\n", key);
indent++;
print_table(tab);
indent--;
prindent();
printf("},\n");
stacktop--;
continue;
}
d = toml_string_in(curtab, key);
if (d.ok) {
prindent();
printf("%s = ", key);
print_string(d.u.s);
printf(",\n");
free(d.u.s);
continue;
}
d = toml_bool_in(curtab, key);
if (d.ok) {
prindent();
printf("%s = %s,\n", key, d.u.b ? "true" : "false");
continue;
}
d = toml_int_in(curtab, key);
if (d.ok) {
prindent();
printf("%s = %" PRId64 ",\n", key, d.u.i);
continue;
}
d = toml_double_in(curtab, key);
if (d.ok) {
prindent();
printf("%s = %g,\n", key, d.u.d);
continue;
}
d = toml_timestamp_in(curtab, key);
if (d.ok) {
prindent();
printf(" %s = %s,\n", key, toml_raw_in(curtab, key));
free(d.u.ts);
continue;
}
abort();
}
}
static void print_array(toml_array_t* curarr)
{
toml_datum_t d;
toml_array_t* arr;
const char* raw;
toml_table_t* tab;
int i;
const int n = toml_array_nelem(curarr);
switch (toml_array_kind(curarr)) {
for (int i = 0; i < n; i++) {
case 'v':
for (i = 0; 0 != (raw = toml_raw_at(curarr, i)); i++) {
printf(" %d: %s,\n", i, raw);
if (0 != (arr = toml_array_at(curarr, i))) {
prindent();
printf("[\n");
indent++;
print_array(arr);
indent--;
prindent();
printf("],\n");
continue;
}
if (0 != (tab = toml_table_at(curarr, i))) {
prindent();
printf("{\n");
indent++;
print_table(tab);
indent--;
prindent();
printf("},\n");
continue;
}
d = toml_string_at(curarr, i);
if (d.ok) {
prindent();
print_string(d.u.s);
printf(",\n");
free(d.u.s);
continue;
}
d = toml_bool_at(curarr, i);
if (d.ok) {
prindent();
printf("%s,\n", d.u.b ? "true" : "false");
continue;
}
d = toml_int_at(curarr, i);
if (d.ok) {
prindent();
printf("%" PRId64 ",\n", d.u.i);
continue;
}
d = toml_double_at(curarr, i);
if (d.ok) {
prindent();
printf("%g,\n", d.u.d);
continue;
}
d = toml_timestamp_at(curarr, i);
if (d.ok) {
prindent();
printf("%s,\n", toml_raw_at(curarr, i));
free(d.u.ts);
continue;
}
abort();
}
break;
case 'a':
for (i = 0; 0 != (arr = toml_array_at(curarr, i)); i++) {
printf(" %d: \n", i);
print_array(arr);
}
break;
case 't':
for (i = 0; 0 != (tab = toml_table_at(curarr, i)); i++) {
print_table(tab);
}
printf("\n");
break;
case '\0':
break;
default:
abort();
}
}
static void cat(FILE* fp)
{
char errbuf[200];
toml_table_t* tab = toml_parse_file(fp, errbuf, sizeof(errbuf));
if (!tab) {
fprintf(stderr, "ERROR: %s\n", errbuf);
return;
fprintf(stderr, "ERROR: %s\n", errbuf);
return;
}
stack[stacktop].tab = tab;
stack[stacktop].key = "";
stacktop++;
printf("{\n");
indent++;
print_table(tab);
indent--;
printf("}\n");
stacktop--;
toml_free(tab);
@ -175,19 +278,19 @@ int main(int argc, const char* argv[])
{
int i;
if (argc == 1) {
cat(stdin);
cat(stdin);
} else {
for (i = 1; i < argc; i++) {
FILE* fp = fopen(argv[i], "r");
if (!fp) {
fprintf(stderr, "ERROR: cannot open %s: %s\n",
argv[i], strerror(errno));
exit(1);
}
cat(fp);
fclose(fp);
}
for (i = 1; i < argc; i++) {
FILE* fp = fopen(argv[i], "r");
if (!fp) {
fprintf(stderr, "ERROR: cannot open %s: %s\n",
argv[i], strerror(errno));
exit(1);
}
cat(fp);
fclose(fp);
}
}
return 0;
}