|  |  | "Show a draft of complex.hpp
 and Sketch parser_expressions.cpp changes.  Since this will be implemented at
the source code level, lowercase i is the preferred embodiment, and should be a
reserved keyword.  The capitalized identifier is only a restriction for code
written in sdl to be scanned by the parser, to avoid lowercase reserved
keywords.  I would like both complex (a,b)+i as well as cis(theta) capability,
however the standard a+bi notation would be most preferred if not too difficult
to implement."
sketch of the parser changes to support complex numbers, including:
- `complex(a,b)` and `cis(theta)` constructors,
- a **lower‑case** reserved keyword **`i`** (imaginary unit),
- and the **`a + b i`** notation via a numeric‑literal **suffix `i`** (so
`3i` becomes a single imaginary literal, not an identifier mash‑up).
notes and citations inline.
---
Create a small, standalone header you can include from `base/types.h` or
directly from the parser. It avoids `std::complex` to keep numerics and ABI
under your control (but you *can* switch internally later).
```cpp
// File: source/base/complex.hpp
// Lightweight complex number support for POV-Ray parser & function VM.
// Depends only on <cmath> and POV-Ray's DBL typedef.
#ifndef POV_BASE_COMPLEX_HPP
#define POV_BASE_COMPLEX_HPP
#include <cmath>
#include "base/types.h" // for DBL
namespace pov {
struct Complex
{
    DBL r; // real
    DBL i; // imag
    // --- ctors ---
    constexpr Complex() : r(DBL(0)), i(DBL(0)) {}
    constexpr explicit Complex(DBL real) : r(real), i(DBL(0)) {}
    constexpr Complex(DBL real, DBL imag) : r(real), i(imag) {}
    // --- unary ---
    constexpr Complex operator+() const { return *this; }
    constexpr Complex operator-() const { return Complex(-r, -i); }
    // --- basic ops (component formulas) ---
    friend inline Complex operator+(const Complex& a, const Complex& b) { return
Complex(a.r + b.r, a.i + b.i); }
    friend inline Complex operator-(const Complex& a, const Complex& b) { return
Complex(a.r - b.r, a.i - b.i); }
    friend inline Complex operator*(const Complex& a, const Complex& b)
    {
        return Complex(a.r*b.r - a.i*b.i, a.r*b.i + a.i*b.r);
    }
    friend inline Complex operator/(const Complex& a, const Complex& b)
    {
        // Smith's algorithm for numerical stability
        DBL ar = std::fabs(b.r), ai = std::fabs(b.i);
        if (ar >= ai) {
            DBL t = b.i / b.r;
            DBL d = b.r + b.i * t;
            return Complex((a.r + a.i * t) / d, (a.i - a.r * t) / d);
        } else {
            DBL t = b.r / b.i;
            DBL d = b.r * t + b.i;
            return Complex((a.r * t + a.i) / d, (a.i * t - a.r) / d);
        }
    }
    // compound
    Complex& operator+=(const Complex& o){ r+=o.r; i+=o.i; return *this; }
    Complex& operator-=(const Complex& o){ r-=o.r; i-=o.i; return *this; }
    Complex& operator*=(const Complex& o){ DBL nr=r*o.r - i*o.i; i = r*o.i +
i*o.r; r = nr; return *this; }
    Complex& operator/=(const Complex& o){ *this = *this / o; return *this; }
};
inline Complex operator+(const Complex& z, DBL x){ return Complex(z.r + x, z.i);
}
inline Complex operator-(const Complex& z, DBL x){ return Complex(z.r - x, z.i);
}
inline Complex operator*(const Complex& z, DBL x){ return Complex(z.r * x, z.i *
x); }
inline Complex operator/(const Complex& z, DBL x){ return Complex(z.r / x, z.i /
x); }
inline Complex operator+(DBL x, const Complex& z){ return Complex(x + z.r, z.i);
}
inline Complex operator-(DBL x, const Complex& z){ return Complex(x - z.r,
-z.i); }
inline Complex operator*(DBL x, const Complex& z){ return Complex(x * z.r, x *
z.i); }
inline Complex operator/(DBL x, const Complex& z){ return Complex(x, DBL(0)) /
z; }
// comparisons: only == / != are defined (lexical epsilon-like)
inline bool operator==(const Complex& a, const Complex& b)
{
    // Use POV-Ray float equal semantics elsewhere; here a simple exact compare.
    return (a.r == b.r) && (a.i == b.i);
}
inline bool operator!=(const Complex& a, const Complex& b){ return !(a==b); }
// --- elementary functions ---
inline Complex conj(const Complex& z){ return Complex(z.r, -z.i); }
inline DBL     abs (const Complex& z){ return std::hypot(z.r, z.i); } //
magnitude
inline DBL     arg (const Complex& z){ return std::atan2(z.i, z.r); }
// exp(x+iy) = exp(x) (cos y + i sin y)
inline Complex cexp(const Complex& z)
{
    DBL ex = std::exp(z.r);
    DBL cy = std::cos(z.i), sy = std::sin(z.i);
    return Complex(ex * cy, ex * sy);
}
// log z = ln|z| + i arg(z)  (principal branch)
inline Complex clog(const Complex& z)
{
    return Complex(std::log(abs(z)), arg(z));
}
// pow(z,w) = exp(w * log z)  (principal value)
inline Complex cpow(const Complex& z, const Complex& w)
{
    return cexp( Complex(w.r * clog(z).r - w.i * clog(z).i,
                         w.r * clog(z).i + w.i * clog(z).r) );
}
inline Complex csqrt(const Complex& z)
{
    // principal square root
    DBL m = abs(z);
    DBL s = std::sqrt( (m + std::fabs(z.r)) / DBL(2) );
    DBL d = (s == 0) ? 0 : (z.i / (2*s));
    return (z.r >= 0) ? Complex(s, d) : Complex(d, (z.i >= 0 ? s : -s));
}
// trig via Euler
inline Complex csin(const Complex& z)
{
    // sin(x+iy) = sin x cosh y + i cos x sinh y
    return Complex( std::sin(z.r) * std::cosh(z.i),
                    std::cos(z.r) * std::sinh(z.i) );
}
inline Complex ccos(const Complex& z)
{
    // cos(x+iy) = cos x cosh y - i sin x sinh y
    return Complex( std::cos(z.r) * std::cosh(z.i),
                   -std::sin(z.r) * std::sinh(z.i) );
}
inline Complex ctan(const Complex& z)
{
    return csin(z) / ccos(z);
}
// hyperbolic
inline Complex csinh(const Complex& z){ return (cexp(z) -
cexp(Complex(-z.r,-z.i))) * DBL(0.5); }
inline Complex ccosh(const Complex& z){ return (cexp(z) +
cexp(Complex(-z.r,-z.i))) * DBL(0.5); }
inline Complex ctanh(const Complex& z){ return csinh(z) / ccosh(z); }
// helper constructors
inline Complex cis(DBL theta) { return Complex(std::cos(theta),
std::sin(theta)); }      // e^{i θ}
inline Complex make_complex(DBL re, DBL im){ return Complex(re, im); }
static const Complex I(0.0, 1.0); // imaginary unit; lower-case 'i' will be
reserved in the parser
} // namespace pov
#endif // POV_BASE_COMPLEX_HPP
```
**Why this way?**
- Uses `DBL` consistently with parser math.
- Implements numerically stable division and a principal‑value `pow/log`,
matching how float `pow/log` are documented to behave for scalars today.
---
Below is a *surgical* diff‑style sketch showing where to add complex
support. It assumes:
- you add a new **value kind** `VALUE_COMPLEX`,
- a **token** for `IMAGINARY_LITERAL` (produced by the tokenizer when it sees
`123i`), and
- a **built‑in identifier** `i` bound to `pov::I`.
The existing parser has centralized expression handling with helper methods that
### 2.1 Extend value representation
In (or near) `parsertypes.*`, add a complex arm to the variant; then include the
header.
```cpp
// parsertypes.h (illustrative)
enum ValueKind { VALUE_FLOAT, VALUE_VECTOR, VALUE_COLOR, VALUE_STRING, /*...*/,
VALUE_COMPLEX };
struct Value
{
    ValueKind kind;
    union {
        DBL    f;
        Vector v;   // existing
        // ...
        pov::Complex c;
    } u;
    // helpers...
    static Value FromComplex(const pov::Complex& z) {
        Value v; v.kind = VALUE_COMPLEX; v.u.c = z; return v;
    }
};
```
### 2.2 Recognize complex literals and the `i` keyword
Parser‑side:
```cpp
// parser_expressions.cpp : inside primary/atom expression parsing
switch (token.Type)
{
    case FLOAT_LITERAL_TOKEN:
        return Value::FromFloat(token.float_value);
    case IMAGINARY_LITERAL_TOKEN: // e.g. "3.5i" produced by tokenizer
        return Value::FromComplex(pov::Complex(0.0, token.float_value));
    case IDENT_TOKEN:
        if (token.is_builtin_i) { // lowercase 'i'
            return Value::FromComplex(pov::I);
        }
        // ... existing identifier handling ...
        break;
    case COMPLEX_KW_TOKEN: // "complex(" ... ")" treated as literal ctor
        // parse '(' expr ',' expr ')', both must be float-like
        {
            DBL re = ParseFloatExpression();
            Expect(COMMA_TOKEN);
            DBL im = ParseFloatExpression();
            Expect(RPAREN_TOKEN);
            return Value::FromComplex(pov::Complex(re, im));
        }
    case CIS_KW_TOKEN: // "cis("
        {
            DBL theta = ParseFloatExpression();
            Expect(RPAREN_TOKEN);
            return Value::FromComplex(pov::cis(theta));
        }
    // ...
}
```
*(Token names are illustrative; wire them to your actual enums. The important
part is: the parser sees both constructors as **literal** expressions, not as
general function calls.)*
### 2.3 Promotion helpers
Add small helpers to unify arithmetic:
```cpp
inline Value PromoteToComplex(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return v;
    if (v.kind == VALUE_FLOAT)  return Value::FromComplex( pov::Complex(v.u.f,
0.0) );
    // vectors/colors/strings are not implicitly promotable; throw parse error.
    SyntaxError("Complex arithmetic requires float or complex operands.");
    return v; // unreachable
}
```
### 2.4 Binary operators `+ - * /`
Extend the binary‑op reducer:
```cpp
Value Parser::ReduceBinaryOp(TokenType op, const Value& lhs, const Value& rhs)
{
    // Complex cases first (covers mixed real/complex via promotion)
    if (lhs.kind == VALUE_COMPLEX || rhs.kind == VALUE_COMPLEX)
    {
        pov::Complex a = (lhs.kind == VALUE_COMPLEX) ? lhs.u.c :
pov::Complex(lhs.u.f, 0.0);
        pov::Complex b = (rhs.kind == VALUE_COMPLEX) ? rhs.u.c :
pov::Complex(rhs.u.f, 0.0);
        switch (op) {
            case PLUS_TOKEN:  return Value::FromComplex(a + b);
            case MINUS_TOKEN: return Value::FromComplex(a - b);
            case MULT_TOKEN:  return Value::FromComplex(a * b);
            case DIV_TOKEN:   return Value::FromComplex(a / b);
            default: break; // fall through
        }
    }
    // ... existing float/vector operator logic ...
}
```
### 2.5 Unary minus
```cpp
Value Parser::ReduceUnaryMinus(const Value& x)
{
    if (x.kind == VALUE_COMPLEX) return Value::FromComplex( -x.u.c );
    if (x.kind == VALUE_FLOAT)  return Value::FromFloat( -x.u.f );
    // vectors/colors: existing behavior
    // ...
}
```
### 2.6 Function call dispatch (`pow`, `abs`, `re`, `im`, `arg`, `cis`, etc.)
Where scalar functions are resolved (typically in `parser_functions*.cpp`), add
the complex variants:
```cpp
// abs(z) : float magnitude when argument is complex; preserve float abs for
float
Value Parser::CallAbs(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return Value::FromFloat( pov::abs(v.u.c) );
    // existing float abs
}
// re(z) / im(z) / arg(z)
Value Parser::CallRe(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return Value::FromFloat( v.u.c.r );
    SyntaxError("re() expects a complex argument.");
}
Value Parser::CallIm(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return Value::FromFloat( v.u.c.i );
    SyntaxError("im() expects a complex argument.");
}
Value Parser::CallArg(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return Value::FromFloat( pov::arg(v.u.c) );
    SyntaxError("arg() expects a complex argument.");
}
// pow(a,b)
Value Parser::CallPow(const Value& a, const Value& b)
{
    if (a.kind == VALUE_COMPLEX || b.kind == VALUE_COMPLEX)
    {
        pov::Complex ac = (a.kind == VALUE_COMPLEX) ? a.u.c :
pov::Complex(a.u.f,0);
        pov::Complex bc = (b.kind == VALUE_COMPLEX) ? b.u.c :
pov::Complex(b.u.f,0);
        return Value::FromComplex( pov::cpow(ac, bc) );
    }
    // existing float pow
}
```
> The function catalog already enumerates float functions that are legal in
expressions and in `function{}` blocks; you
ts.
---
## 3) Tokenizer / reserved word notes (for `a + b i` and lowercase `i`)
While you asked for parser changes, **the `3i` case must be handled by the
tokenizer**; otherwise `3i` is a single (invalid) identifier token. Add:
- **A numeric‑literal suffix `i`** to produce `IMAGINARY_LITERAL_TOKEN`
with payload `float_value = 3.0` → parser converts to `0 + 3i`.
- **A reserved built‑in identifier `i`** mapped to the value `pov::I`
(0+1i) in the symbol table. This lives in `reservedwords.*` and
symbol‑table bootstrap.
Sketch in `parser_tokenizer.cpp` (where numeric literals are finalized):
```cpp
// After scanning a FLOAT_LITERAL: check for immediate 'i' suffix without
whitespace.
if (CurrentChar() == 'i' || CurrentChar() == 'I') {
    Advance(); // consume 'i'
    token.Type = IMAGINARY_LITERAL_TOKEN;
    token.float_value = scanned_number; // imag part only
    return token;
}
```
Add `i` to reserved words:
```cpp
// reservedwords.cpp
{ "i", I_BUILTIN_TOKEN }, // lowercase preferred embodiment (user request)
{ "complex", COMPLEX_KW_TOKEN },
{ "cis",     CIS_KW_TOKEN },
{ "re", RE_KW_TOKEN }, { "im", IM_KW_TOKEN }, { "arg", ARG_KW_TOKEN },
```
`Value::FromComplex(pov::I)` during parser init. (Compare how `pi`, `version`,
and other built‑ins are installed.)
---
## 4) SDL surface: how it looks after these changes
```pov
#declare Z1 = 2 + 3i;                 // suffix 'i' → imaginary literal
(parser builds 0+3i)
#declare Z2 = complex(1, -2) + cis(pi/6);
#declare R  = abs(Z1);                // float magnitude
#declare TH = arg(Z1);                // float angle
// Works in general expressions:
#debug concat("Z1=", str(re(Z1),0,3), "+", str(im(Z1),0,3), "i\n")
// Works inside function{} (if you also add VM support or wrappers; see earlier
plan)
isosurface {
  function { abs( (x + i*y) * (x - i*y) ) - 1 }
  contained_by { box { <-2,-2,-2>, <2,2,2> } }
  max_gradient 5
}
```
ther (A) add complex to the VM (best), or (B) expose float‑returning wrappers
(e.g., `re(c_sin(x,y))`) until you 
wire complex through. See the earlier plan I sent for both paths.
---
## 5) Why this fits the codebase
- **Files to modify**: `source/parser/parser_expressions.cpp`,
`parser_functions*.cpp`, `parser_tokenizer.cpp`, `reservedwords.*`,
`parsertypes.*`, and `base/types.h` include path to `base/complex.hpp`. These
are the same modules that own expression parsing today.
type‑overloads and two new literal forms (`complex()`/`cis()` and the `i`
suffix).
---
## 6) Next steps (I can help draft these too)
1. **Token/enum plumbing**: add `IMAGINARY_LITERAL_TOKEN`, `COMPLEX_KW_TOKEN`,
`CIS_KW_TOKEN`, and function tokens for `re/im/arg`. Wire into
`reservedwords.*`.
2. **Symbol bootstrap**: insert built‑in `i` into the global table at
parser init.
3. **Function VM**: choose Strategy A (native complex in VM) vs Strategy B
identities; `pow` branch‑cut sanity.
---
`reservedwords` lines, tokenizer suffix detection, plus a
minimally‑invasive `parser_expressions.cpp` change set (guarded by a
`#define POV_ENABLE_COMPLEX` flag) so you can try `2+3i`, `complex()`, and
`cis()` in the SDL right away.
**References**
`parser_tokenizer.cpp`, `reservedwords.*`, `parsertypes.*` (POV‑Ray
GitHub).
- Expression grammar & function catalog (float/vector) to be extended for
complex: POV‑Ray docs & wiki.
- Function include pattern (`functions.inc`) as a precedent for adding wrappers
if you stage VM work.
Post a reply to this message
 |  |