# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/awk_xpg4/Makefile # # Copyright (c) 2018, Joyent, Inc. # NOTE: this is nawk in usr/src/cmd/awk_xpg4 to reside as /usr/xpg4/bin/awk PROG= awk XPG4PROG= awk OBJ1= awk0.o awk1.o awk2.o awk3.o awk4.o OBJ2= awk.o # Hammerhead: GCC 14 automatically links values-xpg4.o via CRT startup. XPG4AWKOBJ= OBJS= $(OBJ2) $(OBJ1) $(XPG4AWKOBJ) include ../Makefile.cmd CPPFLAGS += -D_FILE_OFFSET_BITS=64 CFLAGS += $(CCVERBOSE) CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress pointer/int cast warnings in legacy awk code CERRWARN += -Wno-pointer-to-int-cast YFLAGS += -d LDLIBS += -lm CLEANFILES= awk.c y.tab.h # because of labels from yacc awk.o : CERRWARN += -Wno-unused-label # not linted SMATCH=off # for messaging catalog POFILE= awk_xpg4.po POFILES= $(OBJ1:%.o=%.po) $(OBJ2:%.o=%.po) .KEEP_STATE: .PARALLEL: $(OBJS) all: $(XPG4) $(XPG4): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) $(POFILE): $(POFILES) $(RM) $@ cat $(POFILES) > $@ # install: all $(ROOTXPG4PROG) values-xpg4.o: ../../lib/crt/common/values-xpg4.c $(COMPILE.c) -o $@ ../../lib/crt/common/values-xpg4.c clean: $(RM) $(OBJS) $(XPG4) $(CLEANFILES) # Hammerhead: GNU Make grouped target (was dmake +) # yacc generates both .c and y.tab.h in one invocation awk.c y.tab.h &: awk.y $(YACC.y) awk.y mv y.tab.c awk.c awk.o: awk.c y.tab.h awk0.c: awk.c y.tab.h awk1.c: awk.c y.tab.h awk2.c: awk.c y.tab.h awk3.c: awk.c y.tab.h awk4.c: awk.c y.tab.h include ../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * awk -- common header file. * * Copyright 1986, 1994 by Mortice Kern Systems Inc. All rights reserved. * * This version uses the POSIX.2 compatible routines. * * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes * */ #ifndef AWK_H #define AWK_H #include #include #include #include #include #include #include #include #include #include #include #include #define YYMAXDEPTH 300 /* Max # of productions (used by yacc) */ #define YYSSIZE 300 /* Size of State/Value stacks (MKS YACC) */ #define MAXDIGINT 19 /* Number of digits in an INT */ #define FNULL ((FILE *)0) #define NNULL ((NODE *)0) #define SNULL ((STRING)0) #define LARGE INT_MAX /* Large integer */ #define NPFILE 32 /* Number of -[fl] options allowed */ #define NRECUR 3000 /* Maximum recursion depth */ #define M_LDATA 1 #ifdef M_LDATA #define NLINE 20000 /* Longest input record */ #define NFIELD 4000 /* Number of fields allowed */ #define NBUCKET 1024 /* # of symtab buckets (power of 2) */ #else #define NLINE 2048 /* Longest input record */ #define NFIELD 1024 /* Number of fields allowed */ #define NBUCKET 256 /* # of symtab buckets (power of 2) */ #endif #define NSNODE 40 /* Number of cached nodes */ #define NCONTEXT 50 /* Amount of context for error msgs */ #define hashbuck(n) ((n)&(NBUCKET-1)) #if BSD /* * A speedup for BSD. Use their routines which are * already optimised. Note that BSD bcopy does not * return a value. */ int bcmp(); #define memcmp(b1, b2, n) bcmp(b1, b2, n) void bcopy(); #define memcpy(b1, b2, n) bcopy(b2, b1, (int)n) #endif /* BSD */ #define vlook(n) vlookup(n, 0) /* * Basic AWK internal types. */ typedef double REAL; typedef long long INT; typedef wchar_t *STRING; typedef struct NODE *(*FUNCTION)(struct NODE *np); typedef void *REGEXP; /* * Node in the AWK interpreter expression tree. */ typedef struct NODE { ushort_t n_type; struct NODE *n_next; /* Symbol table/PARM link */ ushort_t n_flags; /* Node flags, type */ union { struct { ushort_t N_hash; /* Full hash value */ struct NODE *N_alink; /* Array link */ union { struct { STRING N_string; size_t N_strlen; } n_str; INT N_int; REAL N_real; FUNCTION N_function; struct NODE *N_ufunc; } n_tun; wchar_t N_name[1]; } n_term; struct { struct NODE *N_left; struct NODE *N_right; ushort_t N_lineno; } n_op; struct { struct NODE *N_left; /* Used for fliplist */ struct NODE *N_right; REGEXP N_regexp; /* Regular expression */ } n_re; } n_un; } NODE; /* * Definitions to make the node access much easier. */ #define n_hash n_un.n_term.N_hash /* full hash value is sym tbl */ #define n_scope n_un.n_term.N_hash /* local variable scope level */ #define n_alink n_un.n_term.N_alink /* link to array list */ #define n_string n_un.n_term.n_tun.n_str.N_string #define n_strlen n_un.n_term.n_tun.n_str.N_strlen #define n_int n_un.n_term.n_tun.N_int #define n_real n_un.n_term.n_tun.N_real #define n_function n_un.n_term.n_tun.N_function #define n_ufunc n_un.n_term.n_tun.N_ufunc #define n_name n_un.n_term.N_name #define n_left n_un.n_op.N_left #define n_right n_un.n_op.N_right #define n_lineno n_un.n_op.N_lineno #define n_keywtype n_un.n_op.N_lineno #define n_regexp n_un.n_re.N_regexp /* * Compress the types that are actually used in the final tree * to save space in the intermediate file. Allows 1 byte to * represent all types */ /* * n_flags bit assignments. */ #define FALLOC 0x01 /* Allocated node */ #define FSTATIC 0x00 /* Not allocated */ #define FMATCH 0x02 /* pattern,pattern (first part matches) */ #define FSPECIAL 0x04 /* Special pre-computed variable */ #define FINARRAY 0x08 /* NODE installed in N_alink array list */ #define FNOALLOC 0x10 /* mark node FALLOC, but don't malloc */ #define FSENSE 0x20 /* Sense if string looks like INT/REAL */ #define FSAVE (FSPECIAL|FINARRAY) /* assign leaves on */ #define FINT 0x40 /* Node has integer type */ #define FREAL 0x80 /* Node has real type */ #define FSTRING 0x100 /* Node has string type */ #define FNONTOK 0x200 /* Node has non-token type */ #define FVINT 0x400 /* Node looks like an integer */ #define FVREAL 0x800 /* Node looks like a real number */ #define FLARRAY 0x1000 /* Local array node */ /* * n_flags macros * These work when given an argument of np->n_flags */ #define isleaf(f) (!((f)&FNONTOK)) #define isstring(f) ((f)&FSTRING) #define isastring(f) (((f)&(FSTRING|FALLOC)) == (FSTRING|FALLOC)) #define isnumber(f) ((f)&(FINT|FVINT|FREAL|FVREAL)) #define isreal(f) ((f)&(FREAL|FVREAL)) #define isint(f) ((f)&(FINT|FVINT)) /* * Prototype file size is defined in awksize.h */ /* * Awkrun prototype default name */ #if defined(DOS) #if defined(__386__) #define AWK_PROTOTYPE M_ETCDIR(awkrunf.dos) #define AWK_LPROTOTYPE M_ETCDIR(awkrunf.dos) #else #define AWK_PROTOTYPE M_ETCDIR(awkrun.dos) #define AWK_LPROTOTYPE M_ETCDIR(awkrunl.dos) #endif #elif defined(OS2) #define AWK_PROTOTYPE M_ETCDIR(awkrun.os2) #elif defined(NT) #define AWK_PROTOTYPE M_ETCDIR(awkrun.nt) #else #define AWK_PROTOTYPE M_ETCDIR(awkrun.mod) #endif /* * This is a kludge that gets around a bug in compact & large * models under DOS. It also makes the generated * code faster even if there wasn't a bug. UNIX people: try * to ignore these noisy "near" declarations. */ #ifndef DOS #define near #endif typedef wchar_t near *LOCCHARP; /* pointer to local strings */ /* * Form of builtin symbols * This should be a union because only one of r_ivalue * and r_svalue is needed, but (alas) unions cannot be * initialised. */ typedef struct RESERVED { LOCCHARP r_name; int r_type; /* Type of node */ INT r_ivalue; /* Integer value or wcslen(r_svalue) */ STRING r_svalue; /* String value */ } RESERVED; /* * Table of builtin functions. */ typedef struct RESFUNC { LOCCHARP rf_name; int rf_type; /* FUNC || GETLINE */ FUNCTION rf_func; /* Function pointer */ } RESFUNC; /* * Structure holding list of open files. */ typedef struct OFILE { ushort_t f_mode; /* Open mode: WRITE, APPEND, PIPE */ FILE *f_fp; /* File pointer if open */ char *f_name; /* Remembered file name */ } OFILE; /* Global functions -- awk.y */ int yyparse(void); /* Global functions -- awk1.c */ #ifdef __WATCOMC__ #pragma aux yyerror aborts; #pragma aux awkerr aborts; #pragma aux awkperr aborts; #endif int yyerror(const char *msg, ...); void awkerr(const char *fmt, ...) __NORETURN; void awkperr(const char *fmt, ...); void uexit(NODE *); int yylex(void); NODE *renode(wchar_t *restr); wchar_t *emalloc(unsigned); wchar_t *erealloc(wchar_t *, unsigned); /* Global functions -- awk2.c */ void awk(void); void dobegin(void); void doend(int status) __NORETURN; int nextrecord(wchar_t *buf, FILE *fp); wchar_t *defrecord(wchar_t *bp, int lim, FILE *fp); wchar_t *charrecord(wchar_t *bp, int lim, FILE *fp); wchar_t *multirecord(wchar_t *bp, int lim, FILE *fp); wchar_t *whitefield(wchar_t **endp); wchar_t *blackfield(wchar_t **endp); wchar_t *refield(wchar_t **endp); void s_print(NODE *np); void s_prf(NODE *np); size_t xprintf(NODE *np, FILE *fp, wchar_t **cp); void awkclose(OFILE *op); /* Global functions -- awk3.c */ void strassign(NODE *np, STRING string, int flags, size_t length); NODE *nassign(NODE *np, NODE *value); NODE *assign(NODE *np, NODE *value); void delarray(NODE *np); NODE *node(int type, NODE *left, NODE *right); NODE *intnode(INT i); NODE *realnode(REAL r); NODE *stringnode(STRING str, int aflag, size_t wcslen); NODE *vlookup(wchar_t *name, int nocreate); NODE *emptynode(int type, size_t nlength); void freenode(NODE *np); void execute(NODE *np); INT exprint(NODE *np); REAL exprreal(NODE *np); STRING exprstring(NODE *np); STRING strsave(wchar_t *string); NODE *exprreduce(NODE *np); NODE *getlist(NODE **npp); NODE *symwalk(int *buckp, NODE **npp); REGEXP getregexp(NODE *np); void addsymtab(NODE *np); void delsymtab(NODE *np, int fflag); NODE * finstall(LOCCHARP name, FUNCTION f, int type); void kinstall(LOCCHARP name, int type); void fieldsplit(void); void promote(NODE *); /* Global functions -- awk4.c */ NODE *f_exp(NODE *np); NODE *f_int(NODE *np); NODE *f_log(NODE *np); NODE *f_sqrt(NODE *np); NODE *f_getline(NODE *np); NODE *f_index(NODE *np); NODE *f_length(NODE *np); NODE *f_split(NODE *np); NODE *f_sprintf(NODE *np); NODE *f_substr(NODE *np); NODE *f_rand(NODE *np); NODE *f_srand(NODE *np); NODE *f_sin(NODE *np); NODE *f_cos(NODE *np); NODE *f_atan2(NODE *np); NODE *f_sub(NODE *np); NODE *f_gsub(NODE *np); NODE *f_match(NODE *np); NODE *f_system(NODE *np); NODE *f_ord(NODE *np); NODE *f_tolower(NODE *np); NODE *f_toupper(NODE *np); NODE *f_close(NODE *np); NODE *f_asort(NODE *np); /* In awk0.c */ extern wchar_t _null[]; extern char r[]; extern char w[]; extern wchar_t s_OFMT[]; extern wchar_t s_CONVFMT[]; extern wchar_t s_NR[]; extern wchar_t s_NF[]; extern wchar_t s_OFS[]; extern wchar_t s_ORS[]; extern wchar_t s_RS[]; extern wchar_t s_FS[]; extern wchar_t s_FNR[]; extern wchar_t s_SUBSEP[]; extern wchar_t s_ARGC[], s_ARGV[], s_ENVIRON[]; extern wchar_t s_FILENAME[], s_SYMTAB[]; extern wchar_t s_BEGIN[], s_END[], s_next[]; extern wchar_t _begin[], _end[]; extern wchar_t s_exp[], s_getline[], s_index[], s_int[], s_length[], s_log[]; extern wchar_t s_split[], s_sprintf[], s_sqrt[], s_substr[]; extern wchar_t s_rand[], s_srand[], s_sin[], s_cos[], s_atan2[]; extern wchar_t s_sub[], s_gsub[], s_match[], s_system[], s_ord[]; extern wchar_t s_toupper[], s_tolower[], s_asort[]; extern wchar_t s_close[]; extern wchar_t redelim; extern unsigned char inprint; extern unsigned char funparm; extern unsigned char splitdone; extern uint_t npattern; extern uint_t nfield; extern uint_t fcount; extern uint_t phase; extern uint_t running; extern uchar_t catterm; extern uint_t lexlast; extern uint_t lineno; extern uchar_t needsplit, needenviron, doing_begin, begin_getline; extern ushort_t slevel; extern ushort_t loopexit; extern wchar_t radixpoint; extern REGEXP resep; extern RESERVED reserved[]; extern RESFUNC resfuncs[]; extern long NIOSTREAM; /* Maximum open I/O streams */ extern OFILE *ofiles; extern wchar_t *linebuf; extern size_t lbuflen; extern char interr[]; extern char nomem[]; extern NODE *symtab[NBUCKET]; extern NODE *yytree; extern NODE *freelist; extern wchar_t *(*awkrecord)(wchar_t *, int, FILE *); extern wchar_t *(*awkfield)(wchar_t **); extern NODE *constant; extern NODE *const0; extern NODE *const1; extern NODE *constundef; extern NODE *field0; extern NODE *incNR; extern NODE *incFNR; extern NODE *clrFNR; extern NODE *ARGVsubi; extern NODE *varNR; extern NODE *varFNR; extern NODE *varNF; extern NODE *varOFMT; extern NODE *varCONVFMT; extern NODE *varOFS; extern NODE *varORS; extern NODE *varFS; extern NODE *varRS; extern NODE *varARGC; extern NODE *varSUBSEP; extern NODE *varENVIRON; extern NODE *varSYMTAB; extern NODE *varFILENAME; extern NODE *curnode; extern NODE *inc_oper; extern NODE *asn_oper; extern char *mbunconvert(wchar_t *); extern wchar_t *mbstowcsdup(char *); extern char *wcstombsdup(wchar_t *); /* * The following defines the expected max length in chars of a printed number. * This should be the longest expected size for any type of number * ie. float, long etc. This number is used to calculate the approximate * number of chars needed to hold the number. */ #ifdef M_NUMSIZE #define NUMSIZE M_NUMSIZE #else #define NUMSIZE 30 #endif #define M_MB_L(s) L##s #ifdef __STDC__ #define ANSI(x) x #else #define const #define signed #define volatile #define ANSI(x) () #endif #define isWblank(x) (((x) == ' ' || (x) == '\t') ? 1 : 0) /* * Wide character version of regular expression functions. */ #define REGWMATCH_T int_regwmatch_t #define REGWCOMP int_regwcomp #define REGWEXEC int_regwexec #define REGWFREE int_regwfree #define REGWERROR int_regwerror #define REGWDOSUBA int_regwdosuba typedef struct { const wchar_t *rm_sp, *rm_ep; regoff_t rm_so, rm_eo; } int_regwmatch_t; extern int int_regwcomp(REGEXP *, const wchar_t *); extern int int_regwexec(REGEXP, const wchar_t *, size_t, int_regwmatch_t *, int); extern void int_regwfree(REGEXP); extern size_t int_regwerror(int, REGEXP, char *, size_t); extern int int_regwdosuba(REGEXP, const wchar_t *, const wchar_t *, wchar_t **, int, int *); #endif /* AWK_H */ %{ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * awk -- YACC grammar * * Copyright (c) 1995 by Sun Microsystems, Inc. * * Copyright 1986, 1992 by Mortice Kern Systems Inc. All rights reserved. * * This Software is unpublished, valuable, confidential property of * Mortice Kern Systems Inc. Use is authorized only in accordance * with the terms and conditions of the source licence agreement * protecting this Software. Any unauthorized use or disclosure of * this Software is strictly prohibited and will result in the * termination of the licence agreement. * * NOTE: this grammar correctly produces NO shift/reduce conflicts from YACC. * */ /* * Do not use any character constants as tokens, so the resulting C file * is codeset independent. */ #include "awk.h" static NODE * fliplist ANSI((NODE *np)); %} %union { NODE *node; }; /* * Do not use any character constants as tokens, so the resulting C file * is codeset independent. * * Declare terminal symbols before their operator * precedences to get them in a contiguous block * for giant switches in action() and exprreduce(). */ /* Tokens from exprreduce() */ %token PARM ARRAY UFUNC FIELD IN INDEX CONCAT %token NOT AND OR EXP QUEST %token EQ NE GE LE GT LT %token ADD SUB MUL DIV REM INC DEC PRE_INC PRE_DEC %token GETLINE CALLFUNC RE TILDE NRE /* Tokens shared by exprreduce() and action() */ %token ASG /* Tokens from action() */ %token PRINT PRINTF %token EXIT RETURN BREAK CONTINUE NEXT %token DELETE WHILE DO FOR FORIN IF /* * Terminal symbols not used in action() and exprrreduce() * switch statements. */ %token CONSTANT VAR FUNC %token DEFFUNC BEGIN END CLOSE ELSE PACT %right ELSE %token DOT CALLUFUNC /* * Tokens not used in grammar */ %token KEYWORD SVAR %token PIPESYM /* * Tokens representing character constants * TILDE, '~', taken care of above */ %token BAR /* '|' */ CARAT /* '^' */ LANGLE /* '<' */ RANGLE /* '>' */ PLUSC /* '+' */ HYPHEN /* '-' */ STAR /* '*' */ SLASH /* '/' */ PERCENT /* '%' */ EXCLAMATION /* '!' */ DOLLAR /* '$' */ LSQUARE /* '[' */ RSQUARE /* ']' */ LPAREN /* '(' */ RPAREN /* ')' */ SEMI /* ';' */ LBRACE /* '{' */ RBRACE /* '}' */ /* * Priorities of operators * Lowest to highest */ %left COMMA %right BAR PIPE WRITE APPEND %right ASG AADD ASUB AMUL ADIV AREM AEXP %right QUEST COLON %left OR %left AND %left IN %left CARAT %left TILDE NRE %left EQ NE LANGLE RANGLE GE LE %left CONCAT %left PLUSC HYPHEN %left STAR SLASH PERCENT %right UPLUS UMINUS %right EXCLAMATION %right EXP %right INC DEC URE %left DOLLAR LSQUARE RSQUARE %left LPAREN RPAREN %type prog rule pattern expr rvalue lvalue fexpr varlist varlist2 %type statement statlist fileout exprlist eexprlist simplepattern %type getline optvar var %type dummy %start dummy %% dummy: prog { yytree = fliplist(yytree); } ; prog: rule { yytree = $1; } | rule SEMI prog { if ($1 != NNULL) { if (yytree != NNULL) yytree = node(COMMA, $1, yytree); else yytree = $1; } } ; rule: pattern LBRACE statlist RBRACE { $$ = node(PACT, $1, $3); doing_begin = 0; } | LBRACE statlist RBRACE { npattern++; $$ = node(PACT, NNULL, $2); } | pattern { $$ = node(PACT, $1, node(PRINT, NNULL, NNULL)); doing_begin = 0; } | DEFFUNC VAR { $2->n_type = UFUNC; funparm = 1; } LPAREN varlist RPAREN { funparm = 0; } LBRACE statlist { uexit($5); } RBRACE { $2->n_ufunc = node(DEFFUNC, $5, fliplist($9)); $$ = NNULL; } | DEFFUNC UFUNC { awkerr((char *) gettext("function \"%S\" redefined"), $2->n_name); /* NOTREACHED */ } | { $$ = NNULL; } ; pattern: simplepattern | expr COMMA expr { ++npattern; $$ = node(COMMA, $1, $3); } ; simplepattern: BEGIN { $$ = node(BEGIN, NNULL, NNULL); doing_begin++; } | END { ++npattern; $$ = node(END, NNULL, NNULL); } | expr { ++npattern; $$ = $1; } ; eexprlist: exprlist | { $$ = NNULL; } ; exprlist: expr %prec COMMA | exprlist COMMA expr { $$ = node(COMMA, $1, $3); } ; varlist: { $$ = NNULL; } | varlist2 ; varlist2: var | var COMMA varlist2 { $$ = node(COMMA, $1, $3); } ; fexpr: expr | { $$ = NNULL; } ; /* * Normal expression (includes regular expression) */ expr: expr PLUSC expr { $$ = node(ADD, $1, $3); } | expr HYPHEN expr { $$ = node(SUB, $1, $3); } | expr STAR expr { $$ = node(MUL, $1, $3); } | expr SLASH expr { $$ = node(DIV, $1, $3); } | expr PERCENT expr { $$ = node(REM, $1, $3); } | expr EXP expr { $$ = node(EXP, $1, $3); } | expr AND expr { $$ = node(AND, $1, $3); } | expr OR expr { $$ = node(OR, $1, $3); } | expr QUEST expr COLON expr { $$ = node(QUEST, $1, node(COLON, $3, $5)); } | lvalue ASG expr { $$ = node(ASG, $1, $3); } | lvalue AADD expr { $$ = node(AADD, $1, $3); } | lvalue ASUB expr { $$ = node(ASUB, $1, $3); } | lvalue AMUL expr { $$ = node(AMUL, $1, $3); } | lvalue ADIV expr { $$ = node(ADIV, $1, $3); } | lvalue AREM expr { $$ = node(AREM, $1, $3); } | lvalue AEXP expr { $$ = node(AEXP, $1, $3); } | lvalue INC { $$ = node(INC, $1, NNULL); } | lvalue DEC { $$ = node(DEC, $1, NNULL); } | expr EQ expr { $$ = node(EQ, $1, $3); } | expr NE expr { $$ = node(NE, $1, $3); } | expr RANGLE expr { $$ = node(GT, $1, $3); } | expr LANGLE expr { $$ = node(LT, $1, $3); } | expr GE expr { $$ = node(GE, $1, $3); } | expr LE expr { $$ = node(LE, $1, $3); } | expr TILDE expr { $$ = node(TILDE, $1, $3); } | expr NRE expr { $$ = node(NRE, $1, $3); } | expr IN var { $$ = node(IN, $3, $1); } | LPAREN exprlist RPAREN IN var { $$ = node(IN, $5, $2); } | getline | rvalue | expr CONCAT expr { $$ = node(CONCAT, $1, $3); } ; lvalue: DOLLAR rvalue { $$ = node(FIELD, $2, NNULL); } /* * Prevents conflict with FOR LPAREN var IN var RPAREN production */ | var %prec COMMA | var LSQUARE exprlist RSQUARE { $$ = node(INDEX, $1, $3); } ; var: VAR | PARM ; rvalue: lvalue %prec COMMA | CONSTANT | LPAREN expr RPAREN term { $$ = $2; } | EXCLAMATION expr { $$ = node(NOT, $2, NNULL); } | HYPHEN expr %prec UMINUS { $$ = node(SUB, const0, $2); } | PLUSC expr %prec UPLUS { $$ = $2; } | DEC lvalue { $$ = node(PRE_DEC, $2, NNULL); } | INC lvalue { $$ = node(PRE_INC, $2, NNULL); } | FUNC { $$ = node(CALLFUNC, $1, NNULL); } | FUNC LPAREN eexprlist RPAREN term { $$ = node(CALLFUNC, $1, $3); } | UFUNC LPAREN eexprlist RPAREN term { $$ = node(CALLUFUNC, $1, $3); } | VAR LPAREN eexprlist RPAREN term { $$ = node(CALLUFUNC, $1, $3); } | SLASH {redelim='/';} URE SLASH %prec URE { $$ = $3; } ; statement: FOR LPAREN fexpr SEMI fexpr SEMI fexpr RPAREN statement { $$ = node(FOR, node(COMMA, $3, node(COMMA, $5, $7)), $9); } | FOR LPAREN var IN var RPAREN statement { register NODE *np; /* * attempt to optimize statements for the form * for (i in x) delete x[i] * to * delete x */ np = $7; if (np != NNULL && np->n_type == DELETE && (np = np->n_left)->n_type == INDEX && np->n_left == $5 && np->n_right == $3) $$ = node(DELETE, $5, NNULL); else $$ = node(FORIN, node(IN, $3, $5), $7); } | WHILE LPAREN expr RPAREN statement { $$ = node(WHILE, $3, $5); } | DO statement WHILE LPAREN expr RPAREN { $$ = node(DO, $5, $2); } | IF LPAREN expr RPAREN statement ELSE statement { $$ = node(IF, $3, node(ELSE, $5, $7)); } | IF LPAREN expr RPAREN statement %prec ELSE { $$ = node(IF, $3, node(ELSE, $5, NNULL)); } | CONTINUE SEMI { $$ = node(CONTINUE, NNULL, NNULL); } | BREAK SEMI { $$ = node(BREAK, NNULL, NNULL); } | NEXT SEMI { $$ = node(NEXT, NNULL, NNULL); } | DELETE lvalue SEMI { $$ = node(DELETE, $2, NNULL); } | RETURN fexpr SEMI { $$ = node(RETURN, $2, NNULL); } | EXIT fexpr SEMI { $$ = node(EXIT, $2, NNULL); } | PRINT eexprlist fileout SEMI { $$ = node(PRINT, $2, $3); } | PRINT LPAREN exprlist RPAREN fileout SEMI { $$ = node(PRINT, $3, $5); } | PRINTF exprlist fileout SEMI { $$ = node(PRINTF, $2, $3); } | PRINTF LPAREN exprlist RPAREN fileout SEMI { $$ = node(PRINTF, $3, $5); } | expr SEMI { $$ = $1; } | SEMI { $$ = NNULL; } | LBRACE statlist RBRACE { $$ = $2; } ; statlist: statement | statlist statement { if ($1 == NNULL) $$ = $2; else if ($2 == NNULL) $$ = $1; else $$ = node(COMMA, $1, $2); } ; fileout: WRITE expr { $$ = node(WRITE, $2, NNULL); } | APPEND expr { $$ = node(APPEND, $2, NNULL); } | PIPE expr { $$ = node(PIPE, $2, NNULL); } | { $$ = NNULL; } ; getline: GETLINE optvar %prec WRITE { $$ = node(GETLINE, $2, NNULL); } | expr BAR GETLINE optvar { $$ = node(GETLINE, $4, node(PIPESYM, $1, NNULL)); } | GETLINE optvar LANGLE expr { $$ = node(GETLINE, $2, node(LT, $4, NNULL)); } ; optvar: lvalue | { $$ = NNULL; } ; term: {catterm = 1;} ; %% /* * Flip a left-recursively generated list * so that it can easily be traversed from left * to right without recursion. */ static NODE * fliplist(NODE *np) { int type; if (np!=NNULL && !isleaf(np->n_flags) #if 0 && (type = np->n_type)!=FUNC && type!=UFUNC #endif ) { np->n_right = fliplist(np->n_right); if ((type=np->n_type)==COMMA) { register NODE *lp; while ((lp = np->n_left)!=NNULL && lp->n_type==COMMA) { register NODE* *spp; lp->n_right = fliplist(lp->n_right); for (spp = &lp->n_right; *spp != NNULL && (*spp)->n_type==COMMA; spp = &(*spp)->n_right) ; np->n_left = *spp; *spp = np; np = lp; } } if (np->n_left != NULL && (type = np->n_left->n_type)!= FUNC && type!=UFUNC) np->n_left = fliplist(np->n_left); } return (np); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Awk -- data definitions * * Copyright (c) 1995 by Sun Microsystems, Inc. * * Copyright 1986, 1992 by Mortice Kern Systems Inc. All rights reserved. * * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes */ #include "awk.h" #include "y.tab.h" /* * This file contains data definitions for awk. */ RESERVED reserved[] = { s_BEGIN, KEYWORD, BEGIN, NULL, s_END, KEYWORD, END, NULL, M_MB_L("break"), KEYWORD, BREAK, NULL, M_MB_L("continue"), KEYWORD, CONTINUE, NULL, M_MB_L("for"), KEYWORD, FOR, NULL, M_MB_L("if"), KEYWORD, IF, NULL, M_MB_L("else"), KEYWORD, ELSE, NULL, M_MB_L("in"), KEYWORD, IN, NULL, s_next, KEYWORD, NEXT, NULL, M_MB_L("while"), KEYWORD, WHILE, NULL, M_MB_L("do"), KEYWORD, DO, NULL, M_MB_L("print"), KEYWORD, PRINT, NULL, M_MB_L("printf"), KEYWORD, PRINTF, NULL, M_MB_L("return"), KEYWORD, RETURN, NULL, M_MB_L("func"), KEYWORD, DEFFUNC, NULL, M_MB_L("function"), KEYWORD, DEFFUNC, NULL, M_MB_L("delete"), KEYWORD, DELETE, NULL, M_MB_L("exit"), KEYWORD, EXIT, NULL, s_FILENAME, VAR, 0, _null, s_NF, SVAR, 0, NULL, s_NR, VAR, 0, NULL, s_FS, SVAR, 1, M_MB_L(" "), s_OFS, VAR, 1, M_MB_L(" "), s_ORS, VAR, 1, M_MB_L("\n"), s_OFMT, VAR, 4, M_MB_L("%.6g"), s_CONVFMT, VAR, 4, M_MB_L("%.6g"), s_RS, SVAR, 1, M_MB_L("\n"), s_FNR, VAR, 0, NULL, s_SUBSEP, VAR, 1, #ifdef M_AWK_SUBSEP M_AWK_SUBSEP, #else M_MB_L("\34"), #endif s_ARGC, SVAR, 0, NULL, (LOCCHARP)NULL }; RESFUNC resfuncs[] = { s_exp, FUNC, f_exp, s_getline, GETLINE, f_getline, s_index, FUNC, f_index, s_int, FUNC, f_int, s_length, FUNC, f_length, s_log, FUNC, f_log, s_split, FUNC, f_split, s_sprintf, FUNC, f_sprintf, s_sqrt, FUNC, f_sqrt, s_substr, FUNC, f_substr, s_rand, FUNC, f_rand, s_srand, FUNC, f_srand, s_sin, FUNC, f_sin, s_cos, FUNC, f_cos, s_atan2, FUNC, f_atan2, s_sub, FUNC, f_sub, s_gsub, FUNC, f_gsub, s_match, FUNC, f_match, s_system, FUNC, f_system, s_ord, FUNC, f_ord, s_toupper, FUNC, f_toupper, s_tolower, FUNC, f_tolower, s_asort, FUNC, f_asort, s_close, FUNC, f_close, (LOCCHARP)NULL }; OFILE *ofiles; /* Remembered open files (print) */ long NIOSTREAM = 512; /* max num of open file descriptors */ wchar_t _null[] = M_MB_L(""); /* Empty string */ char r[] = "r"; /* Read file mode */ char w[] = "w"; /* Write file mode */ wchar_t s_OFMT[] = M_MB_L("OFMT"); /* Name of "OFMT" variable */ wchar_t s_CONVFMT[] = M_MB_L("CONVFMT"); /* Name of "CONVFMT" variable */ wchar_t s_NR[] = M_MB_L("NR"); /* Name of "NR" variable */ wchar_t s_NF[] = M_MB_L("NF"); /* Name of "NF" variable */ wchar_t s_OFS[] = M_MB_L("OFS"); /* Name of "OFS" variable */ wchar_t s_ORS[] = M_MB_L("ORS"); /* Name of "ORS" variable */ wchar_t s_RS[] = M_MB_L("RS"); /* Name of "RS" variable */ wchar_t s_FS[] = M_MB_L("FS"); /* Name of "FS" variable */ wchar_t s_FNR[] = M_MB_L("FNR"); /* Name of "FNR" variable */ wchar_t s_SUBSEP[] = M_MB_L("SUBSEP"); /* Name of "SUBSEP" variable */ wchar_t s_ARGC[] = M_MB_L("ARGC"); /* Name of "ARGC" variable */ wchar_t s_ARGV[] = M_MB_L("ARGV"); /* Name of "ARGV" array variable */ wchar_t s_ENVIRON[] = M_MB_L("ENVIRON"); /* Name of "ENVIRON" array variable */ wchar_t s_FILENAME[] = M_MB_L("FILENAME"); /* Name of "FILENAME" variable */ wchar_t s_SYMTAB[] = M_MB_L("SYMTAB"); /* Name of "SYMTAB" variable */ wchar_t s_BEGIN[] = M_MB_L("BEGIN"); /* Name of "BEGIN" action */ wchar_t s_END[] = M_MB_L("END"); /* Name of "END" action */ wchar_t s_next[] = M_MB_L("next"); /* Name of "next" keyword */ wchar_t s_exp[] = M_MB_L("exp"); /* Name of "exp" function */ wchar_t s_getline[] = M_MB_L("getline"); /* Name of "getline" function */ wchar_t s_index[] = M_MB_L("index"); /* Name of "index" function */ wchar_t s_int[] = M_MB_L("int"); /* Name of "int" function */ wchar_t s_length[] = M_MB_L("length"); /* Name of "length" function */ wchar_t s_log[] = M_MB_L("log"); /* Name of "log" function */ wchar_t s_split[] = M_MB_L("split"); /* Name of "split" function */ wchar_t s_sprintf[] = M_MB_L("sprintf"); /* Name of "sprintf" function */ wchar_t s_sqrt[] = M_MB_L("sqrt"); /* Name of "sqrt" function */ wchar_t s_substr[] = M_MB_L("substr"); /* Name of "substr" function */ wchar_t s_rand[] = M_MB_L("rand"); /* Name of "rand" function */ wchar_t s_srand[] = M_MB_L("srand"); /* Name of "srand" function */ wchar_t s_sin[] = M_MB_L("sin"); /* Name of "sin" function */ wchar_t s_cos[] = M_MB_L("cos"); /* Name of "cos" function */ wchar_t s_atan2[] = M_MB_L("atan2"); /* Name of "atan" function */ wchar_t s_sub[] = M_MB_L("sub"); /* Name of "sub" function */ wchar_t s_gsub[] = M_MB_L("gsub"); /* Name of "gsub" function */ wchar_t s_match[] = M_MB_L("match"); /* Name of "match" function */ wchar_t s_system[] = M_MB_L("system"); /* Name of "system" function */ wchar_t s_ord[] = M_MB_L("ord"); /* Name of "ord" function */ wchar_t s_toupper[] = M_MB_L("toupper"); /* Name of "toupper" function */ wchar_t s_tolower[] = M_MB_L("tolower"); /* Name of "tolower" function */ wchar_t s_asort[] = M_MB_L("asort"); /* Name of "asort" function */ wchar_t s_close[] = M_MB_L("close"); /* Name of "close" function */ wchar_t redelim; /* Delimiter for regexp (yylex) */ uchar_t inprint; /* Special meaning for '>' & '|' */ uchar_t funparm; /* Defining function parameters */ uchar_t splitdone; /* Line split into fields (fieldbuf) */ uint npattern; /* Number of non-BEGIN patterns */ uint nfield; /* Number of fields (if splitdone) */ uint fcount; /* Field counter (used by blackfield)*/ uint phase; /* BEGIN, END, or 0 */ uint running = 0; /* Set if not in compile phase */ uchar_t catterm; /* Can inject concat or ';' */ uint lexlast = '\n'; /* Last lexical token */ uint lineno = 0; /* Current programme line number */ uchar_t doing_begin; /* set if compiling BEGIN block */ uchar_t begin_getline; /* flags a getline was done in BEGIN */ uchar_t needsplit; /* Set if $0 must be split when read */ uchar_t needenviron; /* Set if ENVIRON variable referenced */ ushort slevel; /* Scope level (0 == root) */ ushort loopexit; /* Short circuit loop with keyword */ wchar_t radixpoint; /* soft radix point for I18N */ REGEXP resep; /* Field separator as regexp */ wchar_t *linebuf = NULL; /* $0 buffer - malloc'd in awk1.c */ size_t lbuflen; /* Length of linebuf */ /* * XXX - Make sure to check where this error message is printed */ char interr[] = "internal execution tree error at E string"; char nomem[] = "insufficient memory for string storage"; NODE *symtab[NBUCKET]; /* Heads of symbol table buckets */ NODE *yytree; /* Code tree */ NODE *freelist; /* Free every pattern {action} line */ wchar_t *(*awkrecord) ANSI((wchar_t *, int, FILE*)) = defrecord; /* Function to read a record */ wchar_t *(*awkfield) ANSI((wchar_t **)) = whitefield; /* Function to extract a field */ /* * Nodes used to speed up the execution of the * interpreter. */ NODE *constant; /* Node to hold a constant INT */ NODE *const0; /* Constant INT 0 node */ NODE *const1; /* Constant INT 1 node */ NODE *constundef; /* Undefined variable */ NODE *field0; /* $0 */ NODE *incNR; /* Code to increment NR variable */ NODE *incFNR; /* Code to increment FNR variable */ NODE *clrFNR; /* Zero FNR variable (each file) */ NODE *ARGVsubi; /* Compute ARGV[i] */ NODE *varNR; /* Remove search for NR variable */ NODE *varFNR; /* Don't search for FNR variable */ NODE *varNF; /* Pointer to NF variable */ NODE *varOFMT; /* For s_prf */ NODE *varCONVFMT; /* For internal conv of float to str */ NODE *varOFS; /* For s_print */ NODE *varORS; /* For s_print */ NODE *varFS; /* Field separtor */ NODE *varRS; /* Record separator */ NODE *varARGC; /* Quick access to ARGC */ NODE *varSUBSEP; /* Quick access to SUBSEP */ NODE *varENVIRON; /* Pointer to ENVIRON variable */ NODE *varSYMTAB; /* Symbol table special variable */ NODE *varFILENAME; /* Node for FILENAME variable */ NODE *curnode; /* Pointer to current line */ NODE *inc_oper; /* used by INC/DEC in awk3.c */ NODE *asn_oper; /* used by AADD, etc in awk3.c */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 1986, 1994 by Mortice Kern Systems Inc. All rights reserved. */ /* * awk -- mainline, yylex, etc. * * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes */ #include "awk.h" #include "y.tab.h" #include #include #include #include static char *progfiles[NPFILE]; /* Programmes files for yylex */ static char **progfilep = &progfiles[0]; /* Pointer to last file */ static wchar_t *progptr; /* In-memory programme */ static int proglen; /* Length of progptr */ static wchar_t context[NCONTEXT]; /* Circular buffer of context */ static wchar_t *conptr = &context[0]; /* context ptr */ static FILE *progfp; /* Stdio stream for programme */ static char *filename; #ifdef DEBUG static int dflag; #endif #define AWK_EXEC_MAGIC "" #define LEN_EXEC_MAGIC 10 static char unbal[] = "unbalanced E char"; static void awkarginit(int c, char **av); static int lexid(wint_t c); static int lexnumber(wint_t c); static int lexstring(wint_t endc); static int lexregexp(wint_t endc); static void awkvarinit(void); static wint_t lexgetc(void); static void lexungetc(wint_t c); static size_t lexescape(wint_t endc, int regx, int cmd_line_operand); static void awkierr(int perr, const char *fmt, va_list ap) __NORETURN; static int usage(void); void strescape(wchar_t *str); static const char *toprint(wint_t); char *_cmdname; static wchar_t *mbconvert(char *str); extern int isclvar(wchar_t *arg); /* * mainline for awk */ int main(int argc, char *argv[]) { wchar_t *ap; char *cmd; cmd = argv[0]; _cmdname = cmd; linebuf = emalloc(NLINE * sizeof (wchar_t)); /* * At this point only messaging should be internationalized. * numbers are still scanned as in the Posix locale. */ (void) setlocale(LC_ALL, ""); (void) setlocale(LC_NUMERIC, "C"); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); awkvarinit(); /* running = 1; */ while (argc > 1 && *argv[1] == '-') { void *save_ptr = NULL; ap = mbstowcsdup(&argv[1][1]); if (ap == NULL) break; if (*ap == '\0') { free(ap); break; } save_ptr = (void *) ap; ++argv; --argc; if (*ap == '-' && ap[1] == '\0') break; for (; *ap != '\0'; ++ap) { switch (*ap) { #ifdef DEBUG case 'd': dflag = 1; continue; #endif case 'f': if (argc < 2) { (void) fprintf(stderr, gettext("Missing script file\n")); return (1); } *progfilep++ = argv[1]; --argc; ++argv; continue; case 'F': if (ap[1] == '\0') { if (argc < 2) { (void) fprintf(stderr, gettext("Missing field separator\n")); return (1); } ap = mbstowcsdup(argv[1]); --argc; ++argv; } else ++ap; strescape(ap); strassign(varFS, linebuf, FALLOC, wcslen(linebuf)); break; case 'v': { wchar_t *vp; wchar_t *arg; if (argc < 2) { (void) fprintf(stderr, gettext("Missing variable assignment\n")); return (1); } arg = mbconvert(argv[1]); /* * Ensure the variable expression * is valid (correct form). */ if (((vp = wcschr(arg, '=')) != NULL) && isclvar(arg)) { *vp = '\0'; strescape(vp+1); strassign(vlook(arg), linebuf, FALLOC|FSENSE, wcslen(linebuf)); *vp = '='; } else { (void) fprintf(stderr, gettext( "Invalid form for variable " "assignment: %S\n"), arg); return (1); } --argc; ++argv; continue; } default: (void) fprintf(stderr, gettext("Unknown option \"-%S\"\n"), ap); return (usage()); } break; } if (save_ptr) free(save_ptr); } if (progfilep == &progfiles[0]) { if (argc < 2) return (usage()); filename = "[command line]"; /* BUG: NEEDS TRANSLATION */ progptr = mbstowcsdup(argv[1]); proglen = wcslen(progptr); --argc; ++argv; } argv[0] = cmd; awkarginit(argc, argv); /* running = 0; */ (void) yyparse(); lineno = 0; /* * Ok, done parsing, so now activate the rest of the nls stuff, set * the radix character. */ (void) setlocale(LC_ALL, ""); radixpoint = *localeconv()->decimal_point; awk(); /* NOTREACHED */ return (0); } /* * Do initial setup of buffers, etc. * This must be called before most processing * and especially before lexical analysis. * Variables initialised here will be overruled by command * line parameter initialisation. */ static void awkvarinit() { NODE *np; (void) setvbuf(stderr, NULL, _IONBF, 0); if ((NIOSTREAM = sysconf(_SC_OPEN_MAX) - 4) <= 0) { (void) fprintf(stderr, gettext("not enough available file descriptors")); exit(1); } ofiles = (OFILE *)emalloc(sizeof (OFILE)*NIOSTREAM); #ifdef A_ZERO_POINTERS (void) memset((wchar_t *)ofiles, 0, sizeof (OFILE) * NIOSTREAM); #else { /* initialize file descriptor table */ OFILE *fp; for (fp = ofiles; fp < &ofiles[NIOSTREAM]; fp += 1) { fp->f_fp = FNULL; fp->f_mode = 0; fp->f_name = (char *)0; } } #endif constant = intnode((INT)0); const0 = intnode((INT)0); const1 = intnode((INT)1); constundef = emptynode(CONSTANT, 0); constundef->n_flags = FSTRING|FVINT; constundef->n_string = _null; constundef->n_strlen = 0; inc_oper = emptynode(ADD, 0); inc_oper->n_right = const1; asn_oper = emptynode(ADD, 0); field0 = node(FIELD, const0, NNULL); { RESFUNC near*rp; for (rp = &resfuncs[0]; rp->rf_name != (LOCCHARP)NULL; ++rp) { np = finstall(rp->rf_name, rp->rf_func, rp->rf_type); } } { RESERVED near*rp; for (rp = &reserved[0]; rp->r_name != (LOCCHARP)NULL; ++rp) { switch (rp->r_type) { case SVAR: case VAR: running = 1; np = vlook(rp->r_name); if (rp->r_type == SVAR) np->n_flags |= FSPECIAL; if (rp->r_svalue != NULL) strassign(np, rp->r_svalue, FSTATIC, (size_t)rp->r_ivalue); else { constant->n_int = rp->r_ivalue; (void) assign(np, constant); } running = 0; break; case KEYWORD: kinstall(rp->r_name, (int)rp->r_ivalue); break; } } } varNR = vlook(s_NR); varFNR = vlook(s_FNR); varNF = vlook(s_NF); varOFMT = vlook(s_OFMT); varCONVFMT = vlook(s_CONVFMT); varOFS = vlook(s_OFS); varORS = vlook(s_ORS); varRS = vlook(s_RS); varFS = vlook(s_FS); varARGC = vlook(s_ARGC); varSUBSEP = vlook(s_SUBSEP); varENVIRON = vlook(s_ENVIRON); varFILENAME = vlook(s_FILENAME); varSYMTAB = vlook(s_SYMTAB); incNR = node(ASG, varNR, node(ADD, varNR, const1)); incFNR = node(ASG, varFNR, node(ADD, varFNR, const1)); clrFNR = node(ASG, varFNR, const0); } /* * Initialise awk ARGC, ARGV variables. */ static void awkarginit(int ac, char **av) { int i; wchar_t *cp; ARGVsubi = node(INDEX, vlook(s_ARGV), constant); running = 1; constant->n_int = ac; (void) assign(varARGC, constant); for (i = 0; i < ac; ++i) { cp = mbstowcsdup(av[i]); constant->n_int = i; strassign(exprreduce(ARGVsubi), cp, FSTATIC|FSENSE, wcslen(cp)); } running = 0; } /* * Clean up when done parsing a function. * All formal parameters, because of a deal (funparm) in * yylex, get put into the symbol table in front of any * global variable of the same name. When the entire * function is parsed, remove these formal dummy nodes * from the symbol table but retain the nodes because * the generated tree points at them. */ void uexit(NODE *np) { NODE *formal; while ((formal = getlist(&np)) != NNULL) delsymtab(formal, 0); } /* * The lexical analyzer. */ int yylex() { wint_t c, c1; int i; static int savetoken = 0; static int wasfield; static int isfuncdef; static int nbrace, nparen, nbracket; static struct ctosymstruct { wint_t c, sym; } ctosym[] = { { '|', BAR }, { '^', CARAT }, { '~', TILDE }, { '<', LANGLE }, { '>', RANGLE }, { '+', PLUSC }, { '-', HYPHEN }, { '*', STAR }, { '/', SLASH }, { '%', PERCENT }, { '!', EXCLAMATION }, { '$', DOLLAR }, { '[', LSQUARE }, { ']', RSQUARE }, { '(', LPAREN }, { ')', RPAREN }, { ';', SEMI }, { '{', LBRACE }, { '}', RBRACE }, { 0, 0 } }; if (savetoken) { c = savetoken; savetoken = 0; } else if (redelim != '\0') { c = redelim; redelim = 0; catterm = 0; savetoken = c; c = lexlast = lexregexp(c); goto out; } else while ((c = lexgetc()) != WEOF) { if (iswalpha(c) || c == '_') { c = lexid(c); } else if (iswdigit(c) || c == '.') { c = lexnumber(c); } else if (isWblank(c)) { continue; } else switch (c) { #if DOS || OS2 case 032: /* ^Z */ continue; #endif case '"': c = lexstring(c); break; case '#': while ((c = lexgetc()) != '\n' && c != WEOF) ; lexungetc(c); continue; case '+': if ((c1 = lexgetc()) == '+') c = INC; else if (c1 == '=') c = AADD; else lexungetc(c1); break; case '-': if ((c1 = lexgetc()) == '-') c = DEC; else if (c1 == '=') c = ASUB; else lexungetc(c1); break; case '*': if ((c1 = lexgetc()) == '=') c = AMUL; else if (c1 == '*') { if ((c1 = lexgetc()) == '=') c = AEXP; else { c = EXP; lexungetc(c1); } } else lexungetc(c1); break; case '^': if ((c1 = lexgetc()) == '=') { c = AEXP; } else { c = EXP; lexungetc(c1); } break; case '/': if ((c1 = lexgetc()) == '=' && lexlast != RE && lexlast != NRE && lexlast != ';' && lexlast != '\n' && lexlast != ',' && lexlast != '(') c = ADIV; else lexungetc(c1); break; case '%': if ((c1 = lexgetc()) == '=') c = AREM; else lexungetc(c1); break; case '&': if ((c1 = lexgetc()) == '&') c = AND; else lexungetc(c1); break; case '|': if ((c1 = lexgetc()) == '|') c = OR; else { lexungetc(c1); if (inprint) c = PIPE; } break; case '>': if ((c1 = lexgetc()) == '=') c = GE; else if (c1 == '>') c = APPEND; else { lexungetc(c1); if (nparen == 0 && inprint) c = WRITE; } break; case '<': if ((c1 = lexgetc()) == '=') c = LE; else lexungetc(c1); break; case '!': if ((c1 = lexgetc()) == '=') c = NE; else if (c1 == '~') c = NRE; else lexungetc(c1); break; case '=': if ((c1 = lexgetc()) == '=') c = EQ; else { lexungetc(c1); c = ASG; } break; case '\n': switch (lexlast) { case ')': if (catterm || inprint) { c = ';'; break; } /* FALLTHROUGH */ case AND: case OR: case COMMA: case '{': case ELSE: case ';': case DO: continue; case '}': if (nbrace != 0) continue; /* FALLTHROUGH */ default: c = ';'; break; } break; case ELSE: if (lexlast != ';') { savetoken = ELSE; c = ';'; } break; case '(': ++nparen; break; case ')': if (--nparen < 0) awkerr(unbal, "()"); break; case '{': nbrace++; break; case '}': if (--nbrace < 0) { char brk[3]; brk[0] = '{'; brk[1] = '}'; brk[2] = '\0'; awkerr(unbal, brk); } if (lexlast != ';') { savetoken = c; c = ';'; } break; case '[': ++nbracket; break; case ']': if (--nbracket < 0) { char brk[3]; brk[0] = '['; brk[1] = ']'; brk[2] = '\0'; awkerr(unbal, brk); } break; case '\\': if ((c1 = lexgetc()) == '\n') continue; lexungetc(c1); break; case ',': c = COMMA; break; case '?': c = QUEST; break; case ':': c = COLON; break; default: if (!iswprint(c)) awkerr( gettext("invalid character \"%s\""), toprint(c)); break; } break; } switch (c) { case ']': ++catterm; break; case VAR: if (catterm) { savetoken = c; c = CONCAT; catterm = 0; } else if (!isfuncdef) { if ((c1 = lexgetc()) != '(') ++catterm; lexungetc(c1); } isfuncdef = 0; break; case PARM: case CONSTANT: if (catterm) { savetoken = c; c = CONCAT; catterm = 0; } else { if (lexlast == '$') wasfield = 2; ++catterm; } break; case INC: case DEC: if (!catterm || lexlast != CONSTANT || wasfield) break; /* FALLTHROUGH */ case UFUNC: case FUNC: case GETLINE: case '!': case '$': case '(': if (catterm) { savetoken = c; c = CONCAT; catterm = 0; } break; case '}': if (nbrace == 0) savetoken = ';'; /* FALLTHROUGH */ case ';': inprint = 0; /* FALLTHROUGH */ default: if (c == DEFFUNC) isfuncdef = 1; catterm = 0; } lexlast = c; if (wasfield) wasfield--; /* * Map character constants to symbolic names. */ for (i = 0; ctosym[i].c != 0; i++) if (c == ctosym[i].c) { c = ctosym[i].sym; break; } out: #ifdef DEBUG if (dflag) (void) printf("%d\n", (int)c); #endif return ((int)c); } /* * Read a number for the lexical analyzer. * Input is the first character of the number. * Return value is the lexical type. */ static int lexnumber(wint_t c) { wchar_t *cp; int dotfound = 0; int efound = 0; INT number; cp = linebuf; do { if (iswdigit(c)) ; else if (c == '.') { if (dotfound++) break; } else if (c == 'e' || c == 'E') { if ((c = lexgetc()) != '-' && c != '+') { lexungetc(c); c = 'e'; } else *cp++ = 'e'; if (efound++) break; } else break; *cp++ = c; } while ((c = lexgetc()) != WEOF); *cp = '\0'; if (dotfound && cp == linebuf+1) return (DOT); lexungetc(c); errno = 0; if (!dotfound && !efound && ((number = wcstol(linebuf, (wchar_t **)0, 10)), errno != ERANGE)) yylval.node = intnode(number); else yylval.node = realnode((REAL)wcstod(linebuf, (wchar_t **)0)); return (CONSTANT); } /* * Read an identifier. * Input is first character of identifier. * Return VAR. */ static int lexid(wint_t c) { wchar_t *cp; size_t i; NODE *np; cp = linebuf; do { *cp++ = c; c = lexgetc(); } while (iswalpha(c) || iswdigit(c) || c == '_'); *cp = '\0'; lexungetc(c); yylval.node = np = vlook(linebuf); switch (np->n_type) { case KEYWORD: switch (np->n_keywtype) { case PRINT: case PRINTF: ++inprint; /* FALLTHROUGH */ default: return ((int)np->n_keywtype); } /* NOTREACHED */ case ARRAY: case VAR: /* * If reading the argument list, create a dummy node * for the duration of that function. These variables * can be removed from the symbol table at function end * but they must still exist because the execution tree * knows about them. */ if (funparm) { do_funparm: np = emptynode(PARM, i = (cp-linebuf)); np->n_flags = FSTRING; np->n_string = _null; np->n_strlen = 0; (void) memcpy(np->n_name, linebuf, (i+1) * sizeof (wchar_t)); addsymtab(np); yylval.node = np; } else if (np == varNF || (np == varFS && (!doing_begin || begin_getline))) { /* * If the user program references NF or sets * FS either outside of a begin block or * in a begin block after a getline then the * input line will be split immediately upon read * rather than when a field is first referenced. */ needsplit = 1; } else if (np == varENVIRON) needenviron = 1; /* FALLTHROUGH */ case PARM: return (VAR); case UFUNC: /* * It is ok to redefine functions as parameters */ if (funparm) goto do_funparm; /* FALLTHROUGH */ case FUNC: case GETLINE: /* * When a getline is encountered, clear the 'doing_begin' flag. * This will force the 'needsplit' flag to be set, even inside * a begin block, if FS is altered. (See VAR case above) */ if (doing_begin) begin_getline = 1; return (np->n_type); } /* NOTREACHED */ return (0); } /* * Read a string for the lexical analyzer. * `endc' terminates the string. */ static int lexstring(wint_t endc) { size_t length = lexescape(endc, 0, 0); yylval.node = stringnode(linebuf, FALLOC, length); return (CONSTANT); } /* * Read a regular expression. */ static int lexregexp(wint_t endc) { (void) lexescape(endc, 1, 0); yylval.node = renode(linebuf); return (URE); } /* * Process a string, converting the escape characters as required by * 1003.2. The processed string ends up in the global linebuf[]. This * routine also changes the value of 'progfd' - the program file * descriptor, so it should be used with some care. It is presently used to * process -v (awk1.c) and var=str type arguments (awk2.c, nextrecord()). */ void strescape(wchar_t *str) { progptr = str; proglen = wcslen(str) + 1; /* Include \0 */ (void) lexescape('\0', 0, 1); progptr = NULL; } /* * Read a string or regular expression, terminated by ``endc'', * for lexical analyzer, processing escape sequences. * Return string length. */ static size_t lexescape(wint_t endc, int regx, int cmd_line_operand) { static char nlre[256]; static char nlstr[256]; static char eofre[256]; static char eofstr[256]; int first_time = 1; wint_t c; wchar_t *cp; int n, max; if (first_time == 1) { (void) strcpy(nlre, gettext("Newline in regular expression\n")); (void) strcpy(nlstr, gettext("Newline in string\n")); (void) strcpy(eofre, gettext("EOF in regular expression\n")); (void) strcpy(eofstr, gettext("EOF in string\n")); first_time = 0; } cp = linebuf; while ((c = lexgetc()) != endc) { if (c == '\n') awkerr(regx ? nlre : nlstr); if (c == '\\') { switch (c = lexgetc(), c) { case '\\': if (regx) *cp++ = '\\'; break; case '/': c = '/'; break; case 'n': c = '\n'; break; case 'b': c = '\b'; break; case 't': c = '\t'; break; case 'r': c = '\r'; break; case 'f': c = '\f'; break; case 'v': c = '\v'; break; case 'a': c = (char)0x07; break; case 'x': n = 0; while (iswxdigit(c = lexgetc())) { if (iswdigit(c)) c -= '0'; else if (iswupper(c)) c -= 'A'-10; else c -= 'a'-10; n = (n<<4) + c; } lexungetc(c); c = n; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': #if 0 /* * Posix.2 draft 10 disallows the use of back-referencing - it explicitly * requires processing of the octal escapes both in strings and * regular expressions. The following code is disabled instead of * removed as back-referencing may be reintroduced in a future draft * of the standard. */ /* * For regular expressions, we disallow * \ooo to mean octal character, in favour * of back referencing. */ if (regx) { *cp++ = '\\'; break; } #endif max = 3; n = 0; do { n = (n<<3) + c-'0'; if ((c = lexgetc()) > '7' || c < '0') break; } while (--max); lexungetc(c); /* * an octal escape sequence must have at least * 2 digits after the backslash, otherwise * it gets passed straight thru for possible * use in backreferencing. */ if (max == 3) { *cp++ = '\\'; n += '0'; } c = n; break; case '\n': continue; default: if (c != endc || cmd_line_operand) { *cp++ = '\\'; if (c == endc) lexungetc(c); } } } if (c == WEOF) awkerr(regx ? eofre : eofstr); *cp++ = c; } *cp = '\0'; return (cp - linebuf); } /* * Build a regular expression NODE. * Argument is the string holding the expression. */ NODE * renode(wchar_t *s) { NODE *np; int n; np = emptynode(RE, 0); np->n_left = np->n_right = NNULL; if ((n = REGWCOMP(&np->n_regexp, s)) != REG_OK) { int m; char *p; m = REGWERROR(n, np->n_regexp, NULL, 0); p = (char *)emalloc(m); REGWERROR(n, np->n_regexp, p, m); awkerr("/%S/: %s", s, p); } return (np); } /* * Get a character for the lexical analyser routine. */ static wint_t lexgetc() { wint_t c; static char **files = &progfiles[0]; if (progfp != FNULL && (c = fgetwc(progfp)) != WEOF) ; else { if (progptr != NULL) { if (proglen-- <= 0) c = WEOF; else c = *progptr++; } else { if (progfp != FNULL) { if (progfp != stdin) (void) fclose(progfp); else clearerr(progfp); progfp = FNULL; } if (files < progfilep) { filename = *files++; lineno = 1; if (filename[0] == '-' && filename[1] == '\0') progfp = stdin; else if ((progfp = fopen(filename, r)) == FNULL) { (void) fprintf(stderr, gettext("script file \"%s\""), filename); exit(1); } c = fgetwc(progfp); } } } if (c == '\n') ++lineno; if (conptr >= &context[NCONTEXT]) conptr = &context[0]; if (c != WEOF) *conptr++ = c; return (c); } /* * Return a character for lexical analyser. * Only one returned character is (not enforced) legitimite. */ static void lexungetc(wint_t c) { if (c == '\n') --lineno; if (c != WEOF) { if (conptr == &context[0]) conptr = &context[NCONTEXT]; *--conptr = '\0'; } if (progfp != FNULL) { (void) ungetwc(c, progfp); return; } if (c == WEOF) return; *--progptr = c; proglen++; } /* * Syntax errors during parsing. */ int yyerror(const char *s, ...) { if (lexlast == FUNC || lexlast == GETLINE || lexlast == KEYWORD) if (lexlast == KEYWORD) awkerr(gettext("inadmissible use of reserved keyword")); else awkerr(gettext("attempt to redefine builtin function")); awkerr(s); return (0); } /* * Error routine for all awk errors. */ void awkerr(const char *fmt, ...) { va_list args; va_start(args, fmt); awkierr(0, fmt, args); va_end(args); } /* * Error routine like "awkerr" except that it prints out * a message that includes an errno-specific indication. */ void awkperr(const char *fmt, ...) { va_list args; va_start(args, fmt); awkierr(1, fmt, args); va_end(args); } /* * Common internal routine for awkerr, awkperr */ static void awkierr(int perr, const char *fmt, va_list ap) { static char sep1[] = "\n>>>\t"; static char sep2[] = "\t<<<"; int saveerr = errno; (void) fprintf(stderr, "%s: ", _cmdname); if (running) { (void) fprintf(stderr, gettext("line %u ("), curnode == NNULL ? 0 : curnode->n_lineno); if (phase == 0) (void) fprintf(stderr, "NR=%lld): ", (INT)exprint(varNR)); else (void) fprintf(stderr, "%s): ", phase == BEGIN ? s_BEGIN : s_END); } else if (lineno != 0) { (void) fprintf(stderr, gettext("file \"%s\": "), filename); (void) fprintf(stderr, gettext("line %u: "), lineno); } (void) vfprintf(stderr, gettext(fmt), ap); if (perr == 1) (void) fprintf(stderr, ": %s", strerror(saveerr)); if (perr != 2 && !running) { wchar_t *cp; int n; int c; (void) fprintf(stderr, gettext(" Context is:%s"), sep1); cp = conptr; n = NCONTEXT; do { if (cp >= &context[NCONTEXT]) cp = &context[0]; if ((c = *cp++) != '\0') (void) fputs(c == '\n' ? sep1 : toprint(c), stderr); } while (--n != 0); (void) fputs(sep2, stderr); } (void) fprintf(stderr, "\n"); exit(1); } wchar_t * emalloc(unsigned n) { wchar_t *cp; if ((cp = malloc(n)) == NULL) awkerr(nomem); return (cp); } wchar_t * erealloc(wchar_t *p, unsigned n) { wchar_t *cp; if ((cp = realloc(p, n)) == NULL) awkerr(nomem); return (cp); } /* * usage message for awk */ static int usage() { (void) fprintf(stderr, gettext( "Usage: awk [-F ERE] [-v var=val] 'program' [var=val ...] [file ...]\n" " awk [-F ERE] -f progfile ... [-v var=val] [var=val ...] [file ...]\n")); return (2); } static wchar_t * mbconvert(char *str) { static wchar_t *op = 0; if (op != 0) free(op); return (op = mbstowcsdup(str)); } char * mbunconvert(wchar_t *str) { static char *op = 0; if (op != 0) free(op); return (op = wcstombsdup(str)); } /* * Solaris port - following functions are typical MKS functions written * to work for Solaris. */ wchar_t * mbstowcsdup(char *s) { int n; wchar_t *w; n = strlen(s) + 1; if ((w = (wchar_t *)malloc(n * sizeof (wchar_t))) == NULL) return (NULL); if (mbstowcs(w, s, n) == (size_t)-1) return (NULL); return (w); } char * wcstombsdup(wchar_t *w) { int n; char *mb; /* Fetch memory for worst case string length */ n = wslen(w) + 1; n *= MB_CUR_MAX; if ((mb = (char *)malloc(n)) == NULL) { return (NULL); } /* Convert the string */ if ((n = wcstombs(mb, w, n)) == -1) { int saverr = errno; free(mb); errno = saverr; return (0); } /* Shrink the string down */ if ((mb = (char *)realloc(mb, strlen(mb)+1)) == NULL) { return (NULL); } return (mb); } /* * The upe_ctrls[] table contains the printable 'control-sequences' for the * character values 0..31 and 127. The first entry is for value 127, thus the * entries for the remaining character values are from 1..32. */ static const char *const upe_ctrls[] = { "^?", "^@", "^A", "^B", "^C", "^D", "^E", "^F", "^G", "^H", "^I", "^J", "^K", "^L", "^M", "^N", "^O", "^P", "^Q", "^R", "^S", "^T", "^U", "^V", "^W", "^X", "^Y", "^Z", "^[", "^\\", "^]", "^^", "^_" }; /* * Return a printable string corresponding to the given character value. If * the character is printable, simply return it as the string. If it is in * the range specified by table 5-101 in the UPE, return the corresponding * string. Otherwise, return an octal escape sequence. */ static const char * toprint(wchar_t c) { int n, len; unsigned char *ptr; static char mbch[MB_LEN_MAX+1]; static char buf[5 * MB_LEN_MAX + 1]; if ((n = wctomb(mbch, c)) == -1) { /* Should never happen */ (void) sprintf(buf, "\\%x", c); return (buf); } mbch[n] = '\0'; if (iswprint(c)) { return (mbch); } else if (c == 127) { return (upe_ctrls[0]); } else if (c < 32) { /* Print as in Table 5-101 in the UPE */ return (upe_ctrls[c+1]); } else { /* Print as an octal escape sequence */ for (len = 0, ptr = (unsigned char *) mbch; 0 < n; --n, ++ptr) len += sprintf(buf+len, "\\%03o", *ptr); } return (buf); } static int wcoff(const wchar_t *astring, const int off) { const wchar_t *s = astring; int c = 0; char mb[MB_LEN_MAX]; while (c < off) { int n; if ((n = wctomb(mb, *s)) == 0) break; if (n == -1) n = 1; c += n; s++; } return (s - astring); } #define NREGHASH 64 #define NREGHOLD 1024 /* max number unused entries */ static int nregunref; struct reghashq { struct qelem hq; struct regcache *regcachep; }; struct regcache { struct qelem lq; wchar_t *pattern; regex_t re; int refcnt; struct reghashq hash; }; static struct qelem reghash[NREGHASH], reglink; /* * Generate a hash value of the given wchar string. * The hashing method is similar to what Java does for strings. */ static uint_t regtxthash(const wchar_t *str) { int k = 0; while (*str != L'\0') k = (31 * k) + *str++; k += ~(k << 9); k ^= (k >> 14); k += (k << 4); k ^= (k >> 10); return (k % NREGHASH); } int int_regwcomp(REGEXP *r, const wchar_t *pattern) { regex_t re; char *mbpattern; int ret; uint_t key; struct qelem *qp; struct regcache *rcp; key = regtxthash(pattern); for (qp = reghash[key].q_forw; qp != NULL; qp = qp->q_forw) { rcp = ((struct reghashq *)qp)->regcachep; if (*rcp->pattern == *pattern && wcscmp(rcp->pattern, pattern) == 0) break; } if (qp != NULL) { /* update link. put this one at the beginning */ if (rcp != (struct regcache *)reglink.q_forw) { remque(&rcp->lq); insque(&rcp->lq, ®link); } if (rcp->refcnt == 0) nregunref--; /* no longer unref'ed */ rcp->refcnt++; *(struct regcache **)r = rcp; return (REG_OK); } if ((mbpattern = wcstombsdup((wchar_t *)pattern)) == NULL) return (REG_ESPACE); ret = regcomp(&re, mbpattern, REG_EXTENDED); free(mbpattern); if (ret != REG_OK) return (ret); if ((rcp = malloc(sizeof (struct regcache))) == NULL) return (REG_ESPACE); rcp->re = re; if ((rcp->pattern = wsdup(pattern)) == NULL) { regfree(&re); free(rcp); return (REG_ESPACE); } rcp->refcnt = 1; insque(&rcp->lq, ®link); insque(&rcp->hash.hq, ®hash[key]); rcp->hash.regcachep = rcp; *(struct regcache **)r = rcp; return (ret); } void int_regwfree(REGEXP r) { int cnt; struct qelem *qp, *nqp; struct regcache *rcp; rcp = (struct regcache *)r; if (--rcp->refcnt != 0) return; /* this cache has no reference */ if (++nregunref < NREGHOLD) return; /* * We've got too much unref'ed regex. Free half of least * used regex. */ cnt = 0; for (qp = reglink.q_forw; qp != NULL; qp = nqp) { nqp = qp->q_forw; rcp = (struct regcache *)qp; if (rcp->refcnt != 0) continue; /* free half of them */ if (++cnt < (NREGHOLD / 2)) continue; /* detach and free */ remque(&rcp->lq); remque(&rcp->hash.hq); /* free up */ free(rcp->pattern); regfree(&rcp->re); free(rcp); nregunref--; } } size_t int_regwerror(int errcode, REGEXP r, char *errbuf, size_t bufsiz) { struct regcache *rcp; rcp = (struct regcache *)r; return (regerror(errcode, &rcp->re, errbuf, bufsiz)); } int int_regwexec(REGEXP r, /* compiled RE */ const wchar_t *astring, /* subject string */ size_t nsub, /* number of subexpressions */ int_regwmatch_t *sub, /* subexpression pointers */ int flags) { char *mbs; regmatch_t *mbsub = NULL; int i; struct regcache *rcp; if ((mbs = wcstombsdup((wchar_t *)astring)) == NULL) return (REG_ESPACE); if (nsub > 0 && sub) { if ((mbsub = malloc(nsub * sizeof (regmatch_t))) == NULL) return (REG_ESPACE); } rcp = (struct regcache *)r; i = regexec(&rcp->re, mbs, nsub, mbsub, flags); /* Now, adjust the pointers/counts in sub */ if (i == REG_OK && nsub > 0 && mbsub) { int j, k; for (j = 0; j < nsub; j++) { regmatch_t *ms = &mbsub[j]; int_regwmatch_t *ws = &sub[j]; if ((k = ms->rm_so) >= 0) { ws->rm_so = wcoff(astring, k); ws->rm_sp = astring + ws->rm_so; } if ((k = ms->rm_eo) >= 0) { ws->rm_eo = wcoff(astring, k); ws->rm_ep = astring + ws->rm_eo; } } } free(mbs); if (mbsub) free(mbsub); return (i); } int int_regwdosuba(REGEXP rp, /* compiled RE: Pattern */ const wchar_t *rpl, /* replacement string: /rpl/ */ const wchar_t *src, /* source string */ wchar_t **dstp, /* destination string */ int len, /* destination length */ int *globp) /* IN: occurence, 0 for all; OUT: substitutions */ { wchar_t *dst, *odst; const wchar_t *ip, *xp; wchar_t *op; int i; wchar_t c; int glob, iglob = *globp, oglob = 0; #define NSUB 10 int_regwmatch_t rm[NSUB], *rmp; int flags; wchar_t *end; int regerr; /* handle overflow of dst. we need "i" more bytes */ #ifdef OVERFLOW #undef OVERFLOW #define OVERFLOW(i) { \ int pos = op - dst; \ dst = (wchar_t *)realloc(odst = dst, \ (len += len + i) * sizeof (wchar_t)); \ if (dst == NULL) \ goto nospace; \ op = dst + pos; \ end = dst + len; \ } #endif *dstp = dst = (wchar_t *)malloc(len * sizeof (wchar_t)); if (dst == NULL) return (REG_ESPACE); if (rp == NULL || rpl == NULL || src == NULL || dst == NULL) return (REG_EFATAL); glob = 0; /* match count */ ip = src; /* source position */ op = dst; /* destination position */ end = dst + len; flags = 0; while ((regerr = int_regwexec(rp, ip, NSUB, rm, flags)) == REG_OK) { /* Copy text preceding match */ if (op + (i = rm[0].rm_sp - ip) >= end) OVERFLOW(i) while (i--) *op++ = *ip++; if (iglob == 0 || ++glob == iglob) { oglob++; xp = rpl; /* do substitute */ } else xp = L"&"; /* preserve text */ /* Perform replacement of matched substing */ while ((c = *xp++) != '\0') { rmp = NULL; if (c == '&') rmp = &rm[0]; else if (c == '\\') { if ('0' <= *xp && *xp <= '9') rmp = &rm[*xp++ - '0']; else if (*xp != '\0') c = *xp++; } if (rmp == NULL) { /* Ordinary character. */ *op++ = c; if (op >= end) OVERFLOW(1) } else if (rmp->rm_sp != NULL && rmp->rm_ep != NULL) { ip = rmp->rm_sp; if (op + (i = rmp->rm_ep - rmp->rm_sp) >= end) OVERFLOW(i) while (i--) *op++ = *ip++; } } ip = rm[0].rm_ep; if (*ip == '\0') /* If at end break */ break; else if (rm[0].rm_sp == rm[0].rm_ep) { /* If empty match copy next char */ *op++ = *ip++; if (op >= end) OVERFLOW(1) } flags = REG_NOTBOL; } if (regerr != REG_OK && regerr != REG_NOMATCH) return (regerr); /* Copy rest of text */ if (op + (i = wcslen(ip)) >= end) OVERFLOW(i) while (i--) *op++ = *ip++; *op++ = '\0'; if ((*dstp = dst = (wchar_t *)realloc(odst = dst, sizeof (wchar_t) * (size_t)(op - dst))) == NULL) { nospace: free(odst); return (REG_ESPACE); } *globp = oglob; return ((oglob == 0) ? REG_NOMATCH : REG_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 1986, 1994 by Mortice Kern Systems Inc. All rights reserved. */ /* * awk -- process input files, field extraction, output * * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes */ #include "awk.h" #include "y.tab.h" static FILE *awkinfp; /* Input file pointer */ static int reclen; /* Length of last record */ static int exstat; /* Exit status */ static FILE *openfile(NODE *np, int flag, int fatal); static FILE *newfile(void); static NODE *nextarg(NODE **npp); static void adjust_buf(wchar_t **, int *, wchar_t **, char *, size_t); static void awk_putwc(wchar_t, FILE *); /* * mainline for awk execution */ void awk() { running = 1; dobegin(); while (nextrecord(linebuf, awkinfp) > 0) execute(yytree); doend(exstat); } /* * "cp" is the buffer to fill. There is a special case if this buffer is * "linebuf" ($0) * Return 1 if OK, zero on EOF, -1 on error. */ int nextrecord(wchar_t *cp, FILE *fp) { wchar_t *ep = cp; nextfile: if (fp == FNULL && (fp = newfile()) == FNULL) return (0); if ((*awkrecord)(ep, NLINE, fp) == NULL) { if (fp == awkinfp) { if (fp != stdin) (void) fclose(awkinfp); awkinfp = fp = FNULL; goto nextfile; } if (ferror(fp)) return (-1); return (0); } if (fp == awkinfp) { if (varNR->n_flags & FINT) ++varNR->n_int; else (void) exprreduce(incNR); if (varFNR->n_flags & FINT) ++varFNR->n_int; else (void) exprreduce(incFNR); } if (cp == linebuf) { lbuflen = reclen; splitdone = 0; if (needsplit) fieldsplit(); } /* if record length is too long then bail out */ if (reclen > NLINE - 2) { awkerr(gettext("Record too long (LIMIT: %d bytes)"), NLINE - 1); /* Not Reached */ } return (1); } /* * isclvar() * * Returns 1 if the input string, arg, is a variable assignment, * otherwise returns 0. * * An argument to awk can be either a pathname of a file, or a variable * assignment. An operand that begins with an undersore or alphabetic * character from the portable character set, followed by a sequence of * underscores, digits, and alphabetics from the portable character set, * followed by the '=' character, shall specify a variable assignment * rather than a pathname. */ int isclvar(wchar_t *arg) { wchar_t *tmpptr = arg; if (tmpptr != NULL) { /* Begins with an underscore or alphabetic character */ if (iswalpha(*tmpptr) || *tmpptr == '_') { /* * followed by a sequence of underscores, digits, * and alphabetics */ for (tmpptr++; *tmpptr; tmpptr++) { if (!(iswalnum(*tmpptr) || (*tmpptr == '_'))) { break; } } return (*tmpptr == '='); } } return (0); } /* * Return the next file from the command line. * Return FNULL when no more files. * Sets awkinfp variable to the new current input file. */ static FILE * newfile() { static int argindex = 1; static int filedone; wchar_t *ap; int argc; wchar_t *arg; extern void strescape(wchar_t *); argc = (int)exprint(varARGC); for (;;) { if (argindex >= argc) { if (filedone) return (FNULL); ++filedone; awkinfp = stdin; arg = M_MB_L("-"); break; } constant->n_int = argindex++; arg = (wchar_t *)exprstring(ARGVsubi); /* * If the argument contains a '=', determine if the * argument needs to be treated as a variable assignment * or as the pathname of a file. */ if (((ap = wcschr(arg, '=')) != NULL) && isclvar(arg)) { *ap = '\0'; strescape(ap+1); strassign(vlook(arg), linebuf, FALLOC|FSENSE, wcslen(linebuf)); *ap = '='; continue; } if (arg[0] == '\0') continue; ++filedone; if (arg[0] == '-' && arg[1] == '\0') { awkinfp = stdin; break; } if ((awkinfp = fopen(mbunconvert(arg), r)) == FNULL) { (void) fprintf(stderr, gettext("input file \"%s\""), mbunconvert(arg)); exstat = 1; continue; } break; } strassign(varFILENAME, arg, FALLOC, wcslen(arg)); if (varFNR->n_flags & FINT) varFNR->n_int = 0; else (void) exprreduce(clrFNR); return (awkinfp); } /* * Default record reading code * Uses fgets for potential speedups found in some (e.g. MKS) * stdio packages. */ wchar_t * defrecord(wchar_t *bp, int lim, FILE *fp) { wchar_t *endp; if (fgetws(bp, lim, fp) == NULL) { *bp = '\0'; return (NULL); } /* * XXXX * switch (fgetws(bp, lim, fp)) { * case M_FGETS_EOF: * *bp = '\0'; * return (NULL); * case M_FGETS_BINARY: * awkerr(gettext("file is binary")); * case M_FGETS_LONG: * awkerr(gettext("line too long: limit %d"), * lim); * case M_FGETS_ERROR: * awkperr(gettext("error reading file")); * } */ if (*(endp = (bp + (reclen = wcslen(bp))-1)) == '\n') { *endp = '\0'; reclen--; } return (bp); } /* * Read a record separated by one character in the RS. * Compatible calling sequence with fgets, but don't include * record separator character in string. */ wchar_t * charrecord(wchar_t *abp, int alim, FILE *fp) { wchar_t *bp; wint_t c; int limit = alim; wint_t endc; bp = abp; endc = *(wchar_t *)varRS->n_string; while (--limit > 0 && (c = getwc(fp)) != endc && c != WEOF) *bp++ = c; *bp = '\0'; reclen = bp-abp; return (c == WEOF && bp == abp ? NULL : abp); } /* * Special routine for multiple line records. */ wchar_t * multirecord(wchar_t *abp, int limit, FILE *fp) { wchar_t *bp; int c; while ((c = getwc(fp)) == '\n') ; bp = abp; if (c != WEOF) do { if (--limit == 0) break; if (c == '\n' && bp[-1] == '\n') break; *bp++ = c; } while ((c = getwc(fp)) != WEOF); *bp = '\0'; if (bp > abp) *--bp = '\0'; reclen = bp-abp; return (c == WEOF && bp == abp ? NULL : abp); } /* * Look for fields separated by spaces, tabs or newlines. * Extract the next field, given pointer to start address. * Return pointer to beginning of field or NULL. * Reset end of field reference, which is the beginning of the * next field. */ wchar_t * whitefield(wchar_t **endp) { wchar_t *sp; wchar_t *ep; sp = *endp; while (*sp == ' ' || *sp == '\t' || *sp == '\n') ++sp; if (*sp == '\0') return (NULL); for (ep = sp; *ep != ' ' && *ep != '\0' && *ep != '\t' && *ep != '\n'; ++ep) ; *endp = ep; return (sp); } /* * Look for fields separated by non-whitespace characters. * Same calling sequence as whitefield(). */ wchar_t * blackfield(wchar_t **endp) { wchar_t *cp; int endc; endc = *(wchar_t *)varFS->n_string; cp = *endp; if (*cp == '\0') return (NULL); if (*cp == endc && fcount != 0) cp++; if ((*endp = wcschr(cp, endc)) == NULL) *endp = wcschr(cp, '\0'); return (cp); } /* * This field separation routine uses the same logic as * blackfield but uses a regular expression to separate * the fields. */ wchar_t * refield(wchar_t **endpp) { wchar_t *cp, *start; int flags; static REGWMATCH_T match[10]; int result; cp = *endpp; if (*cp == '\0') { match[0].rm_ep = NULL; return (NULL); } if (match[0].rm_ep != NULL) { flags = REG_NOTBOL; cp = (wchar_t *)match[0].rm_ep; } else flags = 0; start = cp; again: switch ((result = REGWEXEC(resep, cp, 10, match, flags))) { case REG_OK: /* * Check to see if a null string was matched. If this is the * case, then move the current pointer beyond this position. */ if (match[0].rm_sp == match[0].rm_ep) { cp = (wchar_t *)match[0].rm_sp; if (*cp++ != '\0') { goto again; } } *endpp = (wchar_t *)match[0].rm_sp; break; case REG_NOMATCH: match[0].rm_ep = NULL; *endpp = wcschr(cp, '\0'); break; default: (void) REGWERROR(result, resep, (char *)linebuf, sizeof (linebuf)); awkerr(gettext("error splitting record: %s"), (char *)linebuf); } return (start); } /* * do begin processing */ void dobegin() { /* * Free all keyword nodes to save space. */ { NODE *np; int nbuck; NODE *knp; np = NNULL; nbuck = 0; while ((knp = symwalk(&nbuck, &np)) != NNULL) if (knp->n_type == KEYWORD) delsymtab(knp, 1); } /* * Copy ENVIRON array only if needed. * Note the convoluted work to assign to an array * and that the temporary nodes will be freed by * freetemps() because we are "running". */ if (needenviron) { char **app; wchar_t *name, *value; NODE *namep = stringnode(_null, FSTATIC, 0); NODE *valuep = stringnode(_null, FSTATIC, 0); NODE *ENVsubname = node(INDEX, varENVIRON, namep); extern char **environ; /* (void) m_setenv(); XXX what's this do? */ for (app = environ; *app != NULL; /* empty */) { name = mbstowcsdup(*app++); if ((value = wcschr(name, '=')) != NULL) { *value++ = '\0'; valuep->n_strlen = wcslen(value); valuep->n_string = value; } else { valuep->n_strlen = 0; valuep->n_string = _null; } namep->n_strlen = wcslen(namep->n_string = name); (void) assign(ENVsubname, valuep); if (value != NULL) value[-1] = '='; } } phase = BEGIN; execute(yytree); phase = 0; if (npattern == 0) doend(0); /* * Delete all pattern/action rules that are BEGIN at this * point to save space. * NOTE: this is not yet implemented. */ } /* * Do end processing. * Exit with a status */ void doend(int s) { OFILE *op; if (phase != END) { phase = END; awkinfp = stdin; execute(yytree); } for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++) if (op->f_fp != FNULL) awkclose(op); if (awkinfp == stdin) (void) fflush(awkinfp); exit(s); } /* * Print statement. */ void s_print(NODE *np) { FILE *fp; NODE *listp; char *ofs; int notfirst = 0; fp = openfile(np->n_right, 1, 1); if (np->n_left == NNULL) (void) fputs(mbunconvert(linebuf), fp); else { ofs = wcstombsdup((isstring(varOFS->n_flags)) ? (wchar_t *)varOFS->n_string : (wchar_t *)exprstring(varOFS)); listp = np->n_left; while ((np = getlist(&listp)) != NNULL) { if (notfirst++) (void) fputs(ofs, fp); np = exprreduce(np); if (np->n_flags & FINT) (void) fprintf(fp, "%lld", (INT)np->n_int); else if (isstring(np->n_flags)) (void) fprintf(fp, "%S", np->n_string); else (void) fprintf(fp, mbunconvert((wchar_t *)exprstring(varOFMT)), (double)np->n_real); } free(ofs); } (void) fputs(mbunconvert(isstring(varORS->n_flags) ? (wchar_t *)varORS->n_string : (wchar_t *)exprstring(varORS)), fp); if (ferror(fp)) awkperr("error on print"); } /* * printf statement. */ void s_prf(NODE *np) { FILE *fp; fp = openfile(np->n_right, 1, 1); (void) xprintf(np->n_left, fp, (wchar_t **)NULL); if (ferror(fp)) awkperr("error on printf"); } /* * Get next input line. * Read into variable on left of node (or $0 if NULL). * Read from pipe or file on right of node (or from regular * input if NULL). * This is an oddball inasmuch as it is a function * but parses more like the keywords print, etc. */ NODE * f_getline(NODE *np) { wchar_t *cp; INT ret; FILE *fp; size_t len; if (np->n_right == NULL && phase == END) { /* Pretend we've reached end of (the non-existant) file. */ return (intnode(0)); } if ((fp = openfile(np->n_right, 0, 0)) != FNULL) { if (np->n_left == NNULL) { ret = nextrecord(linebuf, fp); } else { cp = emalloc(NLINE * sizeof (wchar_t)); ret = nextrecord(cp, fp); np = np->n_left; len = wcslen(cp); cp = erealloc(cp, (len+1)*sizeof (wchar_t)); if (isleaf(np->n_flags)) { if (np->n_type == PARM) np = np->n_next; strassign(np, cp, FNOALLOC, len); } else (void) assign(np, stringnode(cp, FNOALLOC, len)); } } else ret = -1; return (intnode(ret)); } /* * Open a file. Flag is non-zero for output. */ static FILE * openfile(NODE *np, int flag, int fatal) { OFILE *op; char *cp; FILE *fp; int type; OFILE *fop; if (np == NNULL) { if (flag) return (stdout); if (awkinfp == FNULL) awkinfp = newfile(); return (awkinfp); } if ((type = np->n_type) == APPEND) type = WRITE; cp = mbunconvert(exprstring(np->n_left)); fop = (OFILE *)NULL; for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++) { if (op->f_fp == FNULL) { if (fop == (OFILE *)NULL) fop = op; continue; } if (op->f_mode == type && strcmp(op->f_name, cp) == 0) return (op->f_fp); } if (fop == (OFILE *)NULL) awkerr(gettext("too many open streams to %s onto \"%s\""), flag ? "print/printf" : "getline", cp); (void) fflush(stdout); op = fop; if (cp[0] == '-' && cp[1] == '\0') { fp = flag ? stdout : stdin; } else { switch (np->n_type) { case WRITE: if ((fp = fopen(cp, w)) != FNULL) { if (isatty(fileno(fp))) (void) setvbuf(fp, 0, _IONBF, 0); } break; case APPEND: fp = fopen(cp, "a"); break; case PIPE: fp = popen(cp, w); (void) setvbuf(fp, (char *)0, _IOLBF, 0); break; case PIPESYM: fp = popen(cp, r); break; case LT: fp = fopen(cp, r); break; default: awkerr(interr, "openfile"); } } if (fp != FNULL) { op->f_name = strdup(cp); op->f_fp = fp; op->f_mode = type; } else if (fatal) { awkperr(flag ? gettext("output file \"%s\"") : gettext("input file \"%s\""), cp); } return (fp); } /* * Close a stream. */ void awkclose(OFILE *op) { if (op->f_mode == PIPE || op->f_mode == PIPESYM) (void) pclose(op->f_fp); else if (fclose(op->f_fp) == EOF) awkperr("error on stream \"%s\"", op->f_name); op->f_fp = FNULL; free(op->f_name); op->f_name = NULL; } /* * Internal routine common to printf, sprintf. * The node is that describing the arguments. * Returns the number of characters written to file * pointer `fp' or the length of the string return * in cp. If cp is NULL then the file pointer is used. If * cp points to a string pointer, a pointer to an allocated * buffer will be returned in it. */ size_t xprintf(NODE *np, FILE *fp, wchar_t **cp) { wchar_t *fmt; int c; wchar_t *bptr = (wchar_t *)NULL; char fmtbuf[40]; size_t length = 0; char *ofmtp; NODE *fnp; wchar_t *fmtsave; int slen; int cplen; fnp = getlist(&np); if (isleaf(fnp->n_flags) && fnp->n_type == PARM) fnp = fnp->n_next; if (isstring(fnp->n_flags)) { fmt = fnp->n_string; fmtsave = NULL; } else fmtsave = fmt = (wchar_t *)strsave(exprstring(fnp)); /* * if a char * pointer has been passed in then allocate an initial * buffer for the string. Make it LINE_MAX plus the length of * the format string but do reallocs only based LINE_MAX. */ if (cp != (wchar_t **)NULL) { cplen = LINE_MAX; bptr = *cp = emalloc(sizeof (wchar_t) * (cplen + wcslen(fmt))); } while ((c = *fmt++) != '\0') { if (c != '%') { if (bptr == (wchar_t *)NULL) awk_putwc(c, fp); else *bptr++ = c; ++length; continue; } ofmtp = fmtbuf; *ofmtp++ = (char)c; nextc: switch (c = *fmt++) { case '%': if (bptr == (wchar_t *)NULL) awk_putwc(c, fp); else *bptr++ = c; ++length; continue; case 'c': *ofmtp++ = 'w'; *ofmtp++ = 'c'; *ofmtp = '\0'; fnp = exprreduce(nextarg(&np)); if (isnumber(fnp->n_flags)) c = exprint(fnp); else c = *(wchar_t *)exprstring(fnp); if (bptr == (wchar_t *)NULL) length += fprintf(fp, fmtbuf, c); else { /* * Make sure that the buffer is long * enough to hold the formatted string. */ adjust_buf(cp, &cplen, &bptr, fmtbuf, 0); /* * Since the call to adjust_buf() has already * guaranteed that the buffer will be long * enough, just pass in INT_MAX as * the length. */ (void) wsprintf(bptr, (const char *) fmtbuf, c); bptr += (slen = wcslen(bptr)); length += slen; } continue; /* XXXX Is this bogus? Figure out what s & S mean - look at original code */ case 's': case 'S': *ofmtp++ = 'w'; *ofmtp++ = 's'; *ofmtp = '\0'; if (bptr == (wchar_t *)NULL) length += fprintf(fp, fmtbuf, (wchar_t *)exprstring(nextarg(&np))); else { wchar_t *ts = exprstring(nextarg(&np)); adjust_buf(cp, &cplen, &bptr, fmtbuf, wcslen(ts)); (void) wsprintf(bptr, (const char *) fmtbuf, ts); bptr += (slen = wcslen(bptr)); length += slen; } continue; case 'o': case 'O': case 'X': case 'x': case 'd': case 'i': case 'D': case 'U': case 'u': *ofmtp++ = 'l'; *ofmtp++ = 'l'; /* now dealing with long longs */ *ofmtp++ = c; *ofmtp = '\0'; if (bptr == (wchar_t *)NULL) length += fprintf(fp, fmtbuf, exprint(nextarg(&np))); else { adjust_buf(cp, &cplen, &bptr, fmtbuf, 0); (void) wsprintf(bptr, (const char *) fmtbuf, exprint(nextarg(&np))); bptr += (slen = wcslen(bptr)); length += slen; } continue; case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': *ofmtp++ = c; *ofmtp = '\0'; if (bptr == (wchar_t *)NULL) length += fprintf(fp, fmtbuf, exprreal(nextarg(&np))); else { adjust_buf(cp, &cplen, &bptr, fmtbuf, 0); (void) wsprintf(bptr, (const char *) fmtbuf, exprreal(nextarg(&np))); bptr += (slen = wcslen(bptr)); length += slen; } continue; case 'l': case 'L': break; case '*': #ifdef M_BSD_SPRINTF sprintf(ofmtp, "%lld", (INT)exprint(nextarg(&np))); ofmtp += strlen(ofmtp); #else ofmtp += sprintf(ofmtp, "%lld", (INT)exprint(nextarg(&np))); #endif break; default: if (c == '\0') { *ofmtp = (wchar_t)NULL; (void) fprintf(fp, "%s", fmtbuf); continue; } else { *ofmtp++ = (wchar_t)c; break; } } goto nextc; } if (fmtsave != NULL) free(fmtsave); /* * If printing to a character buffer then make sure it is * null-terminated and only uses as much space as required. */ if (bptr != (wchar_t *)NULL) { *bptr = '\0'; *cp = erealloc(*cp, (length+1) * sizeof (wchar_t)); } return (length); } /* * Return the next argument from the list. */ static NODE * nextarg(NODE **npp) { NODE *np; if ((np = getlist(npp)) == NNULL) awkerr(gettext("insufficient arguments to printf or sprintf")); if (isleaf(np->n_flags) && np->n_type == PARM) return (np->n_next); return (np); } /* * Check and adjust the length of the buffer that has been passed in * to make sure that it has space to accomodate the sequence string * described in fmtstr. This routine is used by xprintf() to allow * for arbitrarily long sprintf() strings. * * bp = start of current buffer * len = length of current buffer * offset = offset in current buffer * fmtstr = format string to check * slen = size of string for %s formats */ static void adjust_buf(wchar_t **bp, int *len, wchar_t **offset, char *fmtstr, size_t slen) { int ioff; int width = 0; int prec = 0; do { fmtstr++; } while (strchr("-+ 0", *fmtstr) != (char *)0 || *fmtstr == ('#')); if (*fmtstr != '*') { if (isdigit(*fmtstr)) { width = *fmtstr-'0'; while (isdigit(*++fmtstr)) width = width * 10 + *fmtstr - '0'; } } else fmtstr++; if (*fmtstr == '.') { if (*++fmtstr != '*') { prec = *fmtstr-'0'; while (isdigit(*++fmtstr)) prec = prec * 10 + *fmtstr - '0'; } else fmtstr++; } if (strchr("Llh", *fmtstr) != (char *)0) fmtstr++; if (*fmtstr == 'S') { if (width && slen < width) slen = width; if (prec && slen > prec) slen = prec; width = slen+1; } else if (width == 0) width = NUMSIZE; if (*offset+ width > *bp+ *len) { ioff = *offset-*bp; *len += width+1; *bp = erealloc(*bp, *len * sizeof (wchar_t)); *offset = *bp+ioff; } } static void awk_putwc(wchar_t c, FILE *fp) { char mb[MB_LEN_MAX]; size_t mbl; if ((mbl = wctomb(mb, c)) > 0) { mb[mbl] = '\0'; (void) fputs(mb, fp); } else awkerr(gettext("invalid wide character %x"), c); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * awk -- executor * * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 1985, 1994 by Mortice Kern Systems Inc. All rights reserved. * * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes */ #include "awk.h" #include "y.tab.h" static int dohash(wchar_t *name); static NODE *arithmetic(NODE *np); static NODE *comparison(NODE *np); static int type_of(NODE *np); static NODE *lfield(INT fieldno, NODE *value); static NODE *rfield(INT fieldno); static NODE *userfunc(NODE *np); static wchar_t *lltoa(long long l); static NODE *exprconcat(NODE *np, int len); static int s_if(NODE *np); static int s_while(NODE *np); static int s_for(NODE *np); static int s_forin(NODE *np); static void setrefield(NODE *value); static void freetemps(void); static int action(NODE *np); static wchar_t *makeindex(NODE *np, wchar_t *array, int tag); static int exprtest(NODE *np); #define regmatch(rp, s) REGWEXEC(rp, s, 0, (REGWMATCH_T*)NULL, 0) /* * This code allows for integers to be stored in longs (type INT) and * only promoted to double precision floating point numbers (type REAL) * when overflow occurs during +, -, or * operations. This is very * non-portable if you desire such a speed optimisation. You may wish * to put something here for your system. This "something" would likely * include either an assembler "jump on overflow" instruction or a * method to get traps on overflows from the hardware. * * This portable method works for ones and twos complement integer * representations (which is, realistically) almost all machines. */ #if __TURBOC__ #define addoverflow() asm jo overflow #define suboverflow() asm jo overflow #else /* * These are portable to two's complement integer machines */ #define addoverflow() if ((i1^i2) >= 0 && (iresult^i1) < 0) goto overflow #define suboverflow() if ((i1^i2) < 0 && (iresult^i2) >= 0) goto overflow #endif #define muloverflow() if (((short)i1 != i1 || (short)i2 != i2) && \ ((i2 != 0 && iresult/i2 != i1) || \ (i1 == LONG_MIN && i2 == -1))) goto overflow static char notarray[] = "scalar \"%s\" cannot be used as array"; static char badarray[] = "array \"%s\" cannot be used as a scalar"; static char varnotfunc[] = "variable \"%s\" cannot be used as a function"; static char tmfld[] = "Too many fields (LIMIT: %d)"; static char toolong[] = "Record too long (LIMIT: %d bytes)"; static char divzero[] = "division (/ or %%) by zero"; static char toodeep[] = "too deeply nested for in loop (LIMIT: %d)"; static wchar_t numbuf[NUMSIZE]; /* Used to convert INTs to strings */ static wchar_t *fields[NFIELD]; /* Cache of pointers into fieldbuf */ static wchar_t *fieldbuf; /* '\0' separated copy of linebuf */ static NODE nodes[NSNODE]; /* Cache of quick access nodes */ static NODE *fnodep = &nodes[0]; #define NINDEXBUF 50 static wchar_t indexbuf[NINDEXBUF]; /* Used for simple array indices */ static int concflag; /* In CONCAT operation (no frees) */ static NODE *retval; /* Last return value of a function */ /* * The following stack is used to store the next pointers for all nested * for-in loops. This needs to be global so that delete can check to see * if it is deleting the next node to be used by a loop. */ #define NFORINLOOP 10 static NODE* forindex[NFORINLOOP]; static NODE** next_forin = forindex; /* * Assign a string directly to a NODE without creating an intermediate * NODE. This can handle either FALLOC, FSTATIC, FNOALLOC or FSENSE for * "flags" argument. Also the NODE "np" must be reduced to an lvalue * (PARM nodes are not acceptable). */ void strassign(NODE *np, STRING string, int flags, size_t length) { if (np->n_type == FUNC) awkerr(gettext("attempt to redefine builtin function")); else if (np->n_type == GETLINE || np->n_type == KEYWORD) awkerr(gettext("inadmissible use of reserved keyword")); if (np->n_flags & FSPECIAL) { (void) nassign(np, stringnode(string, flags, length)); return; } if (isastring(np->n_flags)) free((wchar_t *)np->n_string); np->n_strlen = length++; if (flags & FALLOC) { length *= sizeof (wchar_t); np->n_string = (STRING) emalloc(length); (void) memcpy((void *)np->n_string, string, length); } else { np->n_string = string; if (flags & FNOALLOC) { flags &= ~FNOALLOC; flags |= FALLOC; } } np->n_flags &= FSAVE; if (flags & FSENSE) { flags &= ~FSENSE; flags |= type_of(np); } else flags |= FSTRING; np->n_flags |= flags; } /* * Assign to a variable node. * LHS must be a VAR type and RHS must be reduced by now. * To speed certain operations up, check for * certain things here and do special assignments. */ NODE * nassign(NODE *np, NODE *value) { register wchar_t *cp; register int len; /* short circuit assignment of a node to itself */ if (np == value) return (np); if (np->n_flags & FSPECIAL) { if (np == varRS || np == varFS) { if (isastring(np->n_flags)) free((void *)np->n_string); len = sizeof (wchar_t) * ((np->n_strlen = wcslen(cp = exprstring(value)))+1); np->n_string = emalloc(len); (void) memcpy((wchar_t *)np->n_string, cp, len); np->n_flags = FALLOC|FSTRING|FSPECIAL; if (np == varRS) { if (np->n_string[0] == '\n') awkrecord = defrecord; else if (np->n_string[0] == '\0') awkrecord = multirecord; else awkrecord = charrecord; } else if (np == varFS) { if (resep != (REGEXP)NULL) { REGWFREE(resep); resep = (REGEXP)NULL; } if (wcslen((wchar_t *)np->n_string) > 1) setrefield(np); else if (np->n_string[0] == ' ') awkfield = whitefield; else awkfield = blackfield; } return (np); } } if (isastring(np->n_flags)) free((wchar_t *)np->n_string); if (isstring(value->n_flags)) { np->n_strlen = value->n_strlen; if (value->n_flags&FALLOC || value->n_string != _null) { len = (np->n_strlen+1) * sizeof (wchar_t); np->n_string = emalloc(len); (void) memcpy(np->n_string, value->n_string, len); np->n_flags &= FSAVE; np->n_flags |= value->n_flags & ~FSAVE; np->n_flags |= FALLOC; return (np); } else np->n_string = value->n_string; } else if (value->n_flags & FINT) np->n_int = value->n_int; else np->n_real = value->n_real; np->n_flags &= FSAVE; np->n_flags |= value->n_flags & ~FSAVE; return (np); } /* * Set regular expression FS value. */ static void setrefield(NODE *np) { static REGEXP re; int n; if ((n = REGWCOMP(&re, np->n_string)) != REG_OK) { REGWERROR(n, &re, (char *)linebuf, sizeof (linebuf)); awkerr(gettext("syntax error \"%s\" in /%s/\n"), (char *)linebuf, np->n_string); } resep = re; awkfield = refield; } /* * Assign to an l-value node. */ NODE * assign(NODE *left, NODE *right) { if (isleaf(right->n_flags)) { if (right->n_type == PARM) right = right->n_next; } else right = exprreduce(right); top: switch (left->n_type) { case INDEX: left = exprreduce(left); /* FALLTHROUGH */ case VAR: return (nassign(left, right)); case PARM: /* * If it's a parameter then link to the actual value node and * do the checks again. */ left = left->n_next; goto top; case FIELD: return (lfield(exprint(left->n_left), right)); case CALLUFUNC: case UFUNC: awkerr(gettext("cannot assign to function \"%s\""), left->n_name); default: awkerr(gettext("lvalue required in assignment")); } /* NOTREACHED */ return (0); } /* * Compiled tree non-terminal node. */ NODE * node(int type, NODE *left, NODE *right) { register NODE *np; np = emptynode(type, 0); np->n_left = left; np->n_right = right; np->n_lineno = lineno; return (np); } /* * Create an integer node. */ NODE * intnode(INT i) { register NODE *np; np = emptynode(CONSTANT, 0); np->n_flags = FINT|FVINT; np->n_int = i; return (np); } /* * Create a real number node. */ NODE * realnode(REAL real) { register NODE *np; np = emptynode(CONSTANT, 0); np->n_flags = FREAL|FVREAL; np->n_real = real; return (np); } /* * Make a node for a string. */ NODE * stringnode(STRING s, int how, size_t length) { register NODE *np; np = emptynode(CONSTANT, 0); np->n_strlen = length; if (how & FALLOC) { np->n_string = emalloc(length = (length+1) * sizeof (wchar_t)); (void) memcpy(np->n_string, s, length); } else { np->n_string = s; if (how & FNOALLOC) { how &= ~FNOALLOC; how |= FALLOC; } } if (how & FSENSE) { np->n_flags = type_of(np); how &= ~FSENSE; } else np->n_flags = FSTRING; np->n_flags |= how; return (np); } /* * Save a copy of a string. */ STRING strsave(wchar_t *old) { STRING new; register size_t len; new = (STRING)emalloc(len = (wcslen(old)+1) * sizeof (wchar_t)); (void) memcpy(new, old, len); return (new); } /* * Allocate an empty node of given type. * String space for the node is given by `length'. */ NODE * emptynode(int type, size_t length) { register NODE *np; if (length == 0 && running && fnodep < &nodes[NSNODE]) { np = fnodep++; } else { np = (NODE *)emalloc(sizeof (NODE) + (length * sizeof (wchar_t))); if (running && type != VAR && type != ARRAY) { np->n_next = freelist; freelist = np; } } np->n_flags = FNONTOK; np->n_type = type; np->n_alink = NNULL; return (np); } /* * Free a node. */ void freenode(NODE *np) { if (isastring(np->n_flags)) free((wchar_t *)np->n_string); else if (np->n_type == RE) { REGWFREE(np->n_regexp); } free((wchar_t *)np); } /* * Install a keyword of given `type'. */ void kinstall(LOCCHARP name, int type) { register NODE *np; register size_t l; l = wcslen(name); np = emptynode(KEYWORD, l); np->n_keywtype = type; (void) memcpy(np->n_name, name, (l+1) * sizeof (wchar_t)); addsymtab(np); } /* * Install built-in function. */ NODE * finstall(LOCCHARP name, FUNCTION func, int type) { register NODE *np; register size_t l; l = wcslen(name); np = emptynode(type, l); np->n_function = func; (void) memcpy(np->n_name, name, (l+1) * sizeof (wchar_t)); addsymtab(np); return (np); } /* * Lookup an identifier. * nocreate contains the following flag values: * 1 if no creation of a new NODE, * 0 if ok to create new NODE */ NODE * vlookup(wchar_t *name, int nocreate) { register ushort_t hash; register NODE *np; np = symtab[hashbuck(hash = dohash((wchar_t *)name))]; while (np != NNULL) { if (np->n_hash == hash && wcscmp(name, np->n_name) == 0) return (np); np = np->n_next; } if (nocreate) { np = NNULL; } else { np = emptynode(VAR, hash = wcslen(name)); np->n_flags = FSTRING|FVINT; np->n_strlen = 0; np->n_string = _null; (void) memcpy(np->n_name, name, (hash+1) * sizeof (wchar_t)); addsymtab(np); } return (np); } /* * Add a symbol to the table. */ void addsymtab(NODE *np) { register NODE **spp; np->n_hash = dohash((wchar_t *)np->n_name); spp = &symtab[hashbuck(np->n_hash)]; np->n_next = *spp; *spp = np; } /* * Delete the given node from the symbol table. * If fflag is non-zero, also free the node space. * This routine must also check the stack of forin loop pointers. If * we are deleting the next item to be used, then the pointer must be * advanced. */ void delsymtab(NODE *np, int fflag) { register NODE *rnp; register NODE *prevp; register NODE **sptr; register ushort_t h; h = hashbuck(np->n_hash); prevp = NNULL; for (rnp = symtab[h]; rnp != NNULL; rnp = rnp->n_next) { if (rnp == np) { /* * check all of the for-in loop pointers * to see if any need to be advanced because * this element is being deleted. */ if (next_forin != forindex) { sptr = next_forin; do { if (*--sptr == rnp) { *sptr = rnp->n_next; break; } } while (sptr != forindex); } if (prevp == NNULL) symtab[h] = rnp->n_next; else prevp->n_next = rnp->n_next; if (fflag) freenode(rnp); break; } prevp = rnp; } } /* * Hashing function. */ static int dohash(wchar_t *name) { register int hash = 0; while (*name != '\0') hash += *name++; return (hash); } /* * Top level executor for an awk programme. * This will be passed: pattern, action or a list of these. * The former function to evaluate a pattern has been * subsumed into this function for speed. * Patterns are: * BEGIN, * END, * other expressions (including regular expressions) */ void execute(NODE *wp) { register NODE *np; register int type; register NODE *tnp; curnode = wp; if (phase != 0) { linebuf[0] = '\0'; lbuflen = 0; } while (wp != NNULL) { if (wp->n_type == COMMA) { np = wp->n_left; wp = wp->n_right; } else { np = wp; wp = NNULL; } if (np->n_type != PACT) awkerr(interr, "PACT"); /* * Save the parent node and evaluate the pattern. * If it evaluates to false (0) just continue * to the next pattern/action (PACT) pair. */ tnp = np; np = np->n_left; if (np == NNULL) { if (phase != 0) continue; } else if (phase != 0) { if (np->n_type != phase) continue; } else if ((type = np->n_type) == BEGIN || type == END) { continue; } else if (type == COMMA) { /* * The grammar only allows expressions * to be separated by the ',' operator * for range patterns. */ if (np->n_flags & FMATCH) { if (exprint(np->n_right) != 0) np->n_flags &= ~FMATCH; } else if (exprint(np->n_left) != 0) { if (exprint(np->n_right) == 0) np->n_flags |= FMATCH; } else continue; } else if (exprint(np) == 0) continue; np = tnp; if (action(np->n_right)) { loopexit = 0; break; } } if (freelist != NNULL) freetemps(); } /* * Free all temporary nodes. */ static void freetemps() { register NODE *np, *nnp; if (concflag) return; for (np = &nodes[0]; np < fnodep; np++) { if (isastring(np->n_flags)) { free((wchar_t *)np->n_string); } else if (np->n_type == RE) { REGWFREE(np->n_regexp); } } fnodep = &nodes[0]; for (np = freelist; np != NNULL; np = nnp) { nnp = np->n_next; freenode(np); } freelist = NNULL; } /* * Do the given action. * Actions are statements or expressions. */ static int action(NODE *wp) { register NODE *np; register int act = 0; register NODE *l; while (wp != NNULL) { if (wp->n_type == COMMA) { np = wp->n_left; wp = wp->n_right; } else { np = wp; wp = NNULL; } if (freelist != NNULL) freetemps(); curnode = np; /* * Don't change order of these cases without * changing order in awk.y declarations. * The order is optimised. */ switch (np->n_type) { case ASG: (void) assign(np->n_left, np->n_right); continue; case PRINT: s_print(np); continue; case PRINTF: s_prf(np); continue; case EXIT: if (np->n_left != NNULL) act = (int)exprint(np->n_left); else act = 0; doend(act); /* NOTREACHED */ case RETURN: if (slevel == 0) awkerr(gettext("return outside of a function")); np = np->n_left != NNULL ? exprreduce(np->n_left) : const0; retval = emptynode(CONSTANT, 0); retval->n_flags = FINT; (void) nassign(retval, np); return (RETURN); case NEXT: loopexit = NEXT; /* FALLTHROUGH */ case BREAK: case CONTINUE: return (np->n_type); case DELETE: if ((l = np->n_left)->n_type == PARM) { l = l->n_next; if (!(l->n_flags & FLARRAY)) l = l->n_alink; } switch (l->n_type) { case ARRAY: delarray(l); break; case INDEX: if ((np = l->n_left)->n_type == PARM) { np = np->n_next; if (!(np->n_flags & FLARRAY)) np = np->n_alink; } /* * get pointer to the node for this array * element using the hash key. */ l = exprreduce(l); /* * now search linearly from the beginning of * the list to find the element before the * one being deleted. This must be done * because arrays are singley-linked. */ while (np != NNULL) { if (np->n_alink == l) { np->n_alink = l->n_alink; break; } np = np->n_alink; } delsymtab(l, 1); break; case VAR: if (isstring(l->n_flags) && l->n_string == _null) break; /* FALLTHROUGH */ default: awkerr(gettext( "may delete only array element or array")); break; } continue; case WHILE: case DO: if ((act = s_while(np)) != 0) break; continue; case FOR: if ((act = s_for(np)) != 0) break; continue; case FORIN: if ((act = s_forin(np)) != 0) break; continue; case IF: if ((act = s_if(np)) != 0) break; continue; default: (void) exprreduce(np); if (loopexit != 0) { act = loopexit; break; } continue; } return (act); } return (0); } /* * Delete an entire array */ void delarray(NODE *np) { register NODE *nnp; nnp = np->n_alink; np->n_alink = NNULL; while (nnp != NNULL) { np = nnp->n_alink; delsymtab(nnp, 1); nnp = np; } } /* * Return the INT value of an expression. */ INT exprint(NODE *np) { if (isleaf(np->n_flags)) { if (np->n_type == PARM) np = np->n_next; goto leaf; } np = exprreduce(np); switch (np->n_type) { case CONSTANT: case VAR: leaf: if (np->n_flags & FINT) return (np->n_int); if (np->n_flags & FREAL) return ((INT)np->n_real); return ((INT)wcstoll(np->n_string, NULL, 10)); default: awkerr(interr, "exprint"); } /* NOTREACHED */ return (0); } /* * Return a real number from an expression tree. */ REAL exprreal(NODE *np) { if (loopexit) return ((REAL)loopexit); if (isleaf(np->n_flags)) { if (np->n_type == PARM) np = np->n_next; goto leaf; } np = exprreduce(np); switch (np->n_type) { case CONSTANT: case VAR: leaf: if (np->n_flags & FREAL) return (np->n_real); if (np->n_flags & FINT) return ((REAL)np->n_int); return ((REAL)wcstod((wchar_t *)np->n_string, (wchar_t **)0)); default: awkerr(interr, "exprreal"); } /* NOTREACHED */ return ((REAL)0); } /* * Return a string from an expression tree. */ STRING exprstring(NODE *np) { if (isleaf(np->n_flags)) { if (np->n_type == PARM) np = np->n_next; goto leaf; } np = exprreduce(np); switch (np->n_type) { case CONSTANT: case VAR: leaf: if (isstring(np->n_flags)) return (np->n_string); if (np->n_flags & FINT) return (STRING)lltoa((long long)np->n_int); { char *tmp; (void) wsprintf(numbuf, (const char *) (tmp = wcstombsdup(exprstring(varCONVFMT))), (double)np->n_real); if (tmp != NULL) free(tmp); } return ((STRING)numbuf); default: awkerr(interr, "exprstring"); } /* NOTREACHED */ return (0); } /* * Convert number to string. */ static wchar_t * lltoa(long long l) { register wchar_t *p = &numbuf[NUMSIZE]; register int s; register int neg; static wchar_t zero[] = M_MB_L("0"); if (l == 0) return (zero); *--p = '\0'; if (l < 0) neg = 1, l = -l; else neg = 0; if ((s = (short)l) == l) { while (s != 0) { *--p = s%10 + '0'; s /= 10; } } else { while (l != 0) { *--p = l%10 + '0'; l /= 10; } } if (neg) *--p = '-'; return (wcscpy(numbuf, p)); } /* * Return pointer to node with concatenation of operands of CONCAT node. * In the interest of speed, a left recursive tree of CONCAT nodes * is handled with a single malloc. The accumulated lengths of the * right operands are passed down recursive invocations of this * routine, which allocates a large enough string when the left * operand is not a CONCAT node. */ static NODE * exprconcat(NODE *np, int len) { /* we KNOW (np->n_type==CONCAT) */ register NODE *lnp = np->n_left; register NODE *rnp = np->n_right; register STRING rsp; int rlen; size_t llen; wchar_t *cp; wchar_t rnumbuf[NUMSIZE]; if (isleaf(rnp->n_flags) && rnp->n_type == PARM) rnp = rnp->n_next; if (isstring(rnp->n_flags)) { rsp = rnp->n_string; rlen = rnp->n_strlen; } else rlen = wcslen((wchar_t *)(rsp = exprstring(rnp))); if (rsp == numbuf) { /* static, so save a copy */ (void) memcpy(rnumbuf, (wchar_t *)rsp, (rlen+1) * sizeof (wchar_t)); rsp = rnumbuf; } len += rlen; if (lnp->n_type == CONCAT) { lnp = exprconcat(lnp, len); cp = lnp->n_string; llen = lnp->n_strlen; } else { register STRING lsp; if (isleaf(lnp->n_flags) && lnp->n_type == PARM) lnp = lnp->n_next; if (isstring(lnp->n_flags)) { lsp = lnp->n_string; llen = lnp->n_strlen; } else llen = wcslen((wchar_t *)(lsp = exprstring(lnp))); cp = emalloc((llen+len+1) * sizeof (wchar_t)); (void) memcpy(cp, (wchar_t *)lsp, llen * sizeof (wchar_t)); lnp = stringnode(cp, FNOALLOC, llen); } (void) memcpy(cp+llen, (wchar_t *)rsp, (rlen+1) * sizeof (wchar_t)); lnp->n_strlen += rlen; return (lnp); } /* * Reduce an expression to a terminal node. */ NODE * exprreduce(NODE *np) { register wchar_t *cp; NODE *tnp; register int temp; register int t; register int tag; register wchar_t *fname; register wchar_t *aname; /* * a var or constant is a leaf-node (no further reduction required) * so return immediately. */ if ((t = np->n_type) == VAR || t == CONSTANT) return (np); /* * If it's a parameter then it is probably a leaf node but it * might be an array so we check.. If it is an array, then signal * an error as an array by itself cannot be used in this context. */ if (t == PARM) if ((np = np->n_next)->n_type == ARRAY) awkerr(badarray, np->n_name); else return (np); /* * All the rest are non-leaf nodes. */ curnode = np; switch (t) { case CALLUFUNC: return (userfunc(np)); case FIELD: return (rfield(exprint(np->n_left))); case IN: case INDEX: tag = 0; temp = np->n_type; tnp = np->n_left; np = np->n_right; /* initially formal var name and array key name are the same */ fname = aname = tnp->n_name; if (tnp->n_type == PARM) { tnp = tnp->n_next; tag = tnp->n_scope; if (!(tnp->n_flags & FLARRAY)) { tnp = tnp->n_alink; } aname = tnp->n_name; } if (tnp->n_type != ARRAY) { if (!isstring(tnp->n_flags) || tnp->n_string != _null) awkerr(notarray, fname); else { /* promotion to array */ promote(tnp); if (tnp->n_alink != NNULL) { tag = tnp->n_scope; if (!(tnp->n_flags & FLARRAY)) tnp = tnp->n_alink; aname = tnp->n_name; } else { tag = 0; if (tnp->n_flags & FLARRAY) tag = tnp->n_scope; } } } if (tnp == varSYMTAB) { if (np == NNULL || np->n_type == COMMA) awkerr(gettext( "SYMTAB must have exactly one index")); np = vlook(exprstring(np)); return (np); } cp = makeindex(np, aname, tag); if (temp == INDEX) { np = vlook(cp); if (!(np->n_flags & FINARRAY)) { np->n_alink = tnp->n_alink; tnp->n_alink = np; np->n_flags |= FINARRAY; } } else np = vlookup(cp, 1) == NNULL ? const0 : const1; if (cp != indexbuf) free(cp); return (np); case CONCAT: ++concflag; np = exprconcat(np, 0); --concflag; return (np); case NOT: return (intnode(exprtest(np->n_left) == 0 ? (INT)1 : (INT)0)); case AND: return ((exprtest(np->n_left) != 0 && exprtest(np->n_right) != 0) ? const1 : const0); case OR: return ((exprtest(np->n_left) != 0 || exprtest(np->n_right) != 0) ? const1 : const0); case EXP: { double f1, f2; /* * evaluate expressions in proper order before * calling pow(). * Can't guarantee that compiler will do this * correctly for us if we put them inline. */ f1 = (double)exprreal(np->n_left); f2 = (double)exprreal(np->n_right); return (realnode((REAL)pow(f1, f2))); } case QUEST: if (np->n_right->n_type != COLON) awkerr(interr, "?:"); if (exprtest(np->n_left)) np = np->n_right->n_left; else np = np->n_right->n_right; return (exprreduce(np)); case EQ: case NE: case GE: case LE: case GT: case LT: return (comparison(np)); case ADD: case SUB: case MUL: case DIV: case REM: return (arithmetic(np)); case DEC: inc_oper->n_type = SUB; goto do_inc_op; case INC: inc_oper->n_type = ADD; do_inc_op: if ((np = np->n_left)->n_type == INDEX) np = exprreduce(np); if (np->n_flags & FREAL) tnp = realnode(np->n_real); else tnp = intnode(exprint(np)); inc_oper->n_left = np; (void) assign(np, inc_oper); return (tnp); case PRE_DEC: inc_oper->n_type = SUB; goto do_pinc_op; case PRE_INC: inc_oper->n_type = ADD; do_pinc_op: if ((np = np->n_left)->n_type == INDEX) np = exprreduce(np); inc_oper->n_left = np; return (assign(np, inc_oper)); case AADD: asn_oper->n_type = ADD; goto do_asn_op; case ASUB: asn_oper->n_type = SUB; goto do_asn_op; case AMUL: asn_oper->n_type = MUL; goto do_asn_op; case ADIV: asn_oper->n_type = DIV; goto do_asn_op; case AREM: asn_oper->n_type = REM; goto do_asn_op; case AEXP: asn_oper->n_type = EXP; do_asn_op: asn_oper->n_right = np->n_right; if ((np = np->n_left)->n_type == INDEX) np = exprreduce(np); asn_oper->n_left = np; return (assign(np, asn_oper)); case GETLINE: return (f_getline(np)); case CALLFUNC: return ((*np->n_left->n_function)(np->n_right)); case RE: if (regmatch(np->n_regexp, linebuf) == REG_OK) return (const1); return (const0); case TILDE: cp = exprstring(np->n_left); if (regmatch(getregexp(np->n_right), cp) == REG_OK) return (const1); return (const0); case NRE: cp = exprstring(np->n_left); if (regmatch(getregexp(np->n_right), cp) != REG_OK) return (const1); return (const0); case ASG: return (assign(np->n_left, np->n_right)); case ARRAY: awkerr(badarray, np->n_name); case UFUNC: awkerr(varnotfunc, np->n_name); default: awkerr(gettext("panic: exprreduce(%d)"), t); /* NOTREACHED */ } return (0); } /* * Do arithmetic operators. */ static NODE * arithmetic(NODE *np) { register NODE *left, *right; int type; register INT i1, i2; register INT iresult; register REAL r1, r2; left = exprreduce(np->n_left); if (isreal(left->n_flags) || (isstring(left->n_flags) && (type_of(left)&FVREAL))) { type = FREAL; r1 = exprreal(left); r2 = exprreal(np->n_right); } else { i1 = exprint(left); right = exprreduce(np->n_right); if (isreal(right->n_flags) || (isstring(right->n_flags) && (type_of(right)&FVREAL))) { type = FREAL; r1 = i1; r2 = exprreal(right); } else { type = FINT; i2 = exprint(right); } } reswitch: switch (np->n_type) { case ADD: if (type == FINT) { iresult = i1 + i2; addoverflow(); } else r1 += r2; break; /* * Strategically placed between ADD and SUB * so "jo" branches will reach on 80*86 */ overflow: r1 = i1; r2 = i2; type = FREAL; goto reswitch; case SUB: if (type == FINT) { iresult = i1 - i2; suboverflow(); } else r1 -= r2; break; case MUL: if (type == FINT) { iresult = i1 * i2; muloverflow(); } else r1 *= r2; break; case DIV: if (type == FINT) { r1 = i1; r2 = i2; type = FREAL; } if (r2 == 0.0) awkerr(divzero); r1 /= r2; break; case REM: if (type == FINT) { if (i2 == 0) awkerr(divzero); iresult = i1 % i2; } else { double fmod(double x, double y); errno = 0; r1 = fmod(r1, r2); if (errno == EDOM) awkerr(divzero); } break; } return (type == FINT ? intnode(iresult) : realnode(r1)); } /* * Do comparison operators. */ static NODE * comparison(NODE *np) { register NODE *left, *right; register int cmp; int tl, tr; register REAL r1, r2; register INT i1, i2; left = np->n_left; if (isleaf(left->n_flags)) { if (left->n_type == PARM) left = left->n_next; } else left = exprreduce(left); tl = left->n_flags; right = np->n_right; if (isleaf(right->n_flags)) { if (right->n_type == PARM) right = right->n_next; } else { ++concflag; right = exprreduce(right); --concflag; } tr = right->n_flags; /* * Posix mandates semantics for the comparison operators that * are incompatible with traditional AWK behaviour. If the following * define is true then awk will use the traditional behaviour. * if it's false, then AWK will use the POSIX-mandated behaviour. */ #define TRADITIONAL 0 #if TRADITIONAL if (!isnumber(tl) || !isnumber(tr)) { cmp = wcscoll((wchar_t *)exprstring(left), (wchar_t *)exprstring(right)); } else if (isreal(tl) || isreal(tr)) { r1 = exprreal(left); r2 = exprreal(right); if (r1 < r2) cmp = -1; else if (r1 > r2) cmp = 1; else cmp = 0; } else { i1 = exprint(left); i2 = exprint(right); if (i1 < i2) cmp = -1; else if (i1 > i2) cmp = 1; else cmp = 0; } #else if (!isnumber(tl) && !isnumber(tr)) { do_strcmp: cmp = wcscoll((wchar_t *)exprstring(left), (wchar_t *)exprstring(right)); } else { if (isstring(tl)) tl = type_of(left); if (isstring(tr)) tr = type_of(right); if (!isnumber(tl) || !isnumber(tr)) goto do_strcmp; if (isreal(tl) || isreal(tr)) { r1 = exprreal(left); r2 = exprreal(right); if (r1 < r2) cmp = -1; else if (r1 > r2) cmp = 1; else cmp = 0; } else { i1 = exprint(left); i2 = exprint(right); if (i1 < i2) cmp = -1; else if (i1 > i2) cmp = 1; else cmp = 0; } } #endif switch (np->n_type) { case EQ: return (cmp == 0 ? const1 : const0); case NE: return (cmp != 0 ? const1 : const0); case GE: return (cmp >= 0 ? const1 : const0); case LE: return (cmp <= 0 ? const1 : const0); case GT: return (cmp > 0 ? const1 : const0); case LT: return (cmp < 0 ? const1 : const0); default: awkerr(interr, "comparison"); } /* NOTREACHED */ return (0); } /* * Return the type of a constant that is a string. * The node must be a FSTRING type and the return value * will possibly have FVINT or FVREAL or'ed in. */ static int type_of(NODE *np) { wchar_t *cp; int somedigits = 0; int seene = 0; int seenradix = 0; int seensign = 0; int digitsaftere = 0; cp = (wchar_t *)np->n_string; if (*cp == '\0') return (FSTRING|FVINT); while (iswspace(*cp)) cp++; if (*cp == '-' || *cp == '+') cp++; while (*cp != '\0') { switch (*cp) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (seene) digitsaftere = 1; somedigits++; break; case 'E': case 'e': if (seene || !somedigits) return (FSTRING); seene = 1; break; case '+': case '-': if (seensign || !seene || digitsaftere) return (FSTRING); seensign = 1; break; default: if (*cp == radixpoint) { if (seenradix || seene || (!somedigits && !iswdigit(*++cp))) return (FSTRING); } else return (FSTRING); seenradix = 1; } cp++; } if (somedigits == 0) return (FSTRING); if (somedigits >= MAXDIGINT || seenradix || seene) { if (seensign && !digitsaftere) return (FSTRING); else return (FSTRING|FVREAL); } else return (FSTRING|FVINT); } /* * Return a field rvalue. */ static NODE * rfield(INT fieldno) { register wchar_t *cp; if (fieldno == 0) return (stringnode(linebuf, FSTATIC|FSENSE, lbuflen)); if (!splitdone) fieldsplit(); if (fieldno > nfield || fieldno < 0) return (stringnode(_null, FSTATIC, 0)); cp = fields[fieldno-1]; return (stringnode(cp, FSTATIC|FSENSE, wcslen(cp))); } /* * Split linebuf into fields. Done only once * per input record (maximum). */ void fieldsplit() { register wchar_t *ip, *op; register int n; wchar_t *ep; if (fieldbuf == NULL) fieldbuf = emalloc(NLINE * sizeof (wchar_t)); fcount = 0; ep = linebuf; op = fieldbuf; while ((ip = (*awkfield)(&ep)) != NULL) { fields[fcount++] = op; if (fcount > NFIELD) awkerr(tmfld, NFIELD); n = ep-ip; (void) memcpy(op, ip, n * sizeof (wchar_t)); op += n; *op++ = '\0'; } if (varNF->n_flags & FINT) varNF->n_int = fcount; else { constant->n_int = fcount; (void) nassign(varNF, constant); } nfield = fcount; splitdone++; } /* * Assign to a field as an lvalue. * Return the unevaluated node as one doesn't always need it * evaluated in an assignment. */ static NODE * lfield(INT fieldno, NODE *np) { register wchar_t *cp; register wchar_t *op; register wchar_t *sep; register int i; register wchar_t *newval; register int seplen; register int newlen; newlen = wcslen(newval = (wchar_t *)exprstring(np)); if (fieldno == 0) { splitdone = 0; (void) memcpy(linebuf, newval, (newlen+1) * sizeof (wchar_t)); lbuflen = newlen; fieldsplit(); } else { seplen = wcslen(sep = (wchar_t *)exprstring(varOFS)); if (!splitdone) fieldsplit(); if (--fieldno < nfield && (newlen <= wcslen(fields[fieldno]))) { (void) memcpy(fields[fieldno], newval, (newlen+1) * sizeof (wchar_t)); } else { register wchar_t *buf; buf = fieldbuf; fieldbuf = emalloc(NLINE * sizeof (wchar_t)); if (fieldno >= nfield) { if (fieldno >= NFIELD) awkerr(tmfld, NFIELD); while (nfield < fieldno) fields[nfield++] = _null; ++nfield; } fields[fieldno] = newval; op = fieldbuf; for (i = 0; i < nfield; i++) { newlen = wcslen(cp = fields[i])+1; fields[i] = op; if (op+newlen >= fieldbuf+NLINE) awkerr(toolong, NLINE); (void) memcpy(op, cp, newlen * sizeof (wchar_t)); op += newlen; } free(buf); } /* * Reconstruct $0 */ op = linebuf; i = 0; while (i < nfield) { newlen = wcslen(cp = fields[i++]); (void) memcpy(op, cp, newlen * sizeof (wchar_t)); op += newlen; if (i < nfield) { (void) memcpy(op, sep, seplen * sizeof (wchar_t)); op += seplen; } if (op >= &linebuf[NLINE]) awkerr(toolong, NLINE); } *op = '\0'; lbuflen = op-linebuf; if (varNF->n_flags & FINT) varNF->n_int = nfield; else { constant->n_int = nfield; (void) nassign(varNF, constant); } } return (np); } /* * Do a user function. * Each formal parameter must: * have the actual parameter assigned to it (call by value), * have a pointer to an array put into it (call by reference), * and be made undefined (extra formal parameters) */ static NODE * userfunc(NODE *np) { register NODE *temp; NODE *fnp; if ((fnp = np->n_left) == NNULL) awkerr(gettext("impossible function call")); if (fnp->n_type != UFUNC) awkerr(varnotfunc, fnp->n_name); #ifndef M_STKCHK if (slevel >= NRECUR) awkerr(gettext("function \"%S\" nesting level > %u"), fnp->n_name, NRECUR); #else if (!M_STKCHK) awkerr(gettext("function \"%s\" nesting level too deep"), fnp->n_name); #endif fnp = fnp->n_ufunc; { register NODE *formal; register NODE *actual; NODE *formlist, *actlist, *templist, *temptail; templist = temptail = NNULL; actlist = np->n_right; formlist = fnp->n_left; /* * pass through formal list, setting up a list * (on templist) containing temps for the values * of the actuals. * If the actual list runs out before the formal * list, assign 'constundef' as the value */ while ((formal = getlist(&formlist)) != NNULL) { register NODE *array; register int t; register size_t len; register int scope_tag; actual = getlist(&actlist); if (actual == NNULL) { actual = constundef; scope_tag = slevel+1; } else scope_tag = 0; array = actual; switch (actual->n_type) { case ARRAY: t = ARRAY; scope_tag = 0; break; case PARM: array = actual = actual->n_next; t = actual->n_type; scope_tag = actual->n_scope; if (!(actual->n_flags & FLARRAY)) array = actual->n_alink; break; default: t = VAR; break; } temp = emptynode(t, len = wcslen(formal->n_name)); (void) memcpy(temp->n_name, formal->n_name, (len+1) * sizeof (wchar_t)); temp->n_flags = FSTRING|FVINT; temp->n_string = _null; temp->n_strlen = 0; if (t == VAR) (void) assign(temp, actual); if (t != ARRAY) temp->n_flags |= FLARRAY; temp->n_scope = scope_tag; /* * link to actual parameter in case of promotion to * array */ if (actual != constundef) temp->n_alink = actual; /* * Build the templist */ if (templist != NNULL) { temptail->n_next = temp; temptail = temp; } else templist = temptail = temp; temp->n_next = NNULL; if (actual->n_type == CONSTANT) temp->n_alink = temp; else temp->n_alink = array; } /* * Bind results of the evaluation of actuals to formals. */ formlist = fnp->n_left; while (templist != NNULL) { temp = templist; templist = temp->n_next; formal = getlist(&formlist); temp->n_next = formal->n_next; formal->n_next = temp; } } { register NODE *savenode = curnode; ++slevel; if (action(fnp->n_right) == RETURN) np = retval; else np = const0; curnode = savenode; } { register NODE *formal; NODE *formlist; formlist = fnp->n_left; while ((formal = getlist(&formlist)) != NNULL) { temp = formal->n_next; formal->n_next = temp->n_next; /* if node is a local array, free the elements */ if (temp->n_type == ARRAY && (temp->n_scope == slevel)) delarray(temp); freenode(temp); } } --slevel; return (np); } /* * Get the regular expression from an expression tree. */ REGEXP getregexp(NODE *np) { if (np->n_type == RE) return (np->n_regexp); np = renode((wchar_t *)exprstring(np)); return (np->n_regexp); } /* * Get the next element from a list. */ NODE * getlist(NODE **npp) { register NODE *np; if ((np = *npp) == NNULL) return (np); if (np->n_type == COMMA) { *npp = np->n_right; return (np->n_left); } else { *npp = NNULL; return (np); } } /* * if statement. */ static int s_if(NODE *np) { register NODE *xp; register int test; test = exprtest(np->n_left); xp = np->n_right; if (xp->n_type != ELSE) awkerr(interr, "if/else"); if (test) xp = xp->n_left; else xp = xp->n_right; return (action(xp)); } /* * while and do{}while statements. */ static int s_while(NODE *np) { register int act = 0; if (np->n_type == DO) goto dowhile; for (;;) { if (exprtest(np->n_left) == 0) break; dowhile: if ((act = action(np->n_right)) != 0) { switch (act) { case BREAK: act = 0; break; case CONTINUE: act = 0; continue; } break; } } return (act); } /* * for statement. */ static int s_for(NODE *np) { register NODE *testnp, *incnp, *initnp; register int act = 0; NODE *listp; listp = np->n_left; initnp = getlist(&listp); testnp = getlist(&listp); incnp = getlist(&listp); if (initnp != NNULL) (void) exprreduce(initnp); for (;;) { if (exprtest(testnp) == 0) break; if ((act = action(np->n_right)) != 0) { switch (act) { case BREAK: act = 0; break; case CONTINUE: act = 0; goto clabel; } break; } clabel: if (incnp != NNULL) (void) exprreduce(incnp); } return (act); } /* * for variable in array statement. */ static int s_forin(NODE *np) { register NODE *left; register int act = 0; register NODE *var; register NODE **nnp; register NODE *statement; register int issymtab = 0; wchar_t *index; register int alen; int nbuck; left = np->n_left; statement = np->n_right; if (left->n_type != IN) awkerr(interr, "for (var in array)"); if ((var = left->n_left)->n_type == PARM) var = var->n_next; np = left->n_right; if (np->n_type == PARM) { np = np->n_next; if (!(np->n_flags & FLARRAY)) np = np->n_alink; } if (np == varSYMTAB) { issymtab++; np = NNULL; nbuck = 0; } else { /* * At this point if the node is not actually an array * check to see if it has already been established as * a scalar. If it is a scalar then flag an error. If * not then promote the object to an array type. */ if (np->n_type != ARRAY) { if (!isstring(np->n_flags) || np->n_string != _null) awkerr(notarray, np->n_name); else { /* promotion to array */ promote(np); if (np->n_alink != NNULL) if (!(np->n_flags & FLARRAY)) np = np->n_alink; } } /* * Set up a pointer to the first node in the array list. * Save this pointer on the delete stack. This information * is used by the delete function to advance any pointers * that might be pointing at a node which has been deleted. * See the delsymtab() function for more information. Note * that if the a_link field is nil, then just return 0 since * this array has no elements yet. */ if ((*(nnp = next_forin) = np->n_alink) == 0) return (0); if (++next_forin > &forindex[NFORINLOOP]) awkerr(toodeep, NFORINLOOP); /* * array elements have names of the form * ] (global arrays) * or * [] (local arrays) * We need to know the offset of the index portion of the * name string in order to place it in the index variable so * we look for the ']'. This is calculated here and then * used below. */ for (alen = 0; (*nnp)->n_name[alen++] != ']'; ) if ((*nnp)->n_name[alen] == '\0') awkerr(interr, "for: invalid array"); } for (;;) { if (issymtab) { if ((left = symwalk(&nbuck, &np)) == NNULL) break; index = left->n_name; } else { if ((np = *nnp) == NNULL) break; index = np->n_name+alen; *nnp = np->n_alink; } strassign(var, index, FSTATIC, wcslen(index)); if ((act = action(statement)) != 0) { switch (act) { case BREAK: act = 0; break; case CONTINUE: act = 0; continue; } break; } } next_forin--; return (act); } /* * Walk the symbol table using the same algorithm as arraynode. */ NODE * symwalk(int *buckp, NODE **npp) { register NODE *np; np = *npp; for (;;) { while (np == NNULL) { if (*buckp >= NBUCKET) return (*npp = NNULL); np = symtab[(*buckp)++]; } if (np->n_type == VAR && (!isstring(np->n_flags) || np->n_string != _null)) { *npp = np->n_next; return (np); } np = np->n_next; } /* NOTREACHED */ } /* * Test the result of an expression. */ static int exprtest(NODE *np) { register int t; if (np == NNULL) return (1); if (freelist != NNULL) freetemps(); np = exprreduce(np); if (isint(t = np->n_flags)) { if (isstring(t)) return (exprint(np) != 0); return (np->n_int != 0); } if (isreal(t)) { REAL rval; rval = isstring(t) ? exprreal(np) : np->n_real; return (rval != 0.0); } return (*(wchar_t *)exprstring(np) != '\0'); } /* * Return malloc'ed space that holds the given name "[" scope "]" index ... * concatenated string. * The node (np) is the list of indices and 'array' is the array name. */ static wchar_t * makeindex(NODE *np, wchar_t *array, int tag) { static wchar_t tags[sizeof (int)]; static wchar_t tag_chars[] = M_MB_L("0123456789ABCDEF"); register wchar_t *cp; register NODE *index; register uint_t n; register int len; register wchar_t *indstr; register wchar_t *sep; register int seplen; register int taglen; /* * calculate and create the tag string */ for (taglen = 0; tag; tag >>= 4) tags[taglen++] = tag_chars[tag & 0xf]; /* * Special (normal) case: only one index. */ if (np->n_type != COMMA) { wchar_t *ocp; size_t i; if (isleaf(np->n_flags) && np->n_type == PARM) np = np->n_next; if (isstring(np->n_flags)) { indstr = np->n_string; len = np->n_strlen; } else { indstr = exprstring(np); len = wcslen(indstr); } i = (n = wcslen(array)) + len + 3 + taglen; if (i < NINDEXBUF) ocp = indexbuf; else ocp = emalloc(i * sizeof (wchar_t)); (void) memcpy(ocp, array, n * sizeof (wchar_t)); cp = ocp+n; if (taglen) { *cp++ = '['; while (taglen) *cp++ = tags[--taglen]; } *cp++ = ']'; (void) memcpy(cp, indstr, (len+1) * sizeof (wchar_t)); return (ocp); } n = 0; seplen = wcslen(sep = (wchar_t *)exprstring(varSUBSEP)); while ((index = getlist(&np)) != NNULL) { indstr = exprstring(index); len = wcslen(indstr); if (n == 0) { cp = emalloc(sizeof (wchar_t) * ((n = wcslen(array)) + len + 3 + taglen)); (void) memcpy(cp, array, n * sizeof (wchar_t)); if (taglen) { cp[n++] = '['; while (taglen) cp[n++] = tags[--taglen]; } cp[n++] = ']'; } else { cp = erealloc(cp, (n+len+seplen+1) * sizeof (wchar_t)); (void) memcpy(cp+n, sep, seplen * sizeof (wchar_t)); n += seplen; } (void) memcpy(cp+n, indstr, (len+1) * sizeof (wchar_t)); n += len; } return (cp); } /* * Promote a node to an array. In the simplest case, just set the * node type field to ARRAY. The more complicated case involves walking * a list of variables that haven't been determined yet as scalar or array. * This routine plays with the pointers to avoid recursion. */ void promote(NODE *n) { register NODE *prev = NNULL; register NODE *next; /* * walk down the variable chain, reversing the pointers and * setting each node to type array. */ while ((n->n_flags & FLARRAY) && (n->n_alink != n)) { n->n_type = ARRAY; next = n->n_alink; n->n_alink = prev; prev = n; n = next; } /* * If the final entity on the chain is a local variable, then * reset it's alink field to NNULL - normally it points back * to itself - this is used in other parts of the code to * reduce the number of conditionals when handling locals. */ n->n_type = ARRAY; if (n->n_flags & FLARRAY) n->n_alink = NNULL; /* * Now walk back up the list setting the alink to point to * the last entry in the chain and clear the 'local array' * flag. */ while (prev != NNULL) { prev->n_flags &= ~FLARRAY; next = prev->n_alink; prev->n_alink = n; prev = next; } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * awk -- functions * * Copyright (c) 1995, 1996 by Sun Microsystems, Inc. * All rights reserved. * * Copyright 1986, 1994 by Mortice Kern Systems Inc. All rights reserved. * * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes */ #include "awk.h" #include "y.tab.h" #include #include static uint nargs(NODE *np); static NODE *dosub(NODE *np, int glob); static NODE *docasetr(NODE *np, int upper); static int asortcmp(const void *npp1, const void *npp2); static char nargerr[] = "wrong number of arguments to function \"%s\""; static NODE *asortfunc; /* Function call for asort() */ static NODE *asnp1, *asnp2; /* index1, index2 nodes */ static int asarraylen; /* strlen(array)+1 for asort */ /* * Return the value of exp(x). * Usage: y = exp(x) * y = exp() */ NODE * f_exp(NODE *np) { register uint na; if ((na = nargs(np)) > 1) awkerr(nargerr, s_exp); return (realnode(exp(exprreal(na==0 ? field0 : getlist(&np))))); } /* * Return the integer part of the argument. * Usage: i = int(r) * i = int() */ NODE * f_int(NODE *np) { register uint na; if ((na = nargs(np)) > 1) awkerr(nargerr, s_int); return (intnode(exprint(na==0 ? field0 : getlist(&np)))); } /* * Logarithm function. * Usage: y = log(x) * y = log() */ NODE * f_log(NODE *np) { register uint na; if ((na = nargs(np)) > 1) awkerr(nargerr, s_log); return (realnode(log(exprreal(na==0 ? field0 : getlist(&np))))); } /* * Square root function. * Usage: y = sqrt(x) * y = sqrt() */ NODE * f_sqrt(NODE *np) { register uint na; if ((na = nargs(np)) > 1) awkerr(nargerr, s_sqrt); return (realnode(sqrt(exprreal(na==0 ? field0 : getlist(&np))))); } /* * Trigonometric sine function. * Usage: y = sin(x) */ NODE * f_sin(NODE *np) { if (nargs(np) != 1) awkerr(nargerr, s_sin); return (realnode(sin(exprreal(getlist(&np))))); } /* * Trigonometric cosine function. * Usage: y = cos(x) */ NODE * f_cos(NODE *np) { if (nargs(np) != 1) awkerr(nargerr, s_cos); return (realnode(cos(exprreal(getlist(&np))))); } /* * Arctangent of y/x. * Usage: z = atan2(y, x) */ NODE * f_atan2(NODE *np) { double y, x; if (nargs(np) != 2) awkerr(nargerr, s_atan2); y = (double)exprreal(getlist(&np)); x = (double)exprreal(getlist(&np)); return (realnode(atan2(y, x))); } /* * Set the seed for the random number generator function -- rand. * Usage: srand(x) * srand() */ NODE * f_srand(NODE *np) { register uint na; register uint seed; static uint oldseed = 0; if ((na = nargs(np)) > 1) awkerr(nargerr, s_srand); if (na == 0) seed = (uint)time((time_t *)0); else seed = (uint)exprint(getlist(&np)); srand(seed); na = oldseed; oldseed = seed; return (intnode((INT)na)); } /* * Generate a random number. * Usage: x = rand() */ NODE * f_rand(NODE *np) { double result; int expon; ushort rint; if (nargs(np) != 0) awkerr(nargerr, s_rand); rint = rand() & SHRT_MAX; result = frexp((double)rint, &expon); return (realnode((REAL)ldexp(result, expon-15))); } /* * Substitute function. * Usage: n = sub(regex, replace, target) * n = sub(regex, replace) */ NODE * f_sub(NODE *np) { return (dosub(np, 1)); } /* * Global substitution function. * Usage: n = gsub(regex, replace, target) * n = gsub(regex, replace) */ NODE * f_gsub(NODE *np) { return (dosub(np, 0)); } /* * Do actual substitutions. * `glob' is the number to substitute, 0 for all. */ static NODE * dosub(NODE *np, int glob) { wchar_t *text; register wchar_t *sub; register uint n; register uint na; register REGEXP rp; NODE *left; static wchar_t *buf; if ((na = nargs(np)) != 2 && na != 3) awkerr(nargerr, glob==0 ? s_gsub : s_sub); rp = getregexp(getlist(&np)); sub = exprstring(getlist(&np)); if (na == 3) { left = getlist(&np); text = exprstring(left); } else { left = field0; text = linebuf; } switch (REGWDOSUBA(rp, sub, text, &buf, 256, &glob)) { case REG_OK: case REG_NOMATCH: n = glob; break; case REG_ESPACE: if (buf != NULL) free(buf); awkerr(nomem); default: awkerr(gettext("regular expression error")); } (void)assign(left, stringnode(buf, FNOALLOC, wcslen(buf))); return (intnode((INT)n)); } /* * Match function. Return position (origin 1) or 0 for regular * expression match in string. Set new variables RSTART and RLENGTH * as well. * Usage: pos = match(string, re) */ NODE * f_match(NODE *np) { register wchar_t *text; register REGEXP rp; register int pos, length; REGWMATCH_T match[10]; if (nargs(np) != 2) awkerr(nargerr, s_match); text = exprstring(getlist(&np)); rp = getregexp(getlist(&np)); if (REGWEXEC(rp, text, 10, match, 0) == REG_OK) { pos = match[0].rm_sp-text+1; length = match[0].rm_ep - match[0].rm_sp; } else { pos = 0; length = -1; } constant->n_int = length; (void)assign(vlook(M_MB_L("RLENGTH")), constant); return (assign(vlook(M_MB_L("RSTART")), intnode((INT)pos))); } /* * Call shell or command interpreter. * Usage: status = system(command) */ NODE * f_system(NODE *np) { int retcode; if (nargs(np) != 1) awkerr(nargerr, s_system); (void) fflush(stdout); retcode = system(mbunconvert(exprstring(getlist(&np)))); return (intnode((INT)WEXITSTATUS(retcode))); } /* * Search for string within string. * Usage: pos = index(string1, string2) */ NODE * f_index(NODE *np) { register wchar_t *s1, *s2; register int l1, l2; register int result; if (nargs(np) != 2) awkerr(nargerr, s_index); s1 = (wchar_t *)exprstring(getlist(&np)); s2 = (wchar_t *)exprstring(getlist(&np)); l1 = wcslen(s1); l2 = wcslen(s2); result = 1; while (l2 <= l1) { if (memcmp(s1, s2, l2 * sizeof(wchar_t)) == 0) break; result++; s1++; l1--; } if (l2 > l1) result = 0; return (intnode((INT)result)); } /* * Return length of argument or $0 * Usage: n = length(string) * n = length() * n = length */ NODE * f_length(NODE *np) { register uint na; if ((na = nargs(np)) > 1) awkerr(nargerr, s_length); if (na == 0) na = lbuflen; else na = wcslen((wchar_t *)exprstring(getlist(&np))); return (intnode((INT)na)); } /* * Split string into fields. * Usage: nfields = split(string, array [, separator]); */ NODE * f_split(NODE *np) { register wchar_t *cp; wchar_t *ep, *saved = 0; register NODE *tnp, *snp, *otnp; register NODE *sep; REGEXP old_resep = 0; size_t seplen; uint n; wint_t c; wchar_t savesep[20]; wchar_t *(*old_awkfield)(wchar_t **) = 0; if ((n = nargs(np))<2 || n>3) awkerr(nargerr, s_split); ep = exprstring(snp = getlist(&np)); tnp = getlist(&np); if (snp->n_type == INDEX && snp->n_left == tnp) ep = saved = wsdup(ep); if (n == 3) { sep = getlist(&np); } else sep = NNULL; switch (tnp->n_type) { case ARRAY: delarray(tnp); break; case PARM: break; case VAR: if (isstring(tnp->n_flags) && tnp->n_string==_null) break; /* FALLTHROUGH */ default: awkerr(gettext( "second parameter to \"split\" must be an array")); } /* * If an argument has been passed in to be used as the * field separator check to see if it is a constant regular * expression. If so, use it directly otherwise reduce the * expression, convert the result into a string and assign it * to "FS" (after saving the old value for FS.) */ if (sep != NNULL) { if (sep->n_type == PARM) sep = sep->n_next; if (sep->n_type == RE) { old_resep = resep; resep = sep->n_regexp; old_awkfield = awkfield; awkfield = refield; } else { sep = exprreduce(sep); seplen = wcslen(cp = (wchar_t *)exprstring(varFS)); (void) memcpy(savesep, cp, (seplen+1) * sizeof(wchar_t)); (void) assign(varFS, sep); } } /* * Iterate over the record, extracting each field and assigning it to * the corresponding element in the array. */ otnp = tnp; /* save tnp for possible promotion */ tnp = node(INDEX, tnp, constant); fcount = 0; for (;;) { if ((cp = (*awkfield)(&ep)) == NULL) { if (fcount == 0) { if (otnp->n_type == PARM) otnp = otnp->n_next; promote(otnp); } break; } c = *ep; *ep = '\0'; constant->n_int = ++fcount; (void)assign(tnp, stringnode(cp,FALLOC|FSENSE,(size_t)(ep-cp))); *ep = c; } /* * Restore the old record separator/and or regular expression. */ if (sep != NNULL) { if (old_awkfield != 0) { resep = old_resep; awkfield = old_awkfield; } else { (void)assign(varFS, stringnode(savesep, FSTATIC, seplen)); } } if (saved) free(saved); return (intnode((INT)fcount)); } /* * Sprintf function. * Usage: string = sprintf(format, arg, ...) */ NODE * f_sprintf(NODE *np) { wchar_t *cp; size_t length; if (nargs(np) == 0) awkerr(nargerr, s_sprintf); length = xprintf(np, (FILE *)NULL, &cp); np = stringnode(cp, FNOALLOC, length); return (np); } /* * Substring. * newstring = substr(string, start, [length]) */ NODE * f_substr(NODE *np) { register STRING str; register size_t n; register int start; register size_t len; if ((n = nargs(np))<2 || n>3) awkerr(nargerr, s_substr); str = exprstring(getlist(&np)); if ((start = (int)exprint(getlist(&np))-1) < 0) start = 0; if (n == 3) { int x; x = (int)exprint(getlist(&np)); if (x < 0) len = 0; else len = (size_t)x; } else len = LARGE; n = wcslen((wchar_t *)str); if (start > n) start = n; n -= start; if (len > n) len = n; str += start; n = str[len]; str[len] = '\0'; np = stringnode(str, FALLOC, len); str[len] = n; return (np); } /* * Close an output or input file stream. */ NODE * f_close(NODE *np) { register OFILE *op; register char *name; if (nargs(np) != 1) awkerr(nargerr, s_close); name = mbunconvert(exprstring(getlist(&np))); for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++) if (op->f_fp!=FNULL && strcmp(name, op->f_name)==0) { awkclose(op); break; } if (op >= &ofiles[NIOSTREAM]) return (const1); return (const0); } /* * Return the integer value of the first character of a string. * Usage: char = ord(string) */ NODE * f_ord(NODE *np) { if (nargs(np) != 1) awkerr(nargerr, s_ord); return (intnode((INT)*exprstring(getlist(&np)))); } /* * Return the argument string in lower case: * Usage: * lower = tolower(upper) */ NODE * f_tolower(NODE *np) { return (docasetr(np, 0)); } /* * Return the argument string in upper case: * Usage: * upper = toupper(lower) */ NODE * f_toupper(NODE *np) { return (docasetr(np, 1)); } /* * Sort the array into traversal order by the next "for (i in array)" loop. * Usage: * asort(array, "cmpfunc") * cmpfunc(array, index1, index2) * returns: * <0 if array[index1] < array[index2] * 0 if array[index1] == array[index2] * >0 if array[index1] > array[index2] */ NODE * f_asort(NODE *np) { NODE *array; STRING funcname; register size_t nel; register NODE *tnp; register NODE *funcnp; register NODE **alist, **npp; if (nargs(np) != 2) awkerr(nargerr, s_asort); array = getlist(&np); if (array->n_type == PARM) array = array->n_next; if (array->n_type != ARRAY) awkerr(gettext("%s function requires an array"), s_asort); funcname = exprstring(getlist(&np)); if ((funcnp = vlookup(funcname, 1)) == NNULL || funcnp->n_type != UFUNC) awkerr(gettext("%s: %s is not a function\n"), s_asort, funcname); /* * Count size of array, allowing one extra for NULL at end */ nel = 1; for (tnp = array->n_alink; tnp != NNULL; tnp = tnp->n_alink) ++nel; /* * Create UFUNC node that points at the funcnp on left and the * list of three variables on right (array, index1, index2) * UFUNC * / \ * funcnp COMMA * / \ * array COMMA * / \ * index1 index2 */ if (asortfunc == NNULL) { running = 0; asortfunc = node(CALLUFUNC, NNULL, node(COMMA, NNULL, node(COMMA, asnp1=stringnode(_null, FSTATIC, 0), asnp2=stringnode(_null, FSTATIC, 0)))); running = 1; } asortfunc->n_left = funcnp; asortfunc->n_right->n_left = array; asarraylen = wcslen(array->n_name)+1; alist = (NODE **) emalloc(nel*sizeof(NODE *)); /* * Copy array into alist. */ npp = alist; for (tnp = array->n_alink; tnp != NNULL; tnp = tnp->n_alink) *npp++ = tnp; *npp = NNULL; /* * Re-order array to this list */ qsort((wchar_t *)alist, nel-1, sizeof (NODE *), asortcmp); tnp = array; npp = alist; do { tnp = tnp->n_alink = *npp; } while (*npp++ != NNULL); free((wchar_t *)alist); return (constundef); } /* * Return the number of arguments of a function. */ static uint nargs(NODE *np) { register int n; if (np == NNULL) return (0); n = 1; while (np!=NNULL && np->n_type==COMMA) { np = np->n_right; n++; } return (n); } /* * Do case translation. */ static NODE * docasetr(NODE *np, int upper) { register int c; register wchar_t *cp; register wchar_t *str; register uint na; if ((na = nargs(np)) > 1) awkerr(nargerr, upper ? s_toupper : s_tolower); str = strsave(na==0 ? linebuf : exprstring(getlist(&np))); cp = str; if (upper) { while ((c = *cp++) != '\0') cp[-1] = towupper(c); } else { while ((c = *cp++) != '\0') cp[-1] = towlower(c); } return (stringnode((STRING)str, FNOALLOC, (size_t)(cp-str-1))); } /* * The comparison routine used by qsort inside f_asort() */ static int asortcmp(const void *npp1, const void *npp2) { asnp1->n_strlen = wcslen(asnp1->n_string = (*(NODE **)npp1)->n_name+asarraylen); asnp2->n_strlen = wcslen(asnp2->n_string = (*(NODE **)npp2)->n_name+asarraylen); return ((int)exprint(asortfunc)); } #if M_MATHERR #if !defined(__BORLANDC__)&&defined(__TURBOC__)&&__COMPACT__&&__EMULATE__ /* So it won't optimize registers our FP is using */ #define flushesbx() (_BX = 0, _ES = _BX) #else #define flushesbx() (0) #endif /* * Math error for awk. */ int matherr(struct exception *ep) { register uint type; static char msgs[7][256]; static int first_time = 1; if (first_time) { msgs[0] = gettext("Unknown FP error"), msgs[1] = gettext("Domain"), msgs[2] = gettext("Singularity"), msgs[3] = gettext("Overflow"), msgs[4] = gettext("Underflow"), msgs[5] = gettext("Total loss of precision"), msgs[6] = gettext("Partial loss of precision") first_time = 0; } if ((type = ep->type) > (uint)PLOSS) type = 0; (void)fprintf(stderr, "awk: %s", strmsg(msgs[type])); (void)fprintf(stderr, gettext( " error in function %s(%g) at NR=%lld\n"), ((void) flushesbx(), ep->name), ep->arg1, (INT)exprint(varNR)); return (1); } #endif /*M_MATHERR*/