Pikchr

Loops
Login

Loops

(1) By Ryan (RyanSmith) on 2022-07-27 23:20:48 [source]

Hi all,

I've seen a thread over at the SQLite forum that piqued my interest and I went on a pikchr discovery. Reminded me of PIC and the turtle logo program from ages ago, but of course better.

Just felt compelled to say "Great work!" - especially on the relative positioning, it's far superior to the original PIC.

I've been amusing myself and I did wonder if adding some form of loop mechanism would be feasible, ever?

This took a bunch of code, which a loop could reduce significantly. I know this example is playing, but there are real use-cases, such as the grid for CPU registers I tried to do, which can be either a box with many lines in it, or many boxes, either way, looping would be grand.

CP:   dot;

      $h = 10;
      line from CP go 0.7cm heading $h; DA1: dot;
      line         go 1cm heading $h; DA2: dot;
      line         go 1cm heading $h; DA3: dot;
      line         go 1cm heading $h; DA4: dot;
      line         go 1cm heading $h; DA5: dot;
      line         go 1cm heading $h; DA6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 0.8cm heading $h; DB1: dot;
      line         go 1cm heading $h; DB2: dot;
      line         go 1cm heading $h; DB3: dot;
      line         go 1cm heading $h; DB4: dot;
      line         go 1cm heading $h; DB5: dot;
      line         go 1cm heading $h; DB6: dot;
      line         go 1cm heading $h;

      $h = $h+22;
      line from CP go 0.9cm heading $h; DC1: dot;
      line         go 1cm heading $h; DC2: dot;
      line         go 1cm heading $h; DC3: dot;
      line         go 1cm heading $h; DC4: dot;
      line         go 1cm heading $h; DC5: dot;
      line         go 1cm heading $h; DC6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 0.9cm heading $h; DD1: dot;
      line         go 1cm heading $h; DD2: dot;
      line         go 1cm heading $h; DD3: dot;
      line         go 1cm heading $h; DD4: dot;
      line         go 1cm heading $h; DD5: dot;
      line         go 1cm heading $h; DD6: dot;
      line         go 1cm heading $h;

      $h = $h+19;
      line from CP go 1cm heading $h; DE1: dot;
      line         go 1cm heading $h; DE2: dot;
      line         go 1cm heading $h; DE3: dot;
      line         go 1cm heading $h; DE4: dot;
      line         go 1cm heading $h; DE5: dot;
      line         go 1cm heading $h; DE6: dot;
      line         go 1cm heading $h;

      $h = $h+19;
      line from CP go 1cm heading $h; DF1: dot;
      line         go 1cm heading $h; DF2: dot;
      line         go 1cm heading $h; DF3: dot;
      line         go 1cm heading $h; DF4: dot;
      line         go 1cm heading $h; DF5: dot;
      line         go 1cm heading $h; DF6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 1cm heading $h; DG1: dot;
      line         go 1cm heading $h; DG2: dot;
      line         go 1cm heading $h; DG3: dot;
      line         go 1cm heading $h; DG4: dot;
      line         go 1cm heading $h; DG5: dot;
      line         go 1cm heading $h; DG6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 1cm heading $h; DH1: dot;
      line         go 1.1cm heading $h; DH2: dot;
      line         go 1.1cm heading $h; DH3: dot;
      line         go 1cm heading $h; DH4: dot;
      line         go 1cm heading $h; DH5: dot;
      line         go 1cm heading $h; DH6: dot;
      line         go 1cm heading $h;

      $h = $h+24;
      line from CP go 1cm heading $h; DI1: dot;
      line         go 1cm heading $h; DI2: dot;
      line         go 1cm heading $h; DI3: dot;
      line         go 1cm heading $h; DI4: dot;
      line         go 1cm heading $h; DI5: dot;
      line         go 1cm heading $h; DI6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 0.9cm heading $h; DJ1: dot;
      line         go 1cm heading $h; DJ2: dot;
      line         go 1cm heading $h; DJ3: dot;
      line         go 1cm heading $h; DJ4: dot;
      line         go 1cm heading $h; DJ5: dot;
      line         go 1cm heading $h; DJ6: dot;
      line         go 1cm heading $h;

      $h = $h+18;
      line from CP go 1cm heading $h; DK1: dot;
      line         go 1cm heading $h; DK2: dot;
      line         go 1cm heading $h; DK3: dot;
      line         go 1cm heading $h; DK4: dot;
      line         go 1cm heading $h; DK5: dot;
      line         go 1cm heading $h; DK6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 1cm heading $h; DL1: dot;
      line         go 1cm heading $h; DL2: dot;
      line         go 1cm heading $h; DL3: dot;
      line         go 1cm heading $h; DL4: dot;
      line         go 1cm heading $h; DL5: dot;
      line         go 1cm heading $h; DL6: dot;
      line         go 1cm heading $h;

      $h = $h+19;
      line from CP go 1cm heading $h; DM1: dot;
      line         go 1cm heading $h; DM2: dot;
      line         go 1cm heading $h; DM3: dot;
      line         go 1cm heading $h; DM4: dot;
      line         go 1cm heading $h; DM5: dot;
      line         go 1cm heading $h; DM6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 1cm heading $h; DN1: dot;
      line         go 1cm heading $h; DN2: dot;
      line         go 1cm heading $h; DN3: dot;
      line         go 1cm heading $h; DN4: dot;
      line         go 1cm heading $h; DN5: dot;
      line         go 1cm heading $h; DN6: dot;
      line         go 1cm heading $h;

      $h = $h+16;
      line from CP go 1.1cm heading $h; DO1: dot;
      line         go 1cm heading $h; DO2: dot;
      line         go 1cm heading $h; DO3: dot;
      line         go 1cm heading $h; DO4: dot;
      line         go 1cm heading $h; DO5: dot;
      line         go 1cm heading $h; DO6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 1.2cm heading $h; DP1: dot;
      line         go 1cm heading $h; DP2: dot;
      line         go 1cm heading $h; DP3: dot;
      line         go 1cm heading $h; DP4: dot;
      line         go 1cm heading $h; DP5: dot;
      line         go 1cm heading $h; DP6: dot;
      line         go 1cm heading $h;

      $h = $h+22;
      line from CP go 1.3cm heading $h; DQ1: dot;
      line         go 1cm heading $h; DQ2: dot;
      line         go 1cm heading $h; DQ3: dot;
      line         go 1cm heading $h; DQ4: dot;
      line         go 1cm heading $h; DQ5: dot;
      line         go 1cm heading $h; DQ6: dot;
      line         go 1cm heading $h;

      $h = $h+20;
      line from CP go 1.5cm heading $h; DR1: dot;
      line         go 1cm heading $h; DR2: dot;
      line         go 1cm heading $h; DR3: dot;
      line         go 1cm heading $h; DR4: dot;
      line         go 1cm heading $h; DR5: dot;
      line         go 1cm heading $h; DR6: dot;
      line         go 0.4cm heading $h;

      line from DA1 to DB1 then to DC1 then to DD1 \
               then to DE1 then to DF1 then to DG1 \
               then to DH1 then to DI1 then to DJ1 \
               then to DK1 then to DL1 then to DM1 \
               then to DN1 then to DO1 then to DP1 \
               then to DQ1 then to DR1 then to DA2 thin;

      line          to DB2 then to DC2 then to DD2 \
               then to DE2 then to DF2 then to DG2 \
               then to DH2 then to DI2 then to DJ2 \
               then to DK2 then to DL2 then to DM2 \
               then to DN2 then to DO2 then to DP2 \
               then to DQ2 then to DR2 then to DA3 thin;

      line          to DB3 then to DC3 then to DD3 \
               then to DE3 then to DF3 then to DG3 \
               then to DH3 then to DI3 then to DJ3 \
               then to DK3 then to DL3 then to DM3 \
               then to DN3 then to DO3 then to DP3 \
               then to DQ3 then to DR3 then to DA4 thin;

      line          to DB4 then to DC4 then to DD4 \
               then to DE4 then to DF4 then to DG4 \
               then to DH4 then to DI4 then to DJ4 \
               then to DK4 then to DL4 then to DM4 \
               then to DN4 then to DO4 then to DP4 \
               then to DQ4 then to DR4 then to DA5 thin;

      line          to DB5 then to DC5 then to DD5 \
               then to DE5 then to DF5 then to DG5 \
               then to DH5 then to DI5 then to DJ5 \
               then to DK5 then to DL5 then to DM5 \
               then to DN5 then to DO5 then to DP5 \
               then to DQ5 then to DR5 then to DA6 thin;

      line          to DB6 then to DC6 then to DD6 \
               then to DE6 then to DF6 then to DG6 \
               then to DH6 then to DI6 then to DJ6 \
               then to DK6 then to DL6 then to DM6 \
               then to DN6 then to DO6 then to DP6 \
               then to DQ6 then to DR6 thin;

