|
 |
"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
|
 |