Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F9894996
cprintf.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
21 KB
Referenced Files
None
Subscribers
None
cprintf.c
View Options
/*
* cprintf - printf with inline color tags, powered by a register-based VM
*
* usage:
* cprintf("<red>error:</red> something went wrong\n");
* cprintf("<bold><cyan>info:</cyan></bold> hello %s\n", name);
* cprintf("<#ff8800>orange text</#ff8800>\n");
* cprintf(" <pad=18><green>%s</green></pad> %s\n", cmd->name, cmd->desc);
*
* supported tags:
* <red> <green> <yellow> <blue> <magenta> <cyan> <white> <black>
* <gray/grey> <bright_red> <bright_green> ... etc
* <bg_red> <bg_green> ... <bg_#RGB> <bg_#RRGGBB>
* <bold> <dim> <ul> (underline)
* <#RRGGBB> or <#RGB> for arbitrary 24-bit foreground colors
* <pad=N> ... </pad> - right-pad contents to N visible columns
* <rpad=N> ... </rpad> - left-pad (right-align) contents to N visible columns
* <let name=style1+style2+...> - define a named style variable
* <$name> to apply a previously defined style variable
* </tagname> or </> to reset
*
*/
#include
"utils.h"
#include
"cli/cprintf.h"
#include
<ctype.h>
#include
<stdio.h>
#include
<stdarg.h>
#include
<string.h>
#include
<stdlib.h>
#include
<stdint.h>
typedef
enum
{
OP_NOP
=
0
,
OP_EMIT_LIT
,
OP_EMIT_FMT
,
OP_SET_FG
,
OP_SET_BG
,
OP_SET_FG_RGB
,
OP_SET_BG_RGB
,
OP_SET_BOLD
,
OP_SET_DIM
,
OP_SET_UL
,
OP_STYLE_PUSH
,
OP_STYLE_FLUSH
,
OP_STYLE_RESET
,
OP_PAD_BEGIN
,
OP_RPAD_BEGIN
,
OP_PAD_END
,
OP_HALT
,
OP_MAX
}
opcode_t
;
typedef
struct
{
uint32_t
op
;
uint32_t
operand
;
}
instruction_t
;
typedef
enum
{
COL_NONE
=
0
,
COL_BLACK
=
30
,
COL_RED
,
COL_GREEN
,
COL_YELLOW
,
COL_BLUE
,
COL_MAGENTA
,
COL_CYAN
,
COL_WHITE
,
COL_GRAY
=
90
,
COL_BRIGHT_RED
,
COL_BRIGHT_GREEN
,
COL_BRIGHT_YELLOW
,
COL_BRIGHT_BLUE
,
COL_BRIGHT_MAGENTA
,
COL_BRIGHT_CYAN
,
COL_BRIGHT_WHITE
,
COL_RGB
=
0xFF
}
color_t
;
#define UNPACK_R(c) (((c)>>16)&0xFF)
#define UNPACK_G(c) (((c)>>8)&0xFF)
#define UNPACK_B(c) ((c)&0xFF)
#define PACK_RGB(r,g,b) \
(((uint32_t)(r)<<16)|((uint32_t)(g)<<8)|(uint32_t)(b))
typedef
struct
{
size_t
mark
;
int
width
;
int
right_align
;
}
pad_entry_t
;
typedef
struct
{
uint32_t
fg
;
uint32_t
bg
;
uint32_t
fg_rgb
;
uint32_t
bg_rgb
;
int
bold
;
int
dim
;
int
ul
;
}
style_entry_t
;
typedef
struct
{
uint32_t
fg
;
uint32_t
bg
;
uint32_t
fg_rgb
;
uint32_t
bg_rgb
;
int
bold
;
int
dim
;
int
ul
;
style_entry_t
style_stack
[
8
];
int
style_depth
;
pad_entry_t
pad_stack
[
8
];
int
pad_depth
;
}
vm_regs_t
;
#define MAX_VARS 16
#define MAX_VAR_NAME 32
#define MAX_VAR_VALUE 128
typedef
struct
{
char
name
[
MAX_VAR_NAME
];
char
value
[
MAX_VAR_VALUE
];
int
nlen
;
int
vlen
;
}
cprintf_var_t
;
typedef
struct
{
cprintf_var_t
vars
[
MAX_VARS
];
int
count
;
}
var_table_t
;
struct
program_t
{
instruction_t
*
code
;
size_t
code_len
;
size_t
code_cap
;
char
*
literals
;
size_t
lit_len
;
size_t
lit_cap
;
};
static
program_t
*
program_new
(
void
)
{
program_t
*
p
=
calloc
(
1
,
sizeof
(
*
p
));
*
p
=
(
program_t
){
.
code_cap
=
32
,
.
code
=
malloc
(
32
*
sizeof
(
instruction_t
)),
.
lit_cap
=
256
,
.
literals
=
malloc
(
256
),
};
return
p
;
}
static
void
emit_op
(
program_t
*
p
,
uint32_t
op
,
uint32_t
operand
)
{
if
(
p
->
code_len
>=
p
->
code_cap
)
{
p
->
code_cap
*=
2
;
p
->
code
=
realloc
(
p
->
code
,
p
->
code_cap
*
sizeof
(
instruction_t
));
}
p
->
code
[
p
->
code_len
++
]
=
(
instruction_t
){
op
,
operand
};
}
static
uint32_t
add_literal
(
program_t
*
p
,
const
char
*
s
,
size_t
len
)
{
while
(
p
->
lit_len
+
len
+
1
>
p
->
lit_cap
)
{
p
->
lit_cap
*=
2
;
p
->
literals
=
realloc
(
p
->
literals
,
p
->
lit_cap
);
}
uint32_t
off
=
(
uint32_t
)
p
->
lit_len
;
memcpy
(
p
->
literals
+
p
->
lit_len
,
s
,
len
);
p
->
literals
[
p
->
lit_len
+
len
]
=
'\0'
;
p
->
lit_len
+=
len
+
1
;
return
off
;
}
typedef
struct
{
const
char
*
name
;
int
nlen
;
color_t
col
;
}
color_entry_t
;
static
const
color_entry_t
fg_colors
[]
=
{
{
"black"
,
5
,
COL_BLACK
},
{
"red"
,
3
,
COL_RED
},
{
"green"
,
5
,
COL_GREEN
},
{
"yellow"
,
6
,
COL_YELLOW
},
{
"blue"
,
4
,
COL_BLUE
},
{
"magenta"
,
7
,
COL_MAGENTA
},
{
"cyan"
,
4
,
COL_CYAN
},
{
"white"
,
5
,
COL_WHITE
},
{
"gray"
,
4
,
COL_GRAY
},
{
"grey"
,
4
,
COL_GRAY
},
{
"bright_red"
,
10
,
COL_BRIGHT_RED
},
{
"bright_green"
,
12
,
COL_BRIGHT_GREEN
},
{
"bright_yellow"
,
13
,
COL_BRIGHT_YELLOW
},
{
"bright_blue"
,
11
,
COL_BRIGHT_BLUE
},
{
"bright_magenta"
,
14
,
COL_BRIGHT_MAGENTA
},
{
"bright_cyan"
,
11
,
COL_BRIGHT_CYAN
},
{
"bright_white"
,
12
,
COL_BRIGHT_WHITE
},
};
static
const
color_entry_t
bg_colors
[]
=
{
{
"bg_black"
,
8
,
COL_BLACK
},
{
"bg_red"
,
6
,
COL_RED
},
{
"bg_green"
,
8
,
COL_GREEN
},
{
"bg_yellow"
,
9
,
COL_YELLOW
},
{
"bg_blue"
,
7
,
COL_BLUE
},
{
"bg_magenta"
,
10
,
COL_MAGENTA
},
{
"bg_cyan"
,
7
,
COL_CYAN
},
{
"bg_white"
,
8
,
COL_WHITE
},
};
static
const
color_entry_t
seg_bg_colors
[]
=
{
{
"black"
,
5
,
COL_BLACK
},
{
"red"
,
3
,
COL_RED
},
{
"green"
,
5
,
COL_GREEN
},
{
"yellow"
,
6
,
COL_YELLOW
},
{
"blue"
,
4
,
COL_BLUE
},
{
"magenta"
,
7
,
COL_MAGENTA
},
{
"cyan"
,
4
,
COL_CYAN
},
{
"white"
,
5
,
COL_WHITE
},
};
#define FG_COUNT (sizeof(fg_colors)/sizeof(fg_colors[0]))
#define BG_COUNT (sizeof(bg_colors)/sizeof(bg_colors[0]))
#define SEG_BG_COUNT (sizeof(seg_bg_colors)/sizeof(seg_bg_colors[0]))
static
int
parse_hex_rgb
(
const
char
*
hex
,
int
len
,
uint32_t
*
rgb
)
{
int
r
,
g
,
b
;
if
(
len
==
4
)
{
int
r1
=
hex_digit
(
hex
[
1
]),
g1
=
hex_digit
(
hex
[
2
]),
b1
=
hex_digit
(
hex
[
3
]);
if
(
r1
<
0
||
g1
<
0
||
b1
<
0
)
return
0
;
r
=
r1
*
17
;
g
=
g1
*
17
;
b
=
b1
*
17
;
}
else
if
(
len
==
7
)
{
int
r1
=
hex_digit
(
hex
[
1
]),
r2
=
hex_digit
(
hex
[
2
]);
int
g1
=
hex_digit
(
hex
[
3
]),
g2
=
hex_digit
(
hex
[
4
]);
int
b1
=
hex_digit
(
hex
[
5
]),
b2
=
hex_digit
(
hex
[
6
]);
if
(
r1
<
0
||
r2
<
0
||
g1
<
0
||
g2
<
0
||
b1
<
0
||
b2
<
0
)
return
0
;
r
=
r1
*
16
+
r2
;
g
=
g1
*
16
+
g2
;
b
=
b1
*
16
+
b2
;
}
else
return
0
;
*
rgb
=
PACK_RGB
(
r
,
g
,
b
);
return
1
;
}
static
int
compile_hex_fg
(
program_t
*
p
,
const
char
*
tag
,
int
len
)
{
uint32_t
rgb
;
if
(
!
parse_hex_rgb
(
tag
,
len
,
&
rgb
))
return
0
;
emit_op
(
p
,
OP_SET_FG_RGB
,
rgb
);
return
1
;
}
static
int
compile_hex_bg
(
program_t
*
p
,
const
char
*
hex
,
int
hlen
)
{
uint32_t
rgb
;
if
(
!
parse_hex_rgb
(
hex
,
hlen
,
&
rgb
))
return
0
;
emit_op
(
p
,
OP_SET_BG_RGB
,
rgb
);
return
1
;
}
static
int
tag_eq
(
const
char
*
tag
,
int
len
,
const
char
*
lit
)
{
int
ll
=
(
int
)
strlen
(
lit
);
return
len
==
ll
&&
memcmp
(
tag
,
lit
,
ll
)
==
0
;
}
static
int
tag_prefix
(
const
char
*
tag
,
int
len
,
const
char
*
pfx
)
{
int
pl
=
(
int
)
strlen
(
pfx
);
return
len
>
pl
&&
memcmp
(
tag
,
pfx
,
pl
)
==
0
;
}
static
int
match_fg
(
program_t
*
p
,
const
char
*
s
,
int
len
)
{
for
(
int
i
=
0
;
i
<
(
int
)
FG_COUNT
;
i
++
)
if
(
len
==
fg_colors
[
i
].
nlen
&&
memcmp
(
s
,
fg_colors
[
i
].
name
,
len
)
==
0
)
{
emit_op
(
p
,
OP_SET_FG
,
fg_colors
[
i
].
col
);
return
1
;
}
return
0
;
}
static
int
match_bg
(
program_t
*
p
,
const
char
*
s
,
int
len
)
{
for
(
int
i
=
0
;
i
<
(
int
)
BG_COUNT
;
i
++
)
if
(
len
==
bg_colors
[
i
].
nlen
&&
memcmp
(
s
,
bg_colors
[
i
].
name
,
len
)
==
0
)
{
emit_op
(
p
,
OP_SET_BG
,
bg_colors
[
i
].
col
);
return
1
;
}
return
0
;
}
static
int
match_seg_bg
(
program_t
*
p
,
const
char
*
s
,
int
len
)
{
for
(
int
i
=
0
;
i
<
(
int
)
SEG_BG_COUNT
;
i
++
)
if
(
len
==
seg_bg_colors
[
i
].
nlen
&&
memcmp
(
s
,
seg_bg_colors
[
i
].
name
,
len
)
==
0
)
{
emit_op
(
p
,
OP_SET_BG
,
seg_bg_colors
[
i
].
col
);
return
1
;
}
return
0
;
}
typedef
struct
{
const
char
*
name
;
int
nlen
;
int
op
;
}
attr_entry_t
;
static
const
attr_entry_t
attrs
[]
=
{
{
"bold"
,
4
,
OP_SET_BOLD
},
{
"dim"
,
3
,
OP_SET_DIM
},
{
"ul"
,
2
,
OP_SET_UL
},
};
#define ATTR_COUNT (sizeof(attrs) / sizeof(attrs[0]))
static
int
match_attr
(
program_t
*
p
,
const
char
*
s
,
int
len
)
{
for
(
int
i
=
0
;
i
<
(
int
)
ATTR_COUNT
;
i
++
)
if
(
len
==
attrs
[
i
].
nlen
&&
memcmp
(
s
,
attrs
[
i
].
name
,
len
)
==
0
)
{
emit_op
(
p
,
attrs
[
i
].
op
,
1
);
return
1
;
}
return
0
;
}
static
const
char
*
next_seg
(
const
char
*
cur
,
const
char
*
end
,
int
*
out_len
)
{
const
char
*
sep
=
cur
;
while
(
sep
<
end
&&
*
sep
!=
'_'
)
sep
++
;
*
out_len
=
(
int
)(
sep
-
cur
);
return
sep
;
}
static
int
match_plus_seg
(
program_t
*
p
,
const
char
*
seg
,
int
slen
)
{
if
(
match_attr
(
p
,
seg
,
slen
))
return
1
;
if
(
match_fg
(
p
,
seg
,
slen
))
return
1
;
if
(
match_bg
(
p
,
seg
,
slen
))
return
1
;
if
(
slen
>
0
&&
seg
[
0
]
==
'#'
)
return
compile_hex_fg
(
p
,
seg
,
slen
);
if
(
tag_prefix
(
seg
,
slen
,
"bg_#"
))
return
compile_hex_bg
(
p
,
seg
+
3
,
slen
-
3
);
if
(
tag_prefix
(
seg
,
slen
,
"bg_"
))
return
match_seg_bg
(
p
,
seg
+
3
,
slen
-
3
);
return
0
;
}
static
int
compile_plus_segs
(
program_t
*
p
,
const
char
*
s
,
int
len
)
{
const
char
*
seg
=
s
;
const
char
*
end
=
s
+
len
;
int
emitted
=
0
;
while
(
seg
<
end
)
{
const
char
*
next
=
seg
;
while
(
next
<
end
&&
*
next
!=
'+'
)
next
++
;
int
slen
=
(
int
)(
next
-
seg
);
if
(
!
match_plus_seg
(
p
,
seg
,
slen
))
return
0
;
emitted
++
;
seg
=
(
next
<
end
)
?
next
+
1
:
end
;
}
return
emitted
;
}
static
int
compile_let
(
var_table_t
*
vars
,
const
char
*
tag
,
int
len
)
{
const
char
*
eq
=
memchr
(
tag
+
4
,
'='
,
len
-
4
);
if
(
!
eq
)
return
0
;
int
nlen
=
(
int
)(
eq
-
(
tag
+
4
));
int
vlen
=
(
int
)((
tag
+
len
)
-
(
eq
+
1
));
if
(
nlen
<=
0
||
nlen
>=
MAX_VAR_NAME
||
vlen
<=
0
||
vlen
>=
MAX_VAR_VALUE
)
return
0
;
if
(
vars
->
count
>=
MAX_VARS
)
return
0
;
cprintf_var_t
*
v
=
&
vars
->
vars
[
vars
->
count
++
];
memcpy
(
v
->
name
,
tag
+
4
,
nlen
);
v
->
name
[
nlen
]
=
'\0'
;
v
->
nlen
=
nlen
;
memcpy
(
v
->
value
,
eq
+
1
,
vlen
);
v
->
value
[
vlen
]
=
'\0'
;
v
->
vlen
=
vlen
;
return
1
;
}
static
int
compile_var_ref
(
program_t
*
p
,
var_table_t
*
vars
,
const
char
*
tag
,
int
len
)
{
int
nlen
=
len
-
1
;
for
(
int
i
=
0
;
i
<
vars
->
count
;
i
++
)
{
if
(
nlen
==
vars
->
vars
[
i
].
nlen
&&
memcmp
(
tag
+
1
,
vars
->
vars
[
i
].
name
,
nlen
)
==
0
)
{
emit_op
(
p
,
OP_STYLE_PUSH
,
0
);
if
(
!
compile_plus_segs
(
p
,
vars
->
vars
[
i
].
value
,
vars
->
vars
[
i
].
vlen
))
return
0
;
emit_op
(
p
,
OP_STYLE_FLUSH
,
0
);
return
1
;
}
}
return
0
;
}
static
int
compile_tag
(
program_t
*
p
,
const
char
*
tag
,
int
len
,
int
closing
,
var_table_t
*
vars
)
{
if
(
closing
)
{
if
(
tag_eq
(
tag
,
len
,
"pad"
)
||
tag_eq
(
tag
,
len
,
"rpad"
))
emit_op
(
p
,
OP_PAD_END
,
0
);
else
emit_op
(
p
,
OP_STYLE_RESET
,
0
);
return
1
;
}
if
(
tag_prefix
(
tag
,
len
,
"let "
))
return
compile_let
(
vars
,
tag
,
len
);
if
(
tag
[
0
]
==
'$'
&&
len
>
1
)
return
compile_var_ref
(
p
,
vars
,
tag
,
len
);
if
(
tag_prefix
(
tag
,
len
,
"pad="
))
{
emit_op
(
p
,
OP_PAD_BEGIN
,
(
uint32_t
)
atoi
(
tag
+
4
));
return
1
;
}
if
(
tag_prefix
(
tag
,
len
,
"rpad="
))
{
emit_op
(
p
,
OP_RPAD_BEGIN
,
(
uint32_t
)
atoi
(
tag
+
5
));
return
1
;
}
emit_op
(
p
,
OP_STYLE_PUSH
,
0
);
if
(
match_attr
(
p
,
tag
,
len
))
{
emit_op
(
p
,
OP_STYLE_FLUSH
,
0
);
return
1
;
}
if
(
match_fg
(
p
,
tag
,
len
))
{
emit_op
(
p
,
OP_STYLE_FLUSH
,
0
);
return
1
;
}
if
(
match_bg
(
p
,
tag
,
len
))
{
emit_op
(
p
,
OP_STYLE_FLUSH
,
0
);
return
1
;
}
if
(
tag
[
0
]
==
'#'
)
{
if
(
!
compile_hex_fg
(
p
,
tag
,
len
))
return
0
;
emit_op
(
p
,
OP_STYLE_FLUSH
,
0
);
return
1
;
}
if
(
tag_prefix
(
tag
,
len
,
"bg_#"
))
{
if
(
!
compile_hex_bg
(
p
,
tag
+
3
,
len
-
3
))
return
0
;
emit_op
(
p
,
OP_STYLE_FLUSH
,
0
);
return
1
;
}
const
char
*
seg
=
tag
;
const
char
*
end
=
tag
+
len
;
int
emitted
=
0
;
while
(
seg
<
end
)
{
int
slen
;
const
char
*
sep
=
next_seg
(
seg
,
end
,
&
slen
);
if
(
match_attr
(
p
,
seg
,
slen
))
{
}
else
if
(
tag_eq
(
seg
,
slen
,
"bg"
)
&&
sep
<
end
)
{
seg
=
sep
+
1
;
sep
=
next_seg
(
seg
,
end
,
&
slen
);
if
(
!
match_seg_bg
(
p
,
seg
,
slen
))
return
0
;
}
else
if
(
!
match_fg
(
p
,
seg
,
slen
))
return
0
;
emitted
++
;
seg
=
(
sep
<
end
)
?
sep
+
1
:
end
;
}
if
(
emitted
)
{
emit_op
(
p
,
OP_STYLE_FLUSH
,
0
);
return
1
;
}
return
0
;
}
static
void
flush_lit
(
program_t
*
p
,
const
char
*
lit
,
const
char
*
end
)
{
if
(
end
<=
lit
)
return
;
uint32_t
off
=
add_literal
(
p
,
lit
,
end
-
lit
);
emit_op
(
p
,
OP_EMIT_LIT
,
off
);
}
static
const
char
*
scan_tag
(
program_t
*
p
,
const
char
*
ptr
,
const
char
**
lit
,
var_table_t
*
vars
)
{
flush_lit
(
p
,
*
lit
,
ptr
);
const
char
*
start
=
ptr
+
1
;
int
closing
=
0
;
if
(
*
start
==
'/'
)
{
closing
=
1
;
start
++
;
}
if
(
closing
&&
*
start
==
'>'
)
{
emit_op
(
p
,
OP_STYLE_RESET
,
0
);
*
lit
=
start
+
1
;
return
*
lit
;
}
const
char
*
end
=
start
;
while
(
*
end
&&
*
end
!=
'>'
)
end
++
;
if
(
*
end
==
'>'
&&
compile_tag
(
p
,
start
,
(
int
)(
end
-
start
),
closing
,
vars
))
{
*
lit
=
end
+
1
;
return
*
lit
;
}
uint32_t
off
=
add_literal
(
p
,
"<"
,
1
);
emit_op
(
p
,
OP_EMIT_LIT
,
off
);
*
lit
=
ptr
+
1
;
return
*
lit
;
}
typedef
enum
{
ARG_NONE
=
0
,
ARG_INT
,
ARG_LONG
,
ARG_LLONG
,
ARG_SIZE
,
ARG_DOUBLE
,
ARG_CSTR
,
ARG_PTR
,
ARG_WINT
,
ARG_WSTR
,
}
arg_class_t
;
static
arg_class_t
classify_arg
(
const
char
*
spec
,
int
len
)
{
char
conv
=
spec
[
len
-
1
];
if
(
conv
==
'%'
||
conv
==
'n'
)
return
ARG_NONE
;
if
(
conv
==
's'
)
return
ARG_CSTR
;
if
(
conv
==
'p'
)
return
ARG_PTR
;
if
(
conv
==
'f'
||
conv
==
'F'
||
conv
==
'e'
||
conv
==
'E'
||
conv
==
'g'
||
conv
==
'G'
||
conv
==
'a'
||
conv
==
'A'
)
return
ARG_DOUBLE
;
const
char
*
p
=
spec
+
1
;
while
(
*
p
==
'-'
||
*
p
==
'+'
||
*
p
==
' '
||
*
p
==
'#'
||
*
p
==
'0'
)
p
++
;
if
(
*
p
==
'*'
)
p
++
;
else
while
(
*
p
>=
'0'
&&
*
p
<=
'9'
)
p
++
;
if
(
*
p
==
'.'
)
{
p
++
;
if
(
*
p
==
'*'
)
p
++
;
else
while
(
*
p
>=
'0'
&&
*
p
<=
'9'
)
p
++
;
}
if
(
p
[
0
]
==
'z'
)
return
ARG_SIZE
;
if
(
p
[
0
]
==
'l'
&&
p
[
1
]
==
'l'
)
return
ARG_LLONG
;
if
(
p
[
0
]
==
'l'
&&
conv
==
'c'
)
return
ARG_WINT
;
if
(
p
[
0
]
==
'l'
&&
conv
==
's'
)
return
ARG_WSTR
;
if
(
p
[
0
]
==
'l'
)
return
ARG_LONG
;
if
(
p
[
0
]
==
'j'
)
return
ARG_LLONG
;
return
ARG_INT
;
}
static
const
char
*
scan_fmt
(
program_t
*
p
,
const
char
*
ptr
,
const
char
**
lit
)
{
flush_lit
(
p
,
*
lit
,
ptr
);
const
char
*
fs
=
ptr
+
1
;
while
(
*
fs
==
'-'
||*
fs
==
'+'
||*
fs
==
' '
||*
fs
==
'#'
||*
fs
==
'0'
)
fs
++
;
if
(
*
fs
==
'*'
)
fs
++
;
else
while
(
*
fs
>=
'0'
&&
*
fs
<=
'9'
)
fs
++
;
if
(
*
fs
==
'.'
)
{
fs
++
;
if
(
*
fs
==
'*'
)
fs
++
;
else
while
(
*
fs
>=
'0'
&&
*
fs
<=
'9'
)
fs
++
;
}
while
(
*
fs
==
'h'
||*
fs
==
'l'
||*
fs
==
'L'
||*
fs
==
'z'
||*
fs
==
'j'
||*
fs
==
't'
)
fs
++
;
if
(
*
fs
)
fs
++
;
uint32_t
off
=
add_literal
(
p
,
ptr
,
fs
-
ptr
);
arg_class_t
cls
=
classify_arg
(
ptr
,
(
int
)(
fs
-
ptr
));
emit_op
(
p
,
OP_EMIT_FMT
,
off
|
((
uint32_t
)
cls
<<
28
));
*
lit
=
fs
;
return
fs
;
}
static
const
char
*
scan_percent_escape
(
program_t
*
p
,
const
char
*
ptr
,
const
char
**
lit
)
{
flush_lit
(
p
,
*
lit
,
ptr
);
uint32_t
off
=
add_literal
(
p
,
"%%"
,
2
);
emit_op
(
p
,
OP_EMIT_LIT
,
off
);
*
lit
=
ptr
+
2
;
return
*
lit
;
}
program_t
*
cprintf_compile
(
const
char
*
fmt
)
{
program_t
*
p
=
program_new
();
var_table_t
vars
=
{
0
};
const
char
*
ptr
=
fmt
;
const
char
*
lit
=
ptr
;
while
(
*
ptr
)
{
if
(
*
ptr
==
'<'
)
ptr
=
scan_tag
(
p
,
ptr
,
&
lit
,
&
vars
);
else
if
(
*
ptr
==
'%'
&&
ptr
[
1
]
&&
ptr
[
1
]
!=
'%'
)
ptr
=
scan_fmt
(
p
,
ptr
,
&
lit
);
else
if
(
*
ptr
==
'%'
&&
ptr
[
1
]
==
'%'
)
ptr
=
scan_percent_escape
(
p
,
ptr
,
&
lit
);
else
ptr
++
;
}
flush_lit
(
p
,
lit
,
ptr
);
emit_op
(
p
,
OP_HALT
,
0
);
return
p
;
}
static
size_t
visible_len
(
const
char
*
s
,
size_t
n
)
{
size_t
vis
=
0
;
for
(
size_t
i
=
0
;
i
<
n
;
i
++
)
{
if
(
s
[
i
]
==
'\x1b'
)
while
(
++
i
<
n
&&
!
isalpha
(
s
[
i
]));
else
vis
++
;
}
return
vis
;
}
int
cprintf_vm_exec
(
program_t
*
prog
,
FILE
*
stream
,
va_list
ap
)
{
vm_regs_t
regs
=
{
0
};
size_t
cap
=
512
,
pos
=
0
;
char
*
out
=
malloc
(
cap
);
if
(
!
out
)
return
-1
;
#define ENSURE(n) ({ \
while (pos + (n) + 1 > cap) { \
cap *= 2; out = realloc(out, cap); \
if (!out) return -1; \
}})
#define OUT_STR(s, l) ({ ENSURE(l); memcpy(out+pos, s, l); pos += (l); })
#define OUT_CSTR(s) ({ size_t _l = strlen(s); OUT_STR(s, _l); })
const
instruction_t
*
ip
=
prog
->
code
;
static
const
void
*
dispatch
[
OP_MAX
]
=
{
[
OP_NOP
]
=
&&
op_nop
,
[
OP_EMIT_LIT
]
=
&&
op_emit_lit
,
[
OP_EMIT_FMT
]
=
&&
op_emit_fmt
,
[
OP_SET_FG
]
=
&&
op_set_fg
,
[
OP_SET_BG
]
=
&&
op_set_bg
,
[
OP_SET_FG_RGB
]
=
&&
op_set_fg_rgb
,
[
OP_SET_BG_RGB
]
=
&&
op_set_bg_rgb
,
[
OP_SET_BOLD
]
=
&&
op_set_bold
,
[
OP_SET_DIM
]
=
&&
op_set_dim
,
[
OP_SET_UL
]
=
&&
op_set_ul
,
[
OP_STYLE_PUSH
]
=
&&
op_style_push
,
[
OP_STYLE_FLUSH
]
=
&&
op_style_flush
,
[
OP_STYLE_RESET
]
=
&&
op_style_reset
,
[
OP_PAD_BEGIN
]
=
&&
op_pad_begin
,
[
OP_RPAD_BEGIN
]
=
&&
op_rpad_begin
,
[
OP_PAD_END
]
=
&&
op_pad_end
,
[
OP_HALT
]
=
&&
op_halt
,
};
#define DISPATCH() goto *dispatch[ip->op]
#define NEXT() do { ip++; DISPATCH(); } while(0)
DISPATCH
();
op_nop
:
NEXT
();
op_emit_lit
:
{
OUT_CSTR
(
prog
->
literals
+
ip
->
operand
);
NEXT
();
}
op_emit_fmt
:
{
uint32_t
lit_off
=
ip
->
operand
&
0x0FFFFFFF
;
arg_class_t
cls
=
(
arg_class_t
)(
ip
->
operand
>>
28
);
const
char
*
spec
=
prog
->
literals
+
lit_off
;
char
tmp
[
256
];
va_list
ap_copy
;
va_copy
(
ap_copy
,
ap
);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
int
n
=
vsnprintf
(
tmp
,
sizeof
(
tmp
),
spec
,
ap_copy
);
va_end
(
ap_copy
);
if
(
n
>
0
&&
(
size_t
)
n
<
sizeof
(
tmp
))
{
OUT_STR
(
tmp
,
(
size_t
)
n
);
}
else
if
(
n
>
0
)
{
ENSURE
((
size_t
)
n
);
va_copy
(
ap_copy
,
ap
);
vsnprintf
(
out
+
pos
,
(
size_t
)
n
+
1
,
spec
,
ap_copy
);
va_end
(
ap_copy
);
pos
+=
(
size_t
)
n
;
}
#pragma GCC diagnostic pop
switch
(
cls
)
{
case
ARG_INT
:
(
void
)
va_arg
(
ap
,
int
);
break
;
case
ARG_LONG
:
(
void
)
va_arg
(
ap
,
long
);
break
;
case
ARG_LLONG
:
(
void
)
va_arg
(
ap
,
long
long
);
break
;
case
ARG_SIZE
:
(
void
)
va_arg
(
ap
,
size_t
);
break
;
case
ARG_DOUBLE
:
(
void
)
va_arg
(
ap
,
double
);
break
;
case
ARG_CSTR
:
(
void
)
va_arg
(
ap
,
const
char
*
);
break
;
case
ARG_PTR
:
(
void
)
va_arg
(
ap
,
void
*
);
break
;
case
ARG_WINT
:
(
void
)
va_arg
(
ap
,
wint_t
);
break
;
case
ARG_WSTR
:
(
void
)
va_arg
(
ap
,
wchar_t
*
);
break
;
case
ARG_NONE
:
break
;
}
NEXT
();
}
op_set_fg
:
{
regs
.
fg
=
ip
->
operand
;
NEXT
();
}
op_set_bg
:
{
regs
.
bg
=
ip
->
operand
;
NEXT
();
}
op_set_bold
:
{
regs
.
bold
=
ip
->
operand
;
NEXT
();
}
op_set_dim
:
{
regs
.
dim
=
ip
->
operand
;
NEXT
();
}
op_set_ul
:
{
regs
.
ul
=
ip
->
operand
;
NEXT
();
}
op_set_fg_rgb
:
{
regs
.
fg
=
COL_RGB
;
regs
.
fg_rgb
=
ip
->
operand
;
NEXT
();
}
op_set_bg_rgb
:
{
regs
.
bg
=
COL_RGB
;
regs
.
bg_rgb
=
ip
->
operand
;
NEXT
();
}
op_style_push
:
{
if
(
regs
.
style_depth
<
8
)
regs
.
style_stack
[
regs
.
style_depth
++
]
=
(
style_entry_t
){
regs
.
fg
,
regs
.
bg
,
regs
.
fg_rgb
,
regs
.
bg_rgb
,
regs
.
bold
,
regs
.
dim
,
regs
.
ul
,
};
NEXT
();
}
op_pad_begin
:
{
if
(
regs
.
pad_depth
<
8
)
regs
.
pad_stack
[
regs
.
pad_depth
++
]
=
(
pad_entry_t
){
pos
,
(
int
)
ip
->
operand
,
0
};
NEXT
();
}
op_rpad_begin
:
{
if
(
regs
.
pad_depth
<
8
)
regs
.
pad_stack
[
regs
.
pad_depth
++
]
=
(
pad_entry_t
){
pos
,
(
int
)
ip
->
operand
,
1
};
NEXT
();
}
op_pad_end
:
{
if
(
regs
.
pad_depth
<=
0
)
NEXT
();
regs
.
pad_depth
--
;
pad_entry_t
pe
=
regs
.
pad_stack
[
regs
.
pad_depth
];
size_t
vis
=
visible_len
(
out
+
pe
.
mark
,
pos
-
pe
.
mark
);
if
((
size_t
)
pe
.
width
<=
vis
)
NEXT
();
size_t
pad_n
=
pe
.
width
-
vis
;
ENSURE
(
pad_n
);
if
(
pe
.
right_align
)
{
memmove
(
out
+
pe
.
mark
+
pad_n
,
out
+
pe
.
mark
,
pos
-
pe
.
mark
);
memset
(
out
+
pe
.
mark
,
' '
,
pad_n
);
}
else
memset
(
out
+
pos
,
' '
,
pad_n
);
pos
+=
pad_n
;
NEXT
();
}
op_style_reset
:
{
if
(
regs
.
style_depth
>
0
)
{
style_entry_t
se
=
regs
.
style_stack
[
--
regs
.
style_depth
];
regs
.
fg
=
se
.
fg
;
regs
.
bg
=
se
.
bg
;
regs
.
fg_rgb
=
se
.
fg_rgb
;
regs
.
bg_rgb
=
se
.
bg_rgb
;
regs
.
bold
=
se
.
bold
;
regs
.
dim
=
se
.
dim
;
regs
.
ul
=
se
.
ul
;
}
else
{
regs
.
fg
=
COL_NONE
;
regs
.
bg
=
COL_NONE
;
regs
.
fg_rgb
=
0
;
regs
.
bg_rgb
=
0
;
regs
.
bold
=
0
;
regs
.
dim
=
0
;
regs
.
ul
=
0
;
}
if
(
!
io_no_color
)
{
char
esc
[
128
];
int
n
=
snprintf
(
esc
,
sizeof
(
esc
),
"
\x1b
[0m"
);
if
(
regs
.
bold
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[1m"
);
if
(
regs
.
dim
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[2m"
);
if
(
regs
.
ul
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[4m"
);
if
(
regs
.
fg
==
COL_RGB
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[38;2;%d;%d;%dm"
,
UNPACK_R
(
regs
.
fg_rgb
),
UNPACK_G
(
regs
.
fg_rgb
),
UNPACK_B
(
regs
.
fg_rgb
));
else
if
(
regs
.
fg
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[%dm"
,
regs
.
fg
);
if
(
regs
.
bg
==
COL_RGB
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[48;2;%d;%d;%dm"
,
UNPACK_R
(
regs
.
bg_rgb
),
UNPACK_G
(
regs
.
bg_rgb
),
UNPACK_B
(
regs
.
bg_rgb
));
else
if
(
regs
.
bg
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[%dm"
,
regs
.
bg
+
10
);
OUT_STR
(
esc
,
(
size_t
)
n
);
}
NEXT
();
}
op_style_flush
:
{
if
(
!
io_no_color
)
{
char
esc
[
128
];
int
n
=
snprintf
(
esc
,
sizeof
(
esc
),
"
\x1b
[0m"
);
if
(
regs
.
bold
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[1m"
);
if
(
regs
.
dim
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[2m"
);
if
(
regs
.
ul
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[4m"
);
if
(
regs
.
fg
==
COL_RGB
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[38;2;%d;%d;%dm"
,
UNPACK_R
(
regs
.
fg_rgb
),
UNPACK_G
(
regs
.
fg_rgb
),
UNPACK_B
(
regs
.
fg_rgb
));
else
if
(
regs
.
fg
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[%dm"
,
regs
.
fg
);
if
(
regs
.
bg
==
COL_RGB
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[48;2;%d;%d;%dm"
,
UNPACK_R
(
regs
.
bg_rgb
),
UNPACK_G
(
regs
.
bg_rgb
),
UNPACK_B
(
regs
.
bg_rgb
));
else
if
(
regs
.
bg
)
n
+=
snprintf
(
esc
+
n
,
sizeof
(
esc
)
-
n
,
"
\x1b
[%dm"
,
regs
.
bg
+
10
);
OUT_STR
(
esc
,
(
size_t
)
n
);
}
NEXT
();
}
op_halt
:
{
out
[
pos
]
=
'\0'
;
int
ret
=
(
int
)
fwrite
(
out
,
1
,
pos
,
stream
);
free
(
out
);
return
ret
;
}
#undef ENSURE
#undef OUT_STR
#undef OUT_CSTR
}
int
cprintf_exec
(
program_t
*
prog
,
FILE
*
stream
,
...)
{
va_list
ap
;
va_start
(
ap
,
stream
);
int
ret
=
cprintf_vm_exec
(
prog
,
stream
,
ap
);
va_end
(
ap
);
return
ret
;
}
int
csprintf_inner
(
program_t
*
prog
,
char
*
buf
,
size_t
size
,
...)
{
FILE
*
f
=
fmemopen
(
buf
,
size
,
"w"
);
if
(
!
f
)
return
-1
;
va_list
ap
;
va_start
(
ap
,
size
);
int
ret
=
cprintf_vm_exec
(
prog
,
f
,
ap
);
va_end
(
ap
);
fclose
(
f
);
return
ret
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, Aug 1, 12:07 PM (14 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
603466
Default Alt Text
cprintf.c (21 KB)
Attached To
Mode
rANT Ant
Attached
Detach File
Event Timeline
Log In to Comment