BODY: ellipse width 0.3cm height 0.4cm fill black at 2cm heading 340 from CP;
RDT:  ellipse width 0.2cm height 0.2cm fill red at last ellipse.c;
      ellipse width 0.2cm height 0.1cm fill black at RDT.n;
      ellipse width 0.2cm height 0.1cm fill black at RDT.s;
HEAD: ellipse with .s at BODY.n width 0.14cm height 0.12cm;
      line thin thin from HEAD.nw go 0.08cm heading 155;
      line thin thin from HEAD.ne go 0.08cm heading 205;
SPIN: ellipse thin at BODY.s width 0.06cm height 0.08cm;
      line thin from SPIN.s go 1.2cm heading 142;

  $h=340; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h- 70 then go 0.2cm heading $h-150;
  $h=306; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h-108 then go 0.2cm heading $h-150;
  $h=260; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h- 70 then go 0.2cm heading $h-110;
  $h=210; line from RDT thin chop      go 0.8cm heading $h then go 0.5cm heading $h-115 then go 0.2cm heading $h-130;

  $h= 22; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h+ 52 then go 0.2cm heading $h+130;
  $h= 45; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h+ 74 then go 0.2cm heading $h+130;
  $h= 68; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h+ 70 then go 0.2cm heading $h+134;
  $h= 92; line from RDT thin chop      go 0.8cm heading $h then go 0.5cm heading $h+100 then go 0.2cm heading $h+130;

