 |
 |
|
 |
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
"Given the code base at https://github.com/POV-Ray/povray, what would be
required to fully implement robust code to handle complex numbers - complete
with addition, subtraction, division, multiplication, and exponentiation - using
both complex and real numbers in any combination?"
Copilot said:
Reasoned for 72s
string, etc., and user‑defined functions (the ones you can put into
isosurface, pigments, etc.) are limited to float math with a fixed set of float
new value type end‑to‑end: tokenizer → parser/AST →
expression evaluator → function VM, plus SDL syntax, promotion rules,
built‑ins, tests, and docs. Below is a concrete implementation plan tied
to the current repo layout.
povray
+2
0) Where complex would fit into the current architecture
Repo & parser locations. The core language is implemented in source/parser/*
touching parser_tokenizer.cpp, reservedwords.*, parser_expressions.cpp,
parser_functions*.cpp, parsertypes.*, and headers like base/types.h referenced
across parser modules.
github
+3
float-only (even when used by isosurface).
povray
+2
povray
Workarounds used today. Scene authors who need complex math typically emulate it
proves demand but also highlights the lack of core support.
github
+1
1) Language & type design decisions (SDL)
1.1 Choose a literal & constructor syntax (avoid ambiguity with vectors)
Vectors use <x,y,z> and are heavily overloaded throughout SDL. To keep vectors
distinct, prefer one of these explicit constructors for complex literals:
complex(a, b) → parsed as a complex literal (not a function call).
Optional: cis(theta) as a literal constructor on the unit circle (i.e., cos
θ + i sin θ).
already allow mixing with floats, and reinterpreting them would break semantics.
povray
1.2 Imaginary unit
Add a built‑in constant I (uppercase to avoid accidental collisions with
user loop variables and to match your preference for non‑lowercase
identifiers) representing 0 + 1i. This is just a constant symbol in the symbol
table (like pi is a float built‑in), backed by the new complex type. The
reserved words table lives in reservedwords.*.
github
Using I enables familiar forms like Z = 3 + 2*I; while keeping scanning simple
1.3 Operators & promotion rules
Add full arithmetic overloading for + - * / with these rules:
Promotion: real ⊕ complex → promote real to complex with zero
imaginary part; result is complex.
Unary minus: preserves type.
Equality/ordering: Keep only = and != (compare both parts with float epsilon
rules); no <, <=, >, >= for complex.
truthiness). The parser already handles boolean using float zero/non‑zero;
document the complex rule.
hws
1.4 Exponentiation
SDL does not have an ^/** operator; exponentiation uses pow(base, exponent).
Implement pow(Complex, Complex) with principal value; add overloads for
(Complex, Real), (Real, Complex). If you later want **, you must change the
grammar in parser_expressions.cpp to introduce a precedence tier (riskier; not
hws
Complex → complex: c_conj(z), c_exp(z), c_log(z), c_sqrt(z),
c_sin/c_cos/c_tan/c_asin/c_acos/c_atan, c_sinh/c_cosh/c_tanh, c_pow(z, w)
(aliases to pow).
Complex → float: re(z), im(z), abs(z) (magnitude), arg(z) (principal
argument).
Constructors: complex(a, b), cis(theta).
the parser resolve in expression context.
ijs
2) Core implementation (parser & evaluator)
2.1 New type in core headers
Add a struct Complex { DBL r, i; } (or reuse std::complex<DBL> internally and
included by parser modules. Implement basic ops and utility functions.
github
2.2 Tokenizer & reserved words
parser_tokenizer.cpp / reservedwords.*:
Reserve complex, I, re, im, abs, arg, and the c_* family as
keywords/built‑ins.
Recognize complex( as starting a complex-literal token (to differentiate from a
generic function call).
github
2.3 Parser types & value representation
Extend the value/tagged‑union used by the parser and evaluator
(parsertypes.*) to carry a Complex variant alongside Float, Vector, Color,
String, etc.
three kinds: Float, Vector, Complex (a fourth if you count Color). Keep
vectors/colors as‑is.
github
2.4 Expression evaluation
In parser_expressions.cpp implement binary/unary operator cases for complex
(including mixed real/complex with promotion).
Implement built‑ins (re, im, abs, arg, cis) and the c_ family in
parser_functions*.cpp.
Map pow dispatch based on operand types to complex exponentiation when any
operand is complex. Today pow(float,float) is wired via the float function
table; add a complex path.
github
+1
2.5 Printing & formatting
Extend str() or provide cstr(z) so #debug can print complex values intelligibly
(e.g., "(a,b)" or "a+bi"). The existing diagnostics assume float for many paths;
audit the %g style prints where needed.
github
The function system accepts float functions; it exposes a grammar and function
need one of these strategies:
povray
+1
Extend the function VM with a new COMPLEX function category (think analog of
VECTFUNCT_ID_TOKEN) and corresponding bytecode nodes supporting complex
temporaries and ops. Update the function grammar to allow complex expressions
(identifiers and temporaries of complex type). Update the compiler in fncode.cpp
accordingly.
Provide bridges so a complex function can be reduced to float via re(), im(),
abs(), arg() when the host expects float (e.g., isosurface threshold function).
This lets authors write expressions like:
Plain Text
isosurface {
function { abs( c_sin( x + I*y ) ) - 1 } // ok: returns float
contained_by { box { <-2,-2,-2>, <+2,+2,+2> } }
}
cleanly.)
povray
(faster to ship)
If you want to avoid VM changes initially, add float‑returning helpers
that take float pairs (re, im) and return a float:
c_sin_re(x,y), c_sin_im(x,y), etc.
Then users can build complex flows in SDL at parse time, but inside function{}
The official functions.inc already exposes many internal helpers via
povray
+1
works everywhere the float math works today.
4) Math semantics & numerics
DBL precision: Use DBL everywhere for real/imag parts. (DBL is the
project‑wide floating type used in parser/math.)
github
pow and branch cuts: Define principal values (log z principal branch; pow(a,b) =
exp(b*log a)). Document behavior near branch cuts and zero denominators
(returning NaNs consistent with DBL and propagating through). The float function
list already includes exp, log, pow; your complex versions should mirror those
names via the c_* family to avoid changing float semantics.
ijs
Performance: Consider inlining and possibly using std::complex<DBL> internally
for correctness (argument reduction, trig identities) while presenting a simple
POD at interfaces.
5) SDL integration details
Declarations: #declare Z = complex(1,2); should assign a complex value; extend
#declare/#local rvalue typing accordingly (this directive currently accepts
docs and allow a trailing semicolon like floats/vectors.
povray
promotion.
Built‑ins already in use by vectors. SDL provides x, y, z, t, u, v as
vector built‑ins/dot items; do not repurpose them for complex components.
Use re()/im() accessors instead.
povray
grammar and precedence changes in parser_expressions.cpp that ripple across the
evaluator.
github
+1
6) Parser & symbol table updates
Symbol kinds. Add a COMPLEX_ID_TOKEN (analogous to FLOAT_ID_TOKEN,
The DeepWiki summary of symbol handling gives a good overview of where these
live.
deepwiki
Reserved words. Add new keywords into reservedwords.* and ensure the tokenizer
maps them to the right token IDs.
github
7) Standard library & examples
Include files. Provide complex.inc with macro helpers and sugar (e.g., polar
form constructor, safe printing, clipping helpers).
Samples. Ship scenes demonstrating: arithmetic, mixed real/complex math, complex
powers, Newton fractals (via isosurface using abs(f(z))), and domain coloring.
(The community POV‑Ray‑complex‑functions repo is a great
source of sample ideas; it currently implements such features at the SDL macro
level.)
github
8) Backwards compatibility & doc updates
additive.
Docs. Update:
Float/Vector Expressions chapters to mention complex and mixing rules.
User‑Defined Functions to either: (A) describe complex functions as a new
category; or (B) document the *_xy wrappers if you go with Strategy B. Current
povray
+1
9) Testing
Unit tests for arithmetic identities (z + 0 = z, z*conj(z) = abs(z)^2, edge
cases around zeros/NaNs).
Parser tests for literals, promotions, and error messages.
Render tests (scenes) that verify isosurface behavior using
complex‑powered formulas (e.g., abs( c_pow(x+I*y, 3) - 1 ) surfaces).
github
github
github
built‑in.
github
comparisons, truthiness.
github
arg, cis, c_*) and dispatch pow to complex when needed.
github
Strategy A: add complex bytecodes, temps, function kind, and grammar hooks.
Strategy B: add float wrappers c_*_xy.
deepwiki
Strategy B is used. (The shipped functions.inc sets a precedent for exposing
complex helpers if you choose wrappers.)
github
hws
+3
11) Example (proposed SDL surface syntax)
(Using uppercase identifiers and no lowercase to match your style.)
Plain Text
#declare Z1 = complex(2, 3); // 2 + 3i
#declare Z2 = 5; // real
#declare Z3 = Z1 + Z2; // (7 + 3i)
#declare Z4 = pow(Z1, complex(0.5, 0)); // sqrt as pow
#debug concat("Re(Z3)=", str(re(Z3),0,3),
" Im(Z3)=", str(im(Z3),0,3), "\n")
isosurface{
function { abs( c_exp( x + I*y ) - 1 ) - 0.5 } // Strategy A
contained_by { box { <-3,-3,-3>, <3,3,3> } }
max_gradient 5
}
(If you ship Strategy B first, the function line becomes, e.g., c_abs_xy(
c_exp_re(x,y) - 1, c_exp_im(x,y) ) - 0.5.)
povray
12) Scope, risks, and a pragmatic path
surgery) deliver usable complex math across SDL expressions and in function{}
via _xy helpers.
Full robustness: Add Strategy A (complex value kind in VM) so complex
expressions can exist natively in function{} and be reduced to float with
abs/re/im/arg as needed.
Why not reuse vectors? Because vectors already have distinct semantics and
operator overloading, and conflating them with complex would introduce ambiguity
(e.g., is <a,b> a UV, a color slice, or a complex?).
povray
Compatibility: All additions are opt‑in; no change to existing scenes or
shipped function sets (pow, sin, etc. keep float semantics).
ijs
Repo & parser files: top‑level overview and source/parser/* modules.
github
+1
Expression grammar (float/vector) and function limitations: formal docs.
hws
+2
Function include catalog (functions.inc): pattern/internal function interface
(good model for adding complex helpers if you stage Strategy B).
github
+1
Community complex include for inspiration on naming and examples.
github
node tweaks) and a minimal complex.hpp with numerically stable c_log/c_pow you
can drop into base/, then sketch the parser_expressions.cpp switch additions.
literal like cis(theta)?
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
"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
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
**`i` suffix**, all bundled as a zip you can download.
**Download:**
[complex_povray_patch.zip](blob:https://m365.cloud.microsoft/a301547a-bdae-4511-b0ac-75d3eafa5175)
The zip contains:
and arithmetic in `parser_expressions.cpp`.
- add a new token `IMAGINARY_LITERAL_TOKEN` (e.g., `3i`, `2.5i`),
- recognize `complex(`, `cis(`, `re`, `im`, `arg` tokens,
- and (optionally) install the lower-case built-in constant `i` (0+1i).
referenced by the parser.
> The patches are written to be **fuzzy‑applicable** against the current
`master` layout. Exact line numbers may
sed on the public repo and current parser structure.
---
The patch:
- Adds `#include "base/complex.hpp"` so the parser can construct and operate on
complex values.
- Teaches the **primary expression** parser to recognize:
- `complex(re, im)` → builds a complex literal,
- `cis(theta)` → builds `cos θ + i sin θ`.
- Extends **binary operator** reduction to handle `+ - * /` when either side is
complex (promotes floats to complex).
- Extends **unary minus** for complex.
- Overloads **`pow()`** to return complex when any operand is complex; overloads
**`abs()`** to return magnitude for complex; implements **`re()`**, **`im()`**,
**`arg()`** returning floats.
```diff
*** a/source/parser/parser_expressions.cpp
--- b/source/parser/parser_expressions.cpp
***************
*** 1,10 ****
// ... file prolog omitted for brevity ...
#include "parser/parser.h"
#include "parser/parser_expressions.h"
#include "base/povassert.h"
#include <cmath>
+ #include "base/complex.hpp" // [COMPLEX] draft include for Complex math
using namespace pov;
--- 1,11 ----
// ... file prolog omitted for brevity ...
#include "parser/parser.h"
#include "parser/parser_expressions.h"
#include "base/povassert.h"
#include <cmath>
+ #include "base/complex.hpp" // [COMPLEX] draft include for Complex math
using namespace pov;
***************
*** 200,230 ****
- // Existing atom/primary expression parsing here...
- // switch (Tok.Type) { FLOAT_LITERAL_TOKEN, IDENT_TOKEN, ... }
+ // [COMPLEX] Recognize imaginary literal tokens (e.g., 3i) and complex/cis
constructors
+ Value Parser::ParsePrimary()
+ {
+ Token Tok = CurrentToken();
+ switch (Tok.Type)
+ {
+ case FLOAT_LITERAL_TOKEN:
+ NextToken();
+ return Value::FromFloat(Tok.Float);
+
+ case IMAGINARY_LITERAL_TOKEN: // new token from tokenizer suffix 'i'
+ {
+ NextToken();
+ pov::Complex z(0.0, Tok.Float);
+ return Value::FromComplex(z);
+ }
+
+ case COMPLEX_KW_TOKEN: // complex(
+ {
+ NextToken(); Expect(LPAREN_TOKEN);
+ DBL re = ParseFloatExpression();
+ Expect(COMMA_TOKEN);
+ DBL im = ParseFloatExpression();
+ Expect(RPAREN_TOKEN);
+ return Value::FromComplex( pov::Complex(re, im) );
+ }
+
+ case CIS_KW_TOKEN: // cis(
+ {
+ NextToken(); Expect(LPAREN_TOKEN);
+ DBL th = ParseFloatExpression();
+ Expect(RPAREN_TOKEN);
+ return Value::FromComplex( pov::cis(th) );
+ }
+
+ default:
+ return ParsePrimary_Existing(); // fall back to existing
implementation
+ }
+ }
***************
*** 420,490 ****
- // Existing binary operator reduction
+ // [COMPLEX] Extend binary ops for complex + promotion from float
Value Parser::ReduceBinaryOp(TokenType op, const Value& lhs, const Value&
rhs)
{
- // existing float/vector handling ...
+ 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);
+ pov::Complex b = (rhs.kind == VALUE_COMPLEX) ? rhs.u.c :
pov::Complex(rhs.u.f, 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; // defer others to existing code
+ }
+ }
+ // existing float/vector handling follows ...
return ReduceBinaryOp_Existing(op, lhs, rhs);
}
***************
*** 500,560 ****
- // Existing unary minus handling
+ // [COMPLEX] unary minus
Value Parser::ReduceUnaryMinus(const Value& x)
{
- // existing code
+ if (x.kind == VALUE_COMPLEX)
+ return Value::FromComplex(-x.u.c);
+ return ReduceUnaryMinus_Existing(x);
}
***************
*** 700,790 ****
- // Existing function dispatch such as pow(), abs(), etc.
+ // [COMPLEX] overload pow/abs/re/im/arg
Value Parser::CallPow(const Value& a, const Value& b)
{
- // existing float pow
+ 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) );
+ }
+ return CallPow_Existing(a,b);
}
Value Parser::CallAbs(const Value& v)
{
- // existing float abs
+ if (v.kind == VALUE_COMPLEX)
+ return Value::FromFloat( pov::abs(v.u.c) );
+ return CallAbs_Existing(v);
}
Value Parser::CallRe(const Value& v)
{
+ if (v.kind == VALUE_COMPLEX) return Value::FromFloat(v.u.c.r);
SyntaxError("re() expects complex argument");
}
Value Parser::CallIm(const Value& v)
{
+ if (v.kind == VALUE_COMPLEX) return Value::FromFloat(v.u.c.i);
SyntaxError("im() expects complex argument");
}
Value Parser::CallArg(const Value& v)
{
+ if (v.kind == VALUE_COMPLEX) return Value::FromFloat( pov::arg(v.u.c) );
SyntaxError("arg() expects complex argument");
}
```
differences (e.g., if your implementation names these helper methods differently).
---
## 2) How to implement the **`i` suffix** in the tokenizer
**Goal:** Treat `3i`, `2.5i`, `1e-3i`, etc., as **one token** of a new type
without guessing or backtracking. This change happens right after scanning a
the token type.
You also add convenient keyword tokens for `complex`, `cis`, `re`, `im`, `arg`
and (optionally) install a built‑in lower‑case `i` constant (equal
to `0+1i`) into the symbol table during parser initialization.
The included patch (`patches/tokenizer_i_suffix.patch`) makes all these changes.
Key parts:
```diff
*** a/source/parser/parser.h
--- b/source/parser/parser.h
***************
*** 150,175 ****
enum TokenType {
// ... existing tokens ...
FLOAT_LITERAL_TOKEN = 1000,
+ IMAGINARY_LITERAL_TOKEN, // [COMPLEX] e.g., 3i, 2.5i
IDENT_TOKEN,
// keywords ...
+ COMPLEX_KW_TOKEN, // complex(
+ CIS_KW_TOKEN, // cis(
+ RE_KW_TOKEN, IM_KW_TOKEN, ARG_KW_TOKEN,
};
```
```diff
*** a/source/parser/parser_tokenizer.cpp
--- b/source/parser/parser_tokenizer.cpp
***************
*** 300,380 ****
// After scanning a numeric literal (FLOAT_LITERAL_TOKEN), check for 'i'
suffix
// with no intervening whitespace.
if (token.Type == FLOAT_LITERAL_TOKEN)
{
int c = PeekChar();
if (c == 'i' || c == 'I')
{
// verify there's no whitespace between number and 'i'
if (!SawWhitespaceSinceLastChar())
{
GetChar(); // consume 'i'
token.Type = IMAGINARY_LITERAL_TOKEN;
// token.Float already holds the numeric part
return token;
}
}
}
return token;
```
```diff
*** a/source/parser/reservedwords.cpp
--- b/source/parser/reservedwords.cpp
***************
*** 90,120 ****
static const ReservedWord s_reserved[] = {
// ... existing ...
+ { "complex", COMPLEX_KW_TOKEN },
+ { "cis", CIS_KW_TOKEN },
+ { "re", RE_KW_TOKEN },
+ { "im", IM_KW_TOKEN },
+ { "arg", ARG_KW_TOKEN },
+ { "i", I_BUILTIN_TOKEN }, // lowercase imaginary unit constant
};
```
```diff
*** a/source/parser/parser.cpp
--- b/source/parser/parser.cpp
***************
*** 500,560 ****
void Parser::InstallBuiltins()
{
// existing built-ins (pi, version, etc.)
+ // [COMPLEX] install lowercase 'i' as a complex constant
+ Value zi; zi.kind = VALUE_COMPLEX; zi.u.c = pov::I;
+ Symbols->InsertBuiltin("i", zi);
}
```
> The exact token/enum locations depend on your copy of `parser.h` and the tokenizer
structure. The intent is clear: (1
) add a new token for imaginary literals, (2) promote `complex`/`cis`/`re`/`im`/`arg`
to keyword tokens, (3) register `
i` as a built‑in symbol. See the upstream files for reference.
---
## 3) `complex.hpp` helper (included)
The parser and function dispatch rely on a minimal complex implementation that:
- provides robust division, principal `log`, `pow`, `sqrt`,
- and the helpers you need: `abs`, `arg`, `cis`, `re`, `im` via direct field
access.
This file sits at `source/base/complex.hpp` (path chosen to match existing base
headers, which are widely included by parser modules).
---
## 4) Apply & test quickly
1. Unzip into your working tree (anywhere).
2. Copy `src/base/complex.hpp` to `source/base/complex.hpp`.
3. Apply patches:
```bash
patch -p1 < patches/tokenizer_i_suffix.patch
patch -p1 < patches/parser_expressions.patch
```
4. Build.
5. Try a tiny SDL:
```pov
#declare Z1 = 2 + 3i;
#declare Z2 = complex(1, -2) + cis(pi/6);
#debug concat("Z1=", str(re(Z1),0,3), "+", str(im(Z1),0,3), "i\n")
```
(a) add complex to the Function VM as discussed previously, or (b) temporarily
reduce to floats with `abs/re/im/arg` until the VM is extended. The *parsing*
side is handled by these patches.
---
## Pointers to repo structure (for context)
- Parser & expression code lives under **`source/parser/`** (including
`parser_expressions.cpp`, tokenizer, and reserved words).
documented here; `pow()` is the standard way to exponentiate (no `^` operator),
which is why overloading `pow` is the right entry point.
---
If you want me to also generate a **reserved‑keyword scan test** and a
build & render as smoke tests.
Post a reply to this message
Attachments:
Download 'complex_povray_patch.zip' (5 KB)
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
"Bald Eagle" <cre### [at] netscape net> wrote:
> "Given the code base at https://github.com/POV-Ray/povray, what would be
> required to fully implement robust code to handle complex numbers - complete
> with addition, subtraction, division, multiplication, and exponentiation - using
> both complex and real numbers in any combination?"
#declare complexnumber = cmp<re, im>;
it's kind of vector hence the vector notation and fits with the colour vectors
rgb<1,1,1>. All the re and im <x, y> extraction code should be available in
POV-Ray, it c++ after al ...(can't read it, always get lost)
ingo
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
"ingo" <nomail@nomail> wrote:
> #declare complexnumber = cmp<re, im>;
>
> it's kind of vector hence the vector notation and fits with the colour vectors
> rgb<1,1,1>. All the re and im <x, y> extraction code should be available in
> POV-Ray, it c++ after al ...(can't read it, always get lost)
I have written such SDL-side macros to use that type of formulation, however, I
was looking to see what needed to be changed / added at the source code level,
so it could be natively available, fast, useable in isosurfaces, and assignable
to POV-space cardinal axes.
(see threads with complex macros, and discussions about complex numbers with
clipka)
Just exploring options at this point, and hoping the c++ programmers and
dev-types (Jerome, WFP, etc) will look some of it over and be inspired, comment,
and perhaps even try implementing.
In the absence of anything else I'm just trying to relentlessly keep _pushing
forward_.
- BW
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 29/10/2025 20:39, Bald Eagle wrote:
>
> Just exploring options at this point, and hoping the c++ programmers and
> dev-types (Jerome, WFP, etc) will look some of it over and be inspired, comment,
> and perhaps even try implementing.
>
If I remember correctly, WFP has already added the complexes in a
slightly more transparent way in version 4.0.
--
kurtz le pirate
compagnie de la banquise
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|
 |