|  |  | 
**`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)
 
 
 |  |