(2) By Stephan Beal (stephan) on 2022-07-27 23:47:09 in reply to 1 [link] [source]

This took a bunch of code ...

But the results are gorgeous! Seriously impressive.

... which a loop could reduce significantly.

Looping currently requires generating that code with a higher-level tool. For example, Fossil's pikchr command optionally supports inputs which contain TH1 code (TH1 being Fossil's embedded mini-dialect of TCL). Using that command, you can program the loops in TH1, run the whole thing through the pikchr command, and have it output either the resulting pikchr code or the SVG.

It's not the same as having loops built-in, but as soon as pikchr has loops, it needs logic operators and flow control (break/continue), and that starts turning the whole thing into a full-fledged programming language. As soon as a loop can be created, it becomes possible to crash rendering of your forum post, and indeed a whole forum thread, by injecting an endless loop into the post. If such a post is saved, recovering (making the affected thread readable again) would require a fossil admin "shunning" the forum post in question. Merely discovering which post in a thread is causing the problem may require attaching a debugger. That Way Lies Madness.

Though i agree that the utility would be nice at times, i'd currently argue for keeping pikchr free of those complexities and side effects.

(3) By Ryan (RyanSmith) on 2022-07-27 23:57:58 in reply to 2 [link] [source]

Indeed, almost as soon as I posted it I realised that there is also no flow-control in pikchr, and without some form of flow control, loops would be difficult. (Hence the second post on objects)

