Skip to content

Commit

Permalink
Add support for binary numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
uxcn committed Jan 16, 2016
1 parent dddbf9c commit 7ad3f07
Show file tree
Hide file tree
Showing 17 changed files with 350 additions and 95 deletions.
19 changes: 16 additions & 3 deletions runtime/doc/change.txt
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,10 @@ CTRL-X Subtract [count] from the number or alphabetic
character at or after the cursor.

The CTRL-A and CTRL-X commands work for (signed) decimal numbers, unsigned
octal and hexadecimal numbers and alphabetic characters. This depends on the
'nrformats' option.
binary/octal/hexadecimal numbers and alphabetic characters. This
depends on the 'nrformats' option.
- When 'nrformats' includes "bin", Vim considers numbers starting with '0b' or
'0B' as binary.
- When 'nrformats' includes "octal", Vim considers numbers starting with a '0'
to be octal, unless the number includes a '8' or '9'. Other numbers are
decimal and may have a preceding minus sign.
Expand All @@ -386,6 +388,10 @@ octal and hexadecimal numbers and alphabetic characters. This depends on the
under or after the cursor. This is useful to make lists with an alphabetic
index.

For decimals a leading negative sign is considered for incrementing or
decrementing, for binary and octal and hex values, it won't be considered. To
ignore the sign Visually select the number before using CTRL-A or CTRL-X.

For numbers with leading zeros (including all octal and hexadecimal numbers),
Vim preserves the number of characters in the number when possible. CTRL-A on
"0077" results in "0100", CTRL-X on "0x100" results in "0x0ff".
Expand All @@ -397,6 +403,10 @@ octal number.
Note that when 'nrformats' includes "octal", decimal numbers with leading
zeros cause mistakes, because they can be confused with octal numbers.

Note similarly, when 'nrformats' includes "bin", binary numbers with a leading
'0x' or '0X' can be interpreted as hexadecimal rather than binary since '0b'
are valid hexadecimal digits.

The CTRL-A command is very useful in a macro. Example: Use the following
steps to make a numbered list.

Expand Down Expand Up @@ -1602,7 +1612,7 @@ Vim has a sorting function and a sorting command. The sorting function can be
found here: |sort()|, |uniq()|.

*:sor* *:sort*
:[range]sor[t][!] [i][u][r][n][x][o] [/{pattern}/]
:[range]sor[t][!] [i][u][r][n][x][o][b] [/{pattern}/]
Sort lines in [range]. When no range is given all
lines are sorted.

Expand All @@ -1622,6 +1632,9 @@ found here: |sort()|, |uniq()|.
With [o] sorting is done on the first octal number in
the line (after or inside a {pattern} match).

With [b] sorting is done on the first binary number in
the line (after or inside a {pattern} match).

With [u] only keep the first of a sequence of
identical lines (ignoring case when [i] is used).
Without this flag, a sequence of identical lines
Expand Down
44 changes: 25 additions & 19 deletions runtime/doc/eval.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,16 @@ the Number. Examples:
Number 0 --> String "0" ~
Number -1 --> String "-1" ~
*octal*
Conversion from a String to a Number is done by converting the first digits
to a number. Hexadecimal "0xf9" and Octal "017" numbers are recognized. If
the String doesn't start with digits, the result is zero. Examples:
Conversion from a String to a Number is done by converting the first digits to
a number. Hexadecimal "0xf9", Octal "017", and Binary "0b10" numbers are
recognized. If the String doesn't start with digits, the result is zero.
Examples:
String "456" --> Number 456 ~
String "6bar" --> Number 6 ~
String "foo" --> Number 0 ~
String "0xf1" --> Number 241 ~
String "0100" --> Number 64 ~
String "0b101" --> Number 5 ~
String "-8" --> Number -8 ~
String "+8" --> Number 0 ~

Expand Down Expand Up @@ -4912,6 +4914,9 @@ printf({fmt}, {expr1} ...) *printf()*
%c single byte
%d decimal number
%5d decimal number padded with spaces to 5 characters
%b binary number
%08b binary number padded with zeros to at least 8 characters
%B binary number using upper case letters
%x hex number
%04x hex number padded with zeros to at least 4 characters
%X hex number using upper case letters
Expand Down Expand Up @@ -4998,20 +5003,19 @@ printf({fmt}, {expr1} ...) *printf()*

The conversion specifiers and their meanings are:

