command line printf

git-svn-id: file:///srv/svn/repos/haiku/trunk/current@1456 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Daniel Reinhold 2002-10-08 15:03:23 +00:00
parent ef77a305f8
commit 51dc1c7a64

552
src/apps/bin/printf.c Normal file
View File

@ -0,0 +1,552 @@
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
//
// Copyright (c) 2001-2002, OpenBeOS
//
// This software is part of the OpenBeOS distribution and is covered
// by the OpenBeOS license.
//
//
// File: printf.c
// Author: Daniel Reinhold (danielre@users.sf.net)
// Description: command line version of printf
//
// Comments:
// [6 Oct 2002]
// This utility was written using the following document for reference:
//
// http://www.opengroup.org/onlinepubs/007908799/xcu/printf.html
//
// (which is an online Unix man page) and by testing the output against
// the BeOS R5 program '/bin/printf'. There are a few deviations in the
// output between the two, but they are so minor and technical that I
// don't think any normal human being would care ;-).
//
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
#include <OS.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
void do_printf (int, char **);
int esc_string_len(char *, bool *);
char escaped (char, char **);
int get_decimal (char **);
char get_next_specifier(char *, int);
void get_spec_fields(char *, bool *, int *, int *);
bool parse_hex (char **, int, int, char *);
bool parse_octal (char **, int, int, char *);
void print_escaped_string (char *, char *);
void print_next_arg (void);
typedef union
{
int n;
double d;
} number;
number string_to_number(char *, bool);
inline int string_to_decimal(char *);
inline double string_to_real(char *);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// globals
// the variables below don't really have to be global,
// but it sure simplifies the code this way (trust me!)
static char *Format; // format string
static char **Argv; // the list of args to be printed
static int Argc; // arg count
// a kludgy (but useful) value to encode a "\c" escape
static const char SLASHC = 1;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// top-level
int
main(int argc, char *argv[])
{
if (argc < 2)
printf("usage: printf format [arguments]\n");
else
do_printf(argc, argv);
return 0;
}
void
do_printf(int argc, char *argv[])
{
char c;
char e;
// the actual print arguments start at argv[2]
Argv = argv + 2;
Argc = argc - 2;
for (;;) {
Format = argv[1];
// inner loop: interpret the format string
while ((c = *Format++) != 0) {
if (c == '%') {
if (*Format == '%')
++Format, putchar('%');
else
print_next_arg();
}
else {
e = escaped(c, &Format);
putchar(e);
}
}
if (Argc == 0)
return;
// for any args remaining, just cycle thru again
// re-using the previous format string...
}
}
void
print_next_arg()
{
// prints the next argument using the current specifier.
// an empty string ("") is used for the output arg
// when there are no more arguments remaining
char fmt[64]; // format specifier for a single arg
char fc; // format character
char *arg = ""; // next arg to be printed
if (Argc > 0)
arg = *Argv++, Argc--;
fc = get_next_specifier(fmt, sizeof fmt);
switch (fc) {
case 'c':
printf(fmt, arg[0]);
break;
case 's':
printf(fmt, arg);
break;
case 'b':
// fmt+1 ==> skips over the unneeded '%' char
print_escaped_string(fmt+1, arg);
break;
case 'd': case 'i': case 'o': case 'u':
case 'x': case 'X': case 'p':
printf(fmt, string_to_decimal(arg));
break;
case 'f': case 'g': case 'G': case 'e': case 'E':
printf(fmt, string_to_real(arg));
break;
default:
fprintf(stderr, "printf: `%c': illegal format character\n", escaped(fc, &Format));
exit(1);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// parsing, character processing...
char
get_next_specifier(char *spec_buffer, int buflen)
{
// called whenever a '%' is found in the format string.
// extracts the next format specifier from the global format string
// (which is advanced) and copies it into the given buffer.
// the final format character is returned
char *p = spec_buffer;
char c;
char fc = 0;
int len = 1; // will copy in the '%' regardless...
*p++ = '%';
while ((c = *Format++) != 0) {
*p++ = c;
if (++len > buflen)
break;
if (isalpha (c) || c == '%' || c == '\\') {
fc = c;
break;
}
}
*p = 0;
if (fc == 0) {
fprintf(stderr, "printf: `%%': missing format character\n");
exit(1);
}
return fc;
}
int
esc_string_len(char *s, bool *halt)
{
// returns the string length that the string would have
// if all (of any) embedded escape sequences were converted
// e.g. "x\ty\tz\n" ==> 6
//
// note: the special '\c' escape char is not counted,
// but instead terminates the string and sets the halt flag
char c;
char e;
int len = 0;
while ((c = *s++) != 0) {
e = escaped(c, &s);
if (e == SLASHC)
break;
++len;
}
*halt = (e == SLASHC);
return len;
}
void
print_escaped_string(char *fmt, char *s)
{
// called whenever the "%b" format specifier is found.
// prints a string possibly containing escaped chars
// (precision field holds the number of chars requested)
//
// this is the only place where the "\c" escape sequence
// is to be interpreted (i.e. the program halted)
int pad, lpad, rpad; // count of padding chars
bool leftAdjust; // left/right adjust?
int fwidth; // specified field width
int wanted; // requested number of chars
int len; // number of chars to actually print
bool halt = false;
get_spec_fields(fmt, &leftAdjust, &fwidth, &wanted);
// compute the string length and padding
len = esc_string_len(s, &halt);
if (wanted > 0 && wanted < len)
len = wanted;
pad = fwidth - len;
if (leftAdjust)
lpad = 0, rpad = pad;
else
lpad = pad, rpad = 0;
// print the translated chars with padding
{
char c;
char e;
while (lpad-- > 0) putchar (' '); // pad on the left
while (len-- > 0) {
c = *s++;
e = escaped(c, &s);
putchar(e);
}
while (rpad-- > 0) putchar (' '); // pad on the right
}
if (halt)
exit(0);
}
void
get_spec_fields(char *specifier, bool *leftAdjust, int *fieldwidth, int *precision)
{
// parses the given specifier to determine:
// * left/right adjustment
// * field width
// * precision
// these values are then returned thru the parameters
// note: this routine is only called for the "%b" conversion
const char *flags = " 0-+#";
char *s = specifier;
*leftAdjust = false;
*fieldwidth = 0;
*precision = 0;
while (strchr(flags, *s)) {
if (*s == '-')
// only flag we care about here
*leftAdjust = true;
++s;
}
if (isdigit (*s)) {
*fieldwidth = get_decimal(&s);
if (*s == '.')
++s, *precision = get_decimal(&s);
}
if (!(isalpha(*s))) {
fprintf(stderr, "printf: `%c': illegal format character\n", escaped(*s, &s));
exit(1);
}
}
char
escaped(char c, char **src)
{
// returns the character value for an escape sequence
// if no escape is found, the original char is returned
// the source pointer is advanced for any extra chars read
char *p = *src; // local pointer to scan thru the string
char esc; // the converted character
bool found = true; // assume that an escape will be found
if (c != '\\')
return c;
switch (*p++) {
case 'a': esc = '\a'; break;
case 'b': esc = '\b'; break;
case 'f': esc = '\f'; break;
case 'n': esc = '\n'; break;
case 'r': esc = '\r'; break;
case 't': esc = '\t'; break;
case 'v': esc = '\v'; break;
case '\\': esc = '\\'; break;
case '"': esc = '"'; break;
case '\'': esc = '\''; break;
case 'c': esc = SLASHC; break;
case '0':
// format: "\0ooo" (0 to 3 octal digits)
found = parse_octal(&p, 0, 3, &esc);
break;
case 'x':
// format: "\xhhh" (1 to 3 hex digits)
found = parse_hex(&p, 1, 3, &esc);
break;
default:
// nope, not an escape sequence
found = false;
}
if (found) {
*src = p; // update the source pointer
return esc;
}
return c;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// numeric routines
int
get_decimal (char **src)
{
// grabs a decimal integer value from the source string
// and returns the full computed value.
// if any valid digits were found, the source pointer is advanced.
// otherwise, a default value of 0 is returned
char *s = *src;
char c;
int n = 0;
while (isdigit (c = *s)) {
++s;
n = n * 10 + (c - '0');
}
*src = s;
return n;
}
#define isodigit(c) (isdigit(c) && !(c == '8' || c == '9'))
bool
parse_octal (char **src, int min_digits, int max_digits, char *octval)
{
// searches for an octal value in the source string.
// returns true if valid octal digits were found
//
// at least 'min_digits' and no more than 'max_digits'
// of valid octal characters must be present
//
// on success, the source pointer is advanced and the
// full octal value is returned in the last parameter.
// otherwise, the pointer is left unchanged and the
// value of the last parameter is undefined.
char *s = *src;
char c;
int n = 0;
int i;
for (i = 0; i < max_digits; ++i) {
c = *s;
if (isodigit (c)) {
++s;
n = n * 8 + (c - '0');
}
else
break;
}
if (i >= min_digits) {
*src = s;
*octval = (char) n;
return true;
}
return false;
}
bool
parse_hex (char **src, int min_digits, int max_digits, char *hexval)
{
// searches for a hex value in the source string.
// returns true if valid hex digits were found
//
// at least 'min_digits' and no more than 'max_digits'
// of valid hex characters must be present
//
// on success, the source pointer is advanced and the
// full hex value is returned in the last parameter.
// otherwise, the pointer is left unchanged and the
// value of the last parameter is undefined.
char *s = *src;
char c;
int n = 0;
int i;
for (i = 0; i < max_digits; ++i) {
c = tolower(*s);
if (isxdigit (c)) {
++s;
n *= 16;
if (c > '9')
n += (c - 'a' + 10);
else
n += (c - '0');
}
else
break;
}
if (i >= min_digits) {
*src = s;
*hexval = (char) n;
return true;
}
return false;
}
inline
int
string_to_decimal(char *numstr)
{
return string_to_number(numstr, false).n;
}
inline
double
string_to_real(char *numstr)
{
return string_to_number(numstr, true).d;
}
number
string_to_number(char *numstr, bool isreal)
{
// converts a numeric string to a number value
// (either a decimal integer or a floating point)
//
// since the code for the two types of conversion is very
// nearly the same, this master function takes care of both,
// albeit at the price of having to create a union type
// to hold the return values
//
// invalid numeric formats should be caught here and cause
// the program to abort with an error message
char *end;
number num;
// check for a character integer format (e.g. 'A ==> 65)
if (numstr[0] == '\"' || numstr[0] == '\'') {
if (isreal)
num.d = (double) numstr[1];
else
num.n = (int) numstr[1];
return num;
}
// attempt to convert with a library function
errno = 0;
if (isreal)
num.d = strtod(numstr, &end);
else
num.n = (int) strtol(numstr, &end, 10);
if (errno) {
fprintf(stderr, "printf: %s: %s\n", numstr, strerror(errno));
exit(1);
}
if (*end) {
fprintf(stderr, "printf: %s: illegal number\n", numstr);
exit(1);
}
return num;
}