I think the idea of being able to group some items into a set or object or variable, then be able to "draw" or "play" that item again and again as needed would require much less effort, no flow control, not breaking the pikchr design tenets, and help a bunch with making less code do more.

I see even in SQLite's docs the same shapes being reused lots, as well as in other example pikchrs people posted here, making me think there is some general benefit (not just my own eccentric wishes).

Anyway, these thoughts probably belong in the other thread...

(4.1) By Ryan (RyanSmith) on 2022-07-28 13:44:06 edited from 4.0 in reply to 1 [link] [source]

So after learning about Macros (thanks to the people pointing that out), I was able to improve the spider drawing to be thrice as dense (with respect to web rungs) and simultaneously reduce the code to do it to about 40% of the original. It's a vast improvement - even if it feels more pristine (machine-made) than the original (which is what you want for most computer graphics).

The code looks more dense, but that's only because now it is properly commented.

Question: Initially this image was a bit bigger (more web loops) and while it worked in the PikchrShow web-app, here the previewer complained about it being too complex:

  • Why is the complexity different between the two web interfaces?
  • Is there a way to understand the complexity limits, or know when you are crossing the boundary?
  • Is it a compile-time define?

It would be quite inconvenient to make an image and find some target implementation can't display it for being compiled with a lower complexity tolerance - or am I missing something obvious again?

// Spider in Web - pikchr

CP:   dot;

      $wAngle = 18;                   // Web-Angle (Only works between 0 and 20, else Angle-calcs overflow)
      $spiderScale = 1.5;             // Spider-Scaling factor
      $b = 6.5cm;                     // Beam-Size
      $h = $wAngle;                   // Start loop $h at the Web-Angle

define beam {                         // Macro for drawing radial web beams
      line go $b heading $h thin;
      move to CP;                     // Move back to Center after drawing a beam, then
      $h = $h + 20;                   // Increase beam-angle 20 degrees. 360deg / 20 = 18 beams
}

      beam; beam; beam; beam; beam; beam; beam; beam; beam;  // Draw the 18 beams
      beam; beam; beam; beam; beam; beam; beam; beam; beam;

define rung {                         // Macro for drawing web rungs between beams
      RP: line go $b heading $h thin thin;
      $h = ($h + 20 + $r);            // Increase the angle after every rung, plus a radial offset to
}                                     // cause spiraling towards the end

define webloop {
      $r = 0;
      $b = ($z * 3.14159) / 18;                  // Rung length is rung height (radius) x Pi div by 18 beams
      CS: move go $z heading $wAngle;            // Go up the start beam to the rung radius height
      line from last line.end to CS thin thin;   // Continue the line to there
      $h = $wAngle + 100;                        // 1st rung angle is start angle + half beam angle (10) + 90
      rung; rung; rung; rung; rung; rung; rung; 
      rung; rung; rung; rung; rung; rung;        // Draw first 13 rungs without angle offset ($r)
      $h = $h - 360;                             // Modulo hack (no flow-control and no modulo expression)
      $r = 4 - (4 * $z / 8);                     // Increase angle offset proportional to rung height
      rung; rung; rung; rung; // rung;           // Draw last 4 rungs (no 18th rung so spiral is seamless)
      move to CP;
      $z = $z - 0.25;                            // Set rung-height 1/4cm closer to the center for next rung
}

define leg {                          // Macro for legs, taking 3 "parameters" for the leg-angles
      line from RDT thin chop go $ss*0.8cm heading $1 then go $ss*0.5cm heading $2 then go $ss*0.2cm heading $3;
}