*printf-d* *printf-o* *printf-x* *printf-X*
doxX The Number argument is converted to signed decimal
(d), unsigned octal (o), or unsigned hexadecimal (x
and X) notation. The letters "abcdef" are used for
x conversions; the letters "ABCDEF" are used for X
conversions.
The precision, if any, gives the minimum number of
digits that must appear; if the converted value
requires fewer digits, it is padded on the left with
zeros.
In no case does a non-existent or small field width
cause truncation of a numeric field; if the result of
a conversion is wider than the field width, the field
is expanded to contain the conversion result.
*printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X*
dbBoxX The Number argument is converted to signed decimal (d),
unsigned binary (b and B), unsigned octal (o), or
unsigned hexadecimal (x and X) notation. The letters
"abcdef" are used for x conversions; the letters
"ABCDEF" are used for X conversions. The precision, if
any, gives the minimum number of digits that must
appear; if the converted value requires fewer digits, it
is padded on the left with zeros. In no case does a
non-existent or small field width cause truncation of a
numeric field; if the result of a conversion is wider
than the field width, the field is expanded to contain
the conversion result.

*printf-c*
c The Number argument is converted to a byte, and the
Expand Down Expand Up @@ -6127,12 +6131,14 @@ str2float( {expr}) *str2float()*
str2nr( {expr} [, {base}]) *str2nr()*
Convert string {expr} to a number.
{base} is the conversion base, it can be 8, 10 or 16.
{base} is the conversion base, it can be 2, 8, 10 or 16.
When {base} is omitted base 10 is used. This also means that
a leading zero doesn't cause octal conversion to be used, as
with the default String to Number conversion.
When {base} is 16 a leading "0x" or "0X" is ignored. With a
different base the result will be zero.
different base the result will be zero. Similarly, when {base}
is 8 a leading "0" is ignored, and when {base} is 2 a leading
"0b" or "0B" is ignored.
Text after the number is silently ignored.


Expand Down
8 changes: 8 additions & 0 deletions src/nvim/ascii.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ static inline bool ascii_isxdigit(int c)
|| (c >= 'A' && c <= 'F');
}

/// Checks if `c` is a binary digit, that is, 0-1.
///
/// @see {ascii_isdigit}
static inline bool ascii_isbdigit(int c)
{
return (c == '0' || c == '1');
}

/// Checks if `c` is a white-space character, that is,
/// one of \f, \n, \r, \t, \v.
///
Expand Down
85 changes: 66 additions & 19 deletions src/nvim/charset.c
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,20 @@ char_u* skipdigits(char_u *q)
return p;
}

/// skip over binary digits
///
/// @param q
///
/// @return Pointer to the character after the skipped digits.
char_u* skipbin(char_u *q)
{
char_u *p = q;

while (ascii_isbdigit(*p)) /* skip to next non-digit */
++p;
return p;
}

/// skip over digits and hex characters
///
/// @param q
Expand Down Expand Up @@ -1485,6 +1499,20 @@ char_u* skiptodigit(char_u *q)
return p;
}

/// skip to binary character (or NUL after the string)
///
/// @param q
///
/// @return Pointer to the binary character or (NUL after the string).
char_u* skiptobin(char_u *q)
{
char_u *p = q;

while (*p != NUL && !ascii_isbdigit(*p)) /* skip to next digit */
++p;
return p;
}

/// skip to hex character (or NUL after the string)
///
/// @param q
Expand Down Expand Up @@ -1720,33 +1748,38 @@ int vim_isblankline(char_u *lbuf)
}

