command line printf
git-svn-id: file:///srv/svn/repos/haiku/trunk/current@1456 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
ef77a305f8
commit
51dc1c7a64
552
src/apps/bin/printf.c
Normal file
552
src/apps/bin/printf.c
Normal 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user