define spider {                       // Macro for drawing a spider
      $ss = $spiderScale;
      BODY: ellipse width $ss*0.3cm height $ss*0.4cm fill black;                    // Body section
      RDT:  ellipse width $ss*0.2cm height $ss*0.2cm fill red at last ellipse.c;    // Red dot (Button spider)
            ellipse width $ss*0.2cm height $ss*0.1cm fill black at RDT.n;           // Mask dot North
            ellipse width $ss*0.2cm height $ss*0.1cm fill black at RDT.s;           // Mask dot South
      HEAD: ellipse with .s at BODY.n width $ss*0.14cm height $ss*0.12cm fill silver;           // Head
            line thin thin from HEAD.nw go $ss*0.08cm heading 155;                  // Attempt at fangs :)
            line thin thin from HEAD.ne go $ss*0.08cm heading 205;
      SPIN: ellipse thin at BODY.s width $ss*0.06cm height $ss*0.08cm;              // Spinner mechanism

      // 4 x Left-side legs
      leg(335, 270, 190);                        // Its questionable if we "need" this to be a macro, since
      leg(303, 229, 190);                        // we draw only 1 spider, and can only draw 1 as the legs
      leg(279, 200, 170);                        // are placed upon web rungs at the specific position.
      leg(265, 190, 150);

      // 4 x Right -side legs
      leg( 40, 109, 190);                        // Use the leg-angle parameters to place legs
      leg( 52, 136, 190);
      leg( 86, 147, 208);
      leg(108, 179, 230);

      SPE: move to SPIN.s;                       // End at the spinner-South so we can connect a web strand to it
}

      // Draw everything
      line up 0;                                        
      $z = 12cm;                                 // Start on outer web spiral loop
      webloop;  webloop;  webloop;  webloop;  webloop;  webloop;  // Draw 18 loops - this qty of loops, the
      webloop;  webloop;  webloop;  webloop;  webloop;  webloop;  // rung-height decrement, and the beam lengths
      webloop;  webloop;  webloop;  webloop;  webloop;  webloop;  // are all inter-dependent.


      move up 1.4cm right 0.1cm                  // Move to the Spider's position, and
      spider;                                    // Draw the spider 

      line thin to RP.end thin;                  // Connect the spider's spinner to the last rung line point.
     
// ---EOF---

(5) By Martin Gagnon (mgagnon) on 2022-07-28 13:13:31 in reply to 4.0 [link] [source]

Also, what might interest you is that macros can take arguments. Accessible using: $1, $2, $3, …. within the macro. (It support a maximum of 9 if I remember well).

(6) By Ryan (RyanSmith) on 2022-07-28 13:49:27 in reply to 5 [link] [source]

Thank you Martin, it's one of those things that did not immediately work the first time I tried it (for lack of understanding on my part) and thought I would catch up with it later. That time has come thanks to you mentioning it, it works very well - I've adjusted the code use macro parameters.

Thanks!

(7) By Stephan Beal (stephan) on 2022-07-28 15:18:58 in reply to 4.1 [link] [source]

Why is the complexity different between the two web interfaces?

The WASM file built for /pikchrshow is built as part of the pikchr project and then copied into fossil, whereas the copy of pikchr.c fossil uses is compiled by the fossil build process (the difference being that the former requires a wasm-specific toolchain). In the case of the former, the default limit on the number of tokens is 100000, but fossil's build of the .c file reduces that to 10000.

So far, you've been the only person to stumble across that difference.

Is there a way to understand the complexity limits, or know when you are crossing the boundary?

Only by looking at the source. pikchr's own interface is only a single function so can't report that type of info.

It would be quite inconvenient to make an image and find some target implementation can't display it for being compiled with a lower complexity tolerance - or am I missing something obvious again?

Not obvious, per se, but your spider pushes the limits (perhaps even surpasses them) of what pikchr's really intended for: technical diagrams for embedding into docs. That's not to say it can't/shouldn't be used for such things, just that it's overstepping the limits foreseen for fossil. Perhaps, in light of this work of art, Richard would agree to raising it by a factor of 1.5 or 2 for fossil.

(8) By Ryan (RyanSmith) on 2022-07-28 23:34:05 in reply to 7 [link] [source]