/// Convert a string into a long and/or unsigned long, taking care of
/// hexadecimal and octal numbers. Accepts a '-' sign.
/// If "hexp" is not NULL, returns a flag to indicate the type of the number:
/// hexadecimal, octal and binary numbers. Accepts a '-' sign.
/// If "prep" is not NULL, returns a flag to indicate the type of the number:
/// 0 decimal
/// '0' octal
/// 'B' bin
/// 'b' bin
/// 'X' hex
/// 'x' hex
/// If "len" is not NULL, the length of the number in characters is returned.
/// If "nptr" is not NULL, the signed result is returned in it.
/// If "unptr" is not NULL, the unsigned result is returned in it.
/// If "dobin" is non-zero recognize binary numbers, when > 1 always assume
/// binary number.
/// If "dooct" is non-zero recognize octal numbers, when > 1 always assume
/// octal number.
/// If "dohex" is non-zero recognize hex numbers, when > 1 always assume
/// hex number.
///
/// @param start
/// @param hexp Returns type of number 0 = decimal, 'x' or 'X' is hex,
// '0' = octal
/// @param prep Returns type of number 0 = decimal, 'x' or 'X' is hex,
// '0' = octal, 'b' or 'B' is bin
/// @param len Returns the detected length of number.
/// @param dobin recognize binary number
/// @param dooct recognize octal number
/// @param dohex recognize hex number
/// @param nptr Returns the signed result.
/// @param unptr Returns the unsigned result.
void vim_str2nr(char_u *start, int *hexp, int *len, int dooct, int dohex,
void vim_str2nr(char_u *start, int *prep, int *len, int dobin, int dooct, int dohex,
long *nptr, unsigned long *unptr)
{
char_u *ptr = start;
int hex = 0; // default is decimal
int pre = 0; // default is decimal
int negative = FALSE;
unsigned long un = 0;
int n;
Expand All @@ -1756,60 +1789,74 @@ void vim_str2nr(char_u *start, int *hexp, int *len, int dooct, int dohex,
++ptr;
}

// Recognize hex and octal.
// Recognize hex, octal, and bin.
if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')) {
hex = ptr[1];
pre = ptr[1];

if (dohex
&& ((hex == 'X') || (hex == 'x'))
&& ((pre == 'X') || (pre == 'x'))
&& ascii_isxdigit(ptr[2])) {
// hexadecimal
ptr += 2;
} else if (dobin
&& ((pre == 'B') || (pre == 'b'))
&& ascii_isbdigit(ptr[2])) {
// binary
ptr += 2;
} else {
// default is decimal
hex = 0;
pre = 0;

if (dooct) {
// Don't interpret "0", "08" or "0129" as octal.
for (n = 1; ascii_isdigit(ptr[n]); ++n) {
if (ptr[n] > '7') {
// can't be octal
hex = 0;
pre = 0;
break;
}

if (ptr[n] >= '0') {
// assume octal
hex = '0';
pre = '0';
}
}
}
}
}

// Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
if ((hex == '0') || (dooct > 1)) {
if ((pre == 'B') || (pre == 'b') || (dobin > 1)) {
// bin
if (pre != 0)
n += 2; // skip over "0b"
while ('0' <= *ptr && *ptr <= '1') {
un = 2 * un + (unsigned long)(*ptr - '0');
++ptr;
}
} else if ((pre == '0') || (dooct > 1)) {
// octal
while ('0' <= *ptr && *ptr <= '7') {
un = 8 * un + (unsigned long)(*ptr - '0');
ptr++;
}
} else if ((hex != 0) || (dohex > 1)) {
} else if (pre != 0 || dohex > 1) {
// hex
if (pre != 0)
n += 2; // skip over "0x"
while (ascii_isxdigit(*ptr)) {
un = 16 * un + (unsigned long)hex2nr(*ptr);
ptr++;
++ptr;
}
} else {
// decimal
while (ascii_isdigit(*ptr)) {
un = 10 * un + (unsigned long)(*ptr - '0');
ptr++;
++ptr;
}
}

if (hexp != NULL) {
*hexp = hex;
if (prep != NULL) {
*prep = pre;
}

if (len != NULL) {
Expand Down
10 changes: 5 additions & 5 deletions src/nvim/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,7 @@ call_vim_function (
len = 0;
else
/* Recognize a number argument, the others must be strings. */
vim_str2nr(argv[i], NULL, &len, TRUE, TRUE, &n, NULL);
vim_str2nr(argv[i], NULL, &len, TRUE, TRUE, TRUE, &n, NULL);
if (len != 0 && len == (int)STRLEN(argv[i])) {
argvars[i].v_type = VAR_NUMBER;
argvars[i].vval.v_number = n;
Expand Down Expand Up @@ -4127,7 +4127,7 @@ eval7 (
rettv->vval.v_float = f;
}
} else {
vim_str2nr(*arg, NULL, &len, TRUE, TRUE, &n, NULL);
vim_str2nr(*arg, NULL, &len, TRUE, TRUE, TRUE, &n, NULL);
*arg += len;
if (evaluate) {
rettv->v_type = VAR_NUMBER;
Expand Down Expand Up @@ -15982,7 +15982,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv)

if (argvars[1].v_type != VAR_UNKNOWN) {
base = get_tv_number(&argvars[1]);
if (base != 8 && base != 10 && base != 16) {
if (base != 2 && base != 8 && base != 10 && base != 16) {
EMSG(_(e_invarg));
return;
}
Expand All @@ -15991,7 +15991,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv)
p = skipwhite(get_tv_string(&argvars[0]));
if (*p == '+')
p = skipwhite(p + 1);
vim_str2nr(p, NULL, NULL, base == 8 ? 2 : 0, base == 16 ? 2 : 0, &n, NULL);
vim_str2nr(p, NULL, NULL, base == 2 ? 2 : 0, base == 8 ? 2 : 0, base == 16 ? 2 : 0, &n, NULL);
rettv->vval.v_number = n;
}

Expand Down Expand Up @@ -18273,7 +18273,7 @@ long get_tv_number_chk(typval_T *varp, int *denote)
case VAR_STRING:
if (varp->vval.v_string != NULL)
vim_str2nr(varp->vval.v_string, NULL, NULL,
TRUE, TRUE, &n, NULL);
TRUE, TRUE, TRUE, &n, NULL);
return n;
case VAR_LIST:
EMSG(_("E745: Using a List as a Number"));
Expand Down

0 comments on commit 7ad3f07

Please sign in to comment.