bc

bc

part of shnell – a source to source compiler enhancement tool

© Jens Gustedt, 2019

A evaluate constant expressions with POSIX’ bc tool.

This directive spans the whole text that it amends for expressions that are enclosed in special brackets and performs computation on them.

Usage

#pragma CMOD amend bc [LEFTPAREN RIGHTPAREN]

Where LEFTPAREN and RIGHTPAREN can be any strings that help you visually distinguish processed expressions from the rest of the programming text. E.g

#pragma CMOD amend bc ⟦ ⟧

processes all expressions such as ⟦2 ^ 9⟧ or ⟦4*atan(1)⟧ through bc, and replaces them with their value, here 512 and 3.14159265358979323844.

For details on the capacity of bc to do computations see the POSIX documentation. Beware that bc is C-like but not completely C-conforming. In particular, it has no bit operations (^, |, &, << and >>) and the ^ character is used for the exponentiation operation. So in the above example the C expression corresponding to ⟦2 ^ 9⟧ would have been 1 << 9.

The spacing around such replacements should remain intact, such that you may suffix such expressions with the appropriate C number suffix that you want to give the number. E.g ⟦2 ^ 9⟧ULL would result in 512ULL, the value 512 with a type of unsigned long long.

Complicated “programs”

bc is a whole programming language, that allows much more than just evaluating simple expressions. If we detect ;, { or } we switch to a “complicated” processing mode that ensures that changes to the state in one processed expression do not pollute the result of other expressions. The result that such an expression has should then be assigned to the bc variable r.

You may start your bc programs with an auto list of variables, but beware that bc itself already uses the letters a, c, e, j, l, and s for functions, and that we internally use the letters o, p, q and r. So you’d better not chose a letter that conflicts with these. The “variable declaration” part starting with the keyword auto must always be at the beginning of the expression, consist of a list of variables or arrays and be terminated by a ; token.

Example:

⟦scale=200 ; r=4*atan(1)⟧

sets the precision of computation temporarily to 200 digits and returns the value of 𝝅 with that precision.

⟦auto i; for (i=0; i < 10; i++) { r += 2*i + 1; }⟧

is a complicated way to express the value 100.

You may use meta-variable from do, let, foreach, env or bind directives if you expand them in the right order:

⟦auto i; for (i=0; i < ${DIM}; i++) { r += 2*i + 1; }⟧

is a complicated way to express the value ${DIM}*${DIM}, where ${DIM} should resolve to an integer constant provided by one of the directives as listed above.

User functions

Within the limits of bc’ capacities (one-letter names!) , you can define your own functions. Whenever an expression starts with the word define this is considered to contain a function defintion. It is passed into bc as-is and is by itself supposed to not have a return value. The text is replaced by the expression itself, so you better put all of this inside a C comment.

As mentionned above, bc already uses some letters for predefined functions, and we restrict the choice even further. So unfortunately it would not be easy implement a general function library for bc.

Example:

The follwing lines also compute 𝝅:

// Add a function to the bc state: ⟦define q(x) { return a(x); }⟧
double pi = ⟦4*q(1)⟧;

but the replacement removes the brackets but conserves the text in the comment

// Add a function to the bc state: define q(x) { return a(x ) ; }
double pi = 3.14159265358979323844;

Mathematical functions

We use the bc tool with the -l option to ensure that the “math library” is effective. This enables limited support for mathematical functions, but only sqrt is a builtin, directly usable as we know it. We translate the names of the following mathematical functions to bc’s supported one character function names: atan, sin, cos, log, exp, and jn.

BC="${BC:-bc -l}"

Coding and configuration

The following code is needed to enable the sh-module framework.

SRC="$_" . "${0%%/${0##*/}}/import.sh"

Imports

The following sh-modules are imported:

Details

Supported bc implementations

Currently we use a POSIX specific hack that ensures that bc is run with line buffering. This speculates on the fact that if bc also has an “buffered” mode with larger buffers, it will check with POSX’ isatty call if it is attached to a terminal. We don’t know if this would work with other implementations. See ontty.

Implementation

This feature is implemented by starting a bc “server” process and sending the expressions that are to be processed on separate lines. We hope that this should be a bit faster than launching a separate process of each evaluation, but we have not hard evidence for that, yet. You could take all of this just as an example for such a “server” based approach.