but your spider pushes the limits...

I absolutely agree, and I also agree that it's not the intended use case, and that the spider is just a playtime thing to test what can be done, so I'm very happy to accept it is overpowering and not a good example use-case - nor expecting Richard to raise the complexity level (but happy if he does, if it is easily done and causes no harm).

My concern (or rather surprise) more stems from the fact that I did not expect it to work in one pikchr interpreter, and not in the next. I get why that is now, and now have a little flashing neon sign inside my mental workspace that is always on: "Beware the Token-Count"

I was thus hoping there was a way to measure my pikchr's token count complexity. i.e. - after figuring out the forum's limit is 10K tokens, be able to ensure my future pikchr creations are skinny enough in token terms to safely send to the forum, or to the general population. And if I do make a 1M token behemoth, to know I can only really share it with Quantum computing labs.

Perhaps an internal variable can reference the instantaneous token-count, so I can print it out at several stages in the image and see what it amounts to up to that point. Does not need to be 100% precise either, an approximate number will do.

This falls very much in the nice-to-have category - no urgency about it, even a note in the long-run roadmap would be appreciated.

(9) By Stephan Beal (stephan) on 2022-07-28 23:48:21 in reply to 8 [link] [source]

... nor expecting Richard to raise the complexity level (but happy if he does, if it is easily done and causes no harm).

It's just a build-time flag with a default of 100k: /info?name=90bcc7df74ea40b5&ln=133-135

My concern (or rather surprise) more stems from the fact that I did not expect it to work in one pikchr interpreter, and not in the next.

That's definitely unfortunate and confusing but the only way to work around that is to change the default for one project or the other. That decision is up to Richard.

I was thus hoping there was a way to measure my pikchr's token count complexity.

Not without running it through pikchr, unfortunately. You can get an approximation using the Unix "wc" command - the "word" count should be relatively close to correct for most pictures.

And if I do make a 1M token behemoth, to know I can only really share it with Quantum computing labs.

Then you'll need to compile your own copy.

Perhaps an internal variable can reference the instantaneous token-count, so I can print it out at several stages in the image and see what it amounts to up to that point. Does not need to be 100% precise either, an approximate number will do.

There's programmatically no way to do that. Pikchr accepts, as inputs, its whole source code and outputs either an HTML-format error message or an SVG image. Both before and after that, the tokens don't exist, so there is no reliable way for the app-level code to know how many there are. An app can guess a token count, and get relatively close, but doing so requires more editing features than the plain HTML textarea provides. Making that calculation (approximation) in the current UI would mean passing the whole pikchr text to the calculator every time we wanted to calculate it, which would be computationally unsettlingly expensive.

This falls very much in the nice-to-have categor ...

Exactly!

BTW: see post ef8684c6955a411a for the background behind the token limit.

(10.1) By Ryan (RyanSmith) on 2022-07-29 12:11:28 edited from 10.0 in reply to 9 [link] [source]

I agree with everything you said, but I feel we are talking past each other a bit. Allow me this one opportunity to try clarify and be sure we mean the same things:

  1. I'm 100% happy to build my own pikchr, and have already started that.
  2. I'm not suggesting a feature to pre-guess token counts, I'm happy for my pikchr to completely process the entire image and then, in hindsight, tell me how many tokens were involved, which it must know since it done all the token-making (so I can estimate if it is small enough for the forum or not)
  3. The feature I suggested on showing the token-count during the image creation is not an interface request, I meant to physically make it so that I can issue the command (as part of the image) [box "Tokens"; print tokencount;] or something similar somewhere inside the image's pikchr text, in the same way I could do [box "Box Width"; print boxwid;] to show it in the final produced image - like a debug console.log() but ending up in the actual compiled output rather than the non-existing console.

If this is the interpretation you already understood, then apologies, feel free to ignore. If this is different, then it doesn't really change anything, except that the feature request becomes possible in principle at least and we are on the same page, but still is a nice-to-have.