begin % draw sierpinski arrowhead curves using ascii art %
    integer CANVAS_WIDTH;
    CANVAS_WIDTH := 200;
    begin
        % the ascii art canvas and related items %
        string(1) array canvas ( 1 :: CANVAS_WIDTH, 1 :: CANVAS_WIDTH );
        integer         heading, asciiX, asciiY, width, maxX, maxY, minX, minY;
        % draw a line using ascii art - the length is ignored and the heading determines the %
        %                               character to use                                     %
        % the position is updated                                                            %
        procedure drawLine( real value length ) ;
            begin
                % stores the min and max coordinates %
                procedure updateCoordinateRange ;
                    begin
                        if asciiX > maxX then maxX := asciiX;
                        if asciiY > maxY then maxY := asciiY;
                        if asciiX < minX then minX := asciiX;
                        if asciiY < minY then minY := asciiY
                    end updateCoordinateRange ;
                if      heading =   0 then begin
                    updateCoordinateRange;
                    canvas( asciiX, asciiY ) := "_";
                    asciiX := asciiX + 1
                    end
                else if heading =  60 then begin
                    updateCoordinateRange;
                    canvas( asciiX, asciiY ) := "/";
                    asciiY := asciiY - 1;
                    asciiX := asciiX + 1
                    end
                else if heading = 120 then begin
                    asciiX := asciiX - 1;
                    updateCoordinateRange;
                    canvas( asciiX, asciiY ) := "\";
                    asciiY := asciiY - 1
                    end
                else if heading = 180 then begin
                    asciiX := asciiX - 1;
                    updateCoordinateRange;
                    canvas( asciiX, asciiY ) := "_"
                    end
                else if heading = 240 then begin
                    asciiX := asciiX - 1;
                    asciiY := asciiY + 1;
                    updateCoordinateRange;
                    canvas( asciiX, asciiY ) := "/"
                    end
                else if heading = 300 then begin
                    asciiY := asciiY + 1;
                    updateCoordinateRange;
                    canvas( asciiX, asciiY ) := "\";
                    asciiX := asciiX + 1
                end if_various_headings
            end drawLine ;
        % changes the heading by the specified angle ( in degrees ) - angle must be +/- 60 %
        procedure turn( integer value angle ) ;
            if angle > 0
            then heading := ( heading + angle ) rem 360
            else begin
                 heading := heading + angle;
                 if heading < 0 then heading := heading + 360
            end tuen ;
        % initialises the ascii art canvas %
        procedure initArt ;
            begin
                heading :=   0;
                asciiX  :=  CANVAS_WIDTH div 2;
                asciiY  := asciiX;
                maxX    := asciiX;
                maxY    := asciiY;
                minX    := asciiX;
                minY    := asciiY;
                for x := 1 until CANVAS_WIDTH do for y := 1 until CANVAS_WIDTH do canvas( x, y ) := " "
            end initArt ;
        % shows the used parts of the canvas %
        procedure drawArt ;
            begin
                for y := minY until maxY do begin
                    write();
                    for x := minX until maxX do writeon( canvas( x, y ) )
                end for_y ;
                write()
            end drawIArt ;
        % draws a sierpinski arrowhead curve of the specified order and line length %
        procedure sierpinskiArrowheadCurve( integer value order; real value length ) ;
            begin
                % recursively draws a segment of the sierpinski arrowhead curve %
                procedure curve( integer value order; real value length; integer value angle ) ;
                if 0 = order then drawline( length )
                else begin
                    curve( order - 1, length / 2, - angle );
                    turn(  angle );
                    curve( order - 1, length / 2,   angle );
                    turn(  angle );
                    curve( order - 1, length / 2, - angle )
                end curve ;
                if not odd( order ) then begin % order is even, we can just draw the curve. %
                    curve( order, length, +60 );
                    end
                else begin % order is odd %
                    turn( +60 );
                    curve( order, length, -60 )
                end if_not_odd_order__
            end sierpinskiArrowheadCurve ;
        % draw curves %
        i_w := 1; s_w := 0; % set output formatting %
        for order := 5 do begin
            write( "Sierpinski arrowhead curve of order ", order );
            write( "=====================================" );
            write();
            initArt;
            sierpinskiArrowheadCurve( order, 1 );
            drawArt
        end for_order
    end
end.


order	:= 7
theta	:= 0

curve := []
curve.curveW	:= 1000
curve.curveH	:= 1000
curve.iy	:= 1
curve.cx	:= curve.curveW/2
curve.cy	:= curve.curveH
curve.ch	:= curve.cx/2

arrowhead(order, curve, theta, Arr :=[])
xmin := xmax := ymin := ymax := 0
for i, point in Arr
{
	xmin := A_Index = 1 ? point.x : xmin < point.x ? xmin : point.x
	xmax := point.x > xmax ? point.x : xmax
	ymin := A_Index = 1 ? point.y : ymin < point.y ? ymin : point.y
	ymax := point.y > ymax ? point.y : ymax
}
arrowheadX := A_ScreenWidth/2 - (xmax-xmin)/2	, arrowheadY := A_ScreenHeight/2 - (ymax-ymin)/2
for i, point in Arr
	points .= point.x - xmin + arrowheadX "," point.y - ymin + arrowheadY "|"

points := Trim(points, "|")
gdip1()
Gdip_DrawLines(G, pPen, Points)
UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)
return

; ---------------------------------------------------------------
arrowhead(order, curve, theta, Arr) {
	length := curve.cx
	if (order&1 = 0)
		curve(order, length, theta, 60, Arr)
	else
	{
		theta := turn(theta, 60)
		theta := curve(order, length, theta, -60, Arr)
	}
	drawLine(length, theta, Arr)
}
; ---------------------------------------------------------------
drawLine(length, theta, Arr) {
	global curve
	Arr[Arr.count()+1, "x"] := curve.cx-curve.curveW/2+curve.ch
	Arr[Arr.count(), "y"] := (curve.curveH-curve.cy)*curve.iy+2*curve.ch
	pi := 3.141592653589793
	curve.cx := curve.cx + length * Cos(theta*pi/180)
	curve.cy := curve.cy + length * Sin(theta*pi/180)
}
; ---------------------------------------------------------------
turn(theta, angle) {
	return theta := Mod(theta+angle, 360)
}
; ---------------------------------------------------------------
curve(order, length, theta, angle, Arr) {
	if (order = 0)
		drawLine(length, theta, Arr)
	else
	{
		theta := curve(order-1, length/2, theta, -angle, Arr)
		theta := turn(theta, angle)
		theta := curve(order-1, length/2, theta, angle, Arr)
		theta := turn(theta, angle)
		theta := curve(order-1, length/2, theta, -angle, Arr)
	}
	return theta
}
; ---------------------------------------------------------------
gdip1(){
	global
	If !pToken := Gdip_Startup()
	{
		MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system
		ExitApp
	}
	OnExit, Exit
	Width := A_ScreenWidth, Height := A_ScreenHeight
	Gui, 1: -Caption +E0x80000 +LastFound +OwnDialogs +Owner +AlwaysOnTop
	Gui, 1: Show, NA
	hwnd1 := WinExist()
	hbm := CreateDIBSection(Width, Height)
	hdc := CreateCompatibleDC()
	obm := SelectObject(hdc, hbm)
	G := Gdip_GraphicsFromHDC(hdc)
	Gdip_SetSmoothingMode(G, 4)
	pPen := Gdip_CreatePen(0xFFFF0000, 2)
}
; ---------------------------------------------------------------
gdip2(){
	global
	Gdip_DeleteBrush(pBrush)
	Gdip_DeletePen(pPen)
	SelectObject(hdc, obm)
	DeleteObject(hbm)
	DeleteDC(hdc)
	Gdip_DeleteGraphics(G)
}
; ---------------------------------------------------------------
Exit:
gdip2()
Gdip_Shutdown(pToken)
ExitApp
Return


msg$ = "There have been no clicks yet"
[loop] cls                                        ' clear screen
print msg$
button #c, "Click Me", [clickMe]                  'create a button with handle and goto [tag]
wait

[clickMe]
clicks = clicks + 1
msg$ = "Button has been clicked ";clicks;" times"
goto [loop]


use iced::{ // 0.2.0
    button, Button, Column, Element, Length,
    Text, Sandbox, Settings, Space,
};

#[derive(Debug, Copy, Clone)]
struct Pressed;
struct Simple {
    value: i32,
    button: button::State,
}

impl Sandbox for Simple {
    type Message = Pressed;

    fn new() -> Simple {
        Simple {
            value: 0,
            button: button::State::new(),
        }
    }

    fn title(&self) -> String {
        "Simple Windowed Application".into()
    }

    fn view(&mut self) -> Element<Self::Message> {
        Column::new()
            .padding(20)
            .push({
                let text = match self.value {
                    0 => "there have been no clicks yet".into(),
                    1 => "there has been 1 click".into(),
                    n => format!("there have been {} clicks", n),
                };
                Text::new(text).size(24)
            }).push(
                Space::with_height(Length::Fill)
            ).push(
                Button::new(&mut self.button, Text::new("Click Me!"))
                    .on_press(Pressed)
            ).into()
    }

    fn update(&mut self, _: Self::Message) {
        self.value += 1;
    }
}

fn main() {
    let mut settings = Settings::default();
    settings.window.size = (600, 400);
    Simple::run(settings).unwrap();
}


import scala.swing.{ BorderPanel, Button, Label, MainFrame, SimpleSwingApplication }
import scala.swing.event.ButtonClicked

object SimpleApp extends SimpleSwingApplication {
  def top = new MainFrame {
    contents = new BorderPanel {
      var nClicks = 0

      val (button, label) = (new Button { text = "click me" },
        new Label { text = "There have been no clicks yet" })

      layout(button) = BorderPanel.Position.South
      layout(label) = BorderPanel.Position.Center
      listenTo(button)
      reactions += {
        case ButtonClicked(_) =>
          nClicks += 1
          label.text = s"There have been ${nClicks} clicks"
      }
    }
  }
}


// See https://en.wikipedia.org/wiki/Sierpi%C5%84ski_curve#Arrowhead_curve
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

// Structure to keep track of current position and orientation
typedef struct cursor_tag {
    double x;
    double y;
    int angle;
} cursor_t;

void turn(cursor_t* cursor, int angle) {
    cursor->angle = (cursor->angle + angle) % 360;
}

void draw_line(FILE* out, cursor_t* cursor, double length) {
    double theta = (M_PI * cursor->angle)/180.0;
    cursor->x += length * cos(theta);
    cursor->y += length * sin(theta);
    fprintf(out, "L%g,%g\n", cursor->x, cursor->y);
}

void curve(FILE* out, int order, double length, cursor_t* cursor, int angle) {
    if (order == 0) {
        draw_line(out, cursor, length);
    } else {
        curve(out, order - 1, length/2, cursor, -angle);
        turn(cursor, angle);
        curve(out, order - 1, length/2, cursor, angle);
        turn(cursor, angle);
        curve(out, order - 1, length/2, cursor, -angle);
    }
}

void write_sierpinski_arrowhead(FILE* out, int size, int order) {
    const double margin = 20.0;
    const double side = size - 2.0 * margin;
    cursor_t cursor;
    cursor.angle = 0;
    cursor.x = margin;
    cursor.y = 0.5 * size + 0.25 * sqrt(3) * side;
    if ((order & 1) != 0)
        turn(&cursor, -60);
    fprintf(out, "<svg xmlns='http://www.w3.org/2000/svg' width='%d' height='%d'>\n",
            size, size);
    fprintf(out, "<rect width='100%%' height='100%%' fill='white'/>\n");
    fprintf(out, "<path stroke-width='1' stroke='black' fill='none' d='");
    fprintf(out, "M%g,%g\n", cursor.x, cursor.y);
    curve(out, order, side, &cursor, 60);
    fprintf(out, "'/>\n</svg>\n");
}

int main(int argc, char** argv) {
    const char* filename = "sierpinski_arrowhead.svg";
    if (argc == 2)
        filename = argv[1];
    FILE* out = fopen(filename, "w");
    if (!out) {
        perror(filename);
        return EXIT_FAILURE;
    }
    write_sierpinski_arrowhead(out, 600, 8);
    fclose(out);
    return EXIT_SUCCESS;
}


when flag clicked          # when program is run
set counter to "0"         # initialize counter object to zero
set message to "There have been no clicks"
show variable message      # show the message object

when this sprite clicked   # when button clicked
hide message               # hide the initial message
change counter by 1        # increment the counter object


require('Gtk2') -> init

# Window.
var window = %s<Gtk2::Window>.new
window.signal_connect('destroy' => { %s<Gtk2>.main_quit })

# VBox.
var vbox = %s<Gtk2::VBox>.new(0, 0)
window.add(vbox)

# Label.
var label = %s<Gtk2::Label>.new('There have been no clicks yet.')
vbox.add(label)

# Button.
var count = 0
var button = %s<Gtk2::Button>.new(' Click Me ')
vbox.add(button)
button.signal_connect('clicked' => {
    label.set_text(++count)
})

# Show.
window.show_all

# Main loop.
%s<Gtk2>.main


|top clickCount lh button|

clickCount := 0.
lh := ValueHolder with:'There have been no clicks yet'.

top := StandardSystemView label:'Rosetta Simple Window'.
top extent:300@100.
top add:((Label new labelChannel:lh) origin: 0 @ 10 corner: 1.0 @ 40).
top add:((button := Button label:'Eat Me') origin: 10 @ 50 corner: 100 @ 80).

button action:[
        clickCount := clickCount + 1.
        lh value: ('number of clicks: %1' bindWith:clickCount)
       ].

top open


#include <fstream>
#include <iostream>
#include <vector>

constexpr double sqrt3_2 = 0.86602540378444; // sqrt(3)/2

struct point {
    double x;
    double y;
};

std::vector<point> sierpinski_arrowhead_next(const std::vector<point>& points) {
    size_t size = points.size();
    std::vector<point> output(3*(size - 1) + 1);
    double x0, y0, x1, y1;
    size_t j = 0;
    for (size_t i = 0; i + 1 < size; ++i, j += 3) {
        x0 = points[i].x;
        y0 = points[i].y;
        x1 = points[i + 1].x;
        y1 = points[i + 1].y;
        double dx = x1 - x0;
        output[j] = {x0, y0};
        if (y0 == y1) {
            double d = dx * sqrt3_2/2;
            if (d < 0) d = -d;
            output[j + 1] = {x0 + dx/4, y0 - d};
            output[j + 2] = {x1 - dx/4, y0 - d};
        } else if (y1 < y0) {
            output[j + 1] = {x1, y0};
            output[j + 2] = {x1 + dx/2, (y0 + y1)/2};
        } else {
            output[j + 1] = {x0 - dx/2, (y0 + y1)/2};
            output[j + 2] = {x0, y1};
        }
    }
    output[j] = {x1, y1};
    return output;
}

void write_sierpinski_arrowhead(std::ostream& out, int size, int iterations) {
    out << "<svg xmlns='http://www.w3.org/2000/svg' width='"
        << size << "' height='" << size << "'>\n";
    out << "<rect width='100%' height='100%' fill='white'/>\n";
    out << "<path stroke-width='1' stroke='black' fill='none' d='";
    const double margin = 20.0;
    const double side = size - 2.0 * margin;
    const double x = margin;
    const double y = 0.5 * size + 0.5 * sqrt3_2 * side;
    std::vector<point> points{{x, y}, {x + side, y}};
    for (int i = 0; i < iterations; ++i)
        points = sierpinski_arrowhead_next(points);
    for (size_t i = 0, n = points.size(); i < n; ++i)
        out << (i == 0 ? "M" : "L") << points[i].x << ',' << points[i].y << '\n';
    out << "'/>\n</svg>\n";
}

int main() {
    std::ofstream out("sierpinski_arrowhead.svg");
    if (!out) {
        std::cerr << "Cannot open output file\n";
        return EXIT_FAILURE;
    }
    write_sierpinski_arrowhead(out, 600, 8);
    return EXIT_SUCCESS;
}


open XWindows ;
open Motif ;

val countWindow = fn () =>

 let
  val ctr   =  ref 0;
  val shell =  XtAppInitialise     ""    "demo" "top" [] [XmNwidth 300, XmNheight 150 ] ;
  val main  =  XmCreateMainWindow  shell    "main"       [XmNmappedWhenManaged true ] ;
  val frame =  XmCreateForm        main     "frame"      [XmNwidth 390, XmNheight 290 ] ;
  val text  =  XmCreateLabel       frame    "show"       [XmNlabelString "No clicks yet" ] ;
  val buttn =  XmCreateDrawnButton frame    "press"      [XmNwidth 75 , XmNheight 30 ,
                                            XmNlabelString "Click me" ,
                                            XmNbottomAttachment XmATTACH_POSITION,XmNbottomPosition 98 ] ;
  val report = fn (w,c,t)  =>
     (XtSetValues text [XmNlabelString (Int.toString (ctr:= !ctr +1; !ctr)) ] ; t )
 in

  (
   XtSetCallbacks   buttn      [ (XmNactivateCallback , report) ] XmNarmCallback ;
   XtManageChildren [ text,buttn ] ;
   XtManageChildren [ frame ] ;
   XtManageChild    main ;
   XtRealizeWidget  shell
  )

end;


package require Tk
pack [label .l -text "There have been no clicks yet"]
set count 0
pack [button .b -text "click me" -command upd]
proc upd {} {
    .l configure -text "Number of clicks: [incr ::count]"
}


Prgm
  Local clicks
  0 → clicks
  1 → ok    © System variable also set by Dialog statement
  While ok = 1
    Dialog
      Title "Rosetta Code"
      Text "There have been " & string(clicks) & " OKs"
    EndDlog
    clicks + 1 → clicks
  EndWhile
EndPrgm


import gui
$include "guih.icn"

class SimpleApp : Dialog (label)

  # -- automatically called when the dialog is created
  method component_setup()
    # create and add the label
    label := Label("label=There have been no clicks yet","pos=50%,33%", "align=c,c")
    add (label)

    # create and add the button
    button := TextButton("label=Click me", "pos=50%,66%", "align=c,c")
    button.connect(self, "clicked", ACTION_EVENT)
    add (button)

    # some cosmetic settings for the window
    attrib("size=180,70", "bg=light gray")
  end

  method clicked ()
    static count := 0
    count +:= 1
    label.set_label ("Clicked " || count || " times")
  end
end

procedure main()
  local d := SimpleApp ()
  d.show_modal()
end


USING: accessors L-system ui ;

: arrowhead ( L-system -- L-system )
    L-parser-dialect >>commands
    [ 60 >>angle ] >>turtle-values
    "XF" >>axiom
    {
        { "X" "YF+XF+Y" }
        { "Y" "XF-YF-X" }
    } >>rules ;

[ <L-system> arrowhead "Arrowhead" open-window ] with-ui


( ASCII output with use of ANSI terminal control )

: draw-line ( direction -- )
  case
  0 of  .\" _"              endof ( horizontal right:          _          )
  1 of  .\" \e[B\\"         endof (       down right:     CUD  \          )
  2 of  .\" \e[D\e[B/\e[D"  endof (        down left: CUB CUD  /  CUB     )
  3 of  .\" \e[D_\e[D"      endof (  horizontal left:     CUB  _  CUB     )
  4 of  .\" \e[D\\\e[A\e[D" endof (          up left:     CUB  \  CUU CUB )
  5 of  .\" /\e[A"          endof (         up right:          /  CUU     )
  endcase                         ( cursor is up-right of the last point  )
;

: turn+ 1+ 6 mod ;
: turn- 1- 6 mod ;

defer curve
: A-rule ( order direction -- ) turn+  2dup 'B curve  turn-  2dup 'A curve  turn-  'B curve ;
: B-rule ( order direction -- ) turn-  2dup 'A curve  turn+  2dup 'B curve  turn+  'A curve ;

:noname ( order direction type -- )
  2 pick 0 = if drop draw-line drop exit then \ draw line when order is 0
  rot 1- rot rot
  'A = if A-rule else B-rule then
; is curve

: arrowhead ( order -- )
  page
  s" Sierpinski arrowhead curve of order " type dup . cr
  s" =====================================" type cr
  0 'A curve
;

5 arrowhead


package main

import (
    "github.com/fogleman/gg"
    "math"
)

var (
    width  = 770.0
    height = 770.0
    dc     = gg.NewContext(int(width), int(height))
    iy     = 1.0
    theta  = 0
)

var cx, cy, h float64

func arrowhead(order int, length float64) {
    // if order is even, we can just draw the curve
    if order&1 == 0 {
        curve(order, length, 60)
    } else {
        turn(60)
        curve(order, length, -60)
    }
    drawLine(length) // needed to make base symmetric
}

func drawLine(length float64) {
    dc.LineTo(cx-width/2+h, (height-cy)*iy+2*h)
    rads := gg.Radians(float64(theta))
    cx += length * math.Cos(rads)
    cy += length * math.Sin(rads)
}

func turn(angle int) {
    theta = (theta + angle) % 360
}

func curve(order int, length float64, angle int) {
    if order == 0 {
        drawLine(length)
    } else {
        curve(order-1, length/2, -angle)
        turn(angle)
        curve(order-1, length/2, angle)
        turn(angle)
        curve(order-1, length/2, -angle)
    }
}

func main() {
    dc.SetRGB(0, 0, 0) // black background
    dc.Clear()
    order := 6
    if order&1 == 0 {
        iy = -1 // apex will point upwards
    }
    cx, cy = width/2, height
    h = cx / 2
    arrowhead(order, cx)
    dc.SetRGB255(255, 0, 255) // magenta curve
    dc.SetLineWidth(2)
    dc.Stroke()
    dc.SavePNG("sierpinski_arrowhead_curve.png")
}


Reg_Set(10, "There have been no clicks yet")
#1 = 0
repeat (ALL) {
    #2 = Dialog_Input_1(3, "`Simple Windowed Application`,
                        `|@(10)`,
                        `[&Click me]`,`[&Exit]`",
                        APP+CENTER, 0, 0)
    if (#2 != 1) { break }    // ESC or Exit
    #1++
    Num_Str(#1, 10)
    Reg_Set(10, "Clicked", INSERT)
    Reg_Set(10, " times", APPEND)
}


VERSION 5.00
Begin VB.Form Form2
   Caption         =   "There have been no clicks yet"
   ClientHeight    =   2940
   ClientLeft      =   60
   ClientTop       =   600
   ClientWidth     =   8340
   LinkTopic       =   "Form1"
   ScaleHeight     =   2940
   ScaleWidth      =   8340
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton Command1
      Caption         =   "Click me!"
      Height          =   495
      Left            =   3600
      TabIndex        =   0
      Top             =   1200
      Width           =   1215
   End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'-----user-written code begins here; everything above this line is hidden in the GUI-----
Private clicked As Long

Private Sub Command1_Click()
    clicked = clicked + 1
    Me.Caption = clicked & " clicks."
End Sub


@1Introduction.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public Licence as published by
the Free Software Foundation, either version 3 of the Licence, or
(at your option) any later version.

Copyright (c) 2012 Sian Mountbatten.

@m cvs simpleapp = "$Id: $"

@[email protected]<[email protected]>
BEGIN
@<Included [email protected]>
@<Plain values in the outer [email protected]>
@<Names in the outer [email protected]>
@<Procedures in the outer [email protected]>
@<Logic in the outer [email protected]>
END
@<[email protected]>

@ The local compiler requires a special [email protected]^system [email protected]>

@<[email protected]>=
PROGRAM simpleapp CONTEXT VOID
USE @<Library [email protected]>

@ And a special [email protected]^system [email protected]>

@<[email protected]>=
FINISH

@ The program requires the predefined forms and the standard prelude.

@<[email protected]>=
simpleapp fd,standard

@ This Web 68 file provides the Xforms prelude.

@[email protected]>

@1Initialisation.
The initial processing consists of initialising the Xforms library.

@<[email protected]>=
open(argf,"",arg channel);
fl initialize(argc,argv,"simpleapp",NIL,0);

@ Declare the !REF FILE!.

@<[email protected]>=
FILE @!argf;

@1Main processing.
The form is created in !simpleapp fd! the source code of which is 108 lines long.
Firstly, define the callback for the button.

@
=
button cb:=(REF FLOBJECT obj,INT data)VOID:
(
	clicks +:= 1;
	fl set object label(text box OF click form,whole(clicks,0)+" click"+
	                    (clicks=1|""|"s")+" on the button")
);

@ Declare !clicks!.

@<[email protected]>=
INT clicks:=0;

@ Create the form, show it and hand control to the Xforms library.

@<[email protected]>=
click form:=create form click;
fl show form(click OF click form,fl place center,fl full border,"SimpleApp");
fl do forms

@ Declare the form.

@<[email protected]>=
REF FDCLICK click form;

@1Macro declarations.
All the macros used in the program are declared here.

@<[email protected]>=
macro fl do forms;
macro fl initialize;
macro fl set object label;
macro fl show form;

@ To compile the program, use this command:

   ca -l mod -l forms simpleapp.w68

The predefined form will have been compiled with this command:

   ca -m mod simpleappfd.w68

The predefined form was created by the fdesign program for the Xforms library,
and the resulting form definition file was converted to Web 68 by the program
fdtow68.


import "graphics" for Canvas, Color
import "input" for Mouse
import "dome" for Window

class SimpleWindowedApplication {
    construct new(width, height) {
        Window.title = "Simple windowed application"
        _fore = Color.white
        _clicks = 0
    }

    init() {
        drawControls()
    }

    update() {
        if (Mouse["left"].justPressed && insideButton) _clicks = _clicks + 1
    }

    draw(alpha) {
        drawControls()
    }

    insideButton {
        var p = Mouse.position
        return p.x >= 120 && p.x <= 200 && p.y >= 90 && p.y <= 170
    }

    drawControls() {
        Canvas.cls()
        if (_clicks == 0) {
            Canvas.print("There have been no clicks yet", 40, 40, _fore)
        } else if (_clicks == 1) {
            Canvas.print("The button has been clicked once", 30, 40, _fore)
        } else {
            Canvas.print("The button has been clicked %(_clicks) times", 10, 40, _fore)
        }
        Canvas.rectfill(120, 90, 80, 80, Color.red)
        Canvas.rect(120, 90, 80, 80, Color.blue)
        Canvas.print("click me", 130, 125, _fore)
    }
}

var Game = SimpleWindowedApplication.new(600, 600)


include "turtle" {search: "."};

# Compute the curve using a Lindenmayer system of rules
def rules:
  {X: "Yf+Xf+Y", Y: "Xf-Yf-X", "": "X"};

def sierpinski($count):
  rules as $rules
  | def repeat($count):
      if $count == 0 then .
      else ascii_downcase | gsub("x"; $rules["X"]) | gsub("y"; $rules["Y"])
      | repeat($count-1)
      end;
  $rules[""] | repeat($count) ;

def interpret($x):
  if   $x == "+" then turtleRotate(60)
  elif $x == "-" then turtleRotate(-60)
  elif $x == "f" then turtleForward(3)
  else .
  end;

def sierpinski_curve($n):
  sierpinski($n)
  | split("")
  | reduce .[] as $action (
      turtle([0,-350]) | turtleDown | turtleRotate(60);
      interpret($action) ) ;

# viewBox = <min-x> <min-y> <width> <height>
# Input: {svg, minx, miny, maxx, maxy}
def svg:
  "<svg viewBox='\(.minx|floor) \(.miny - 4 |floor) \(.maxx - .minx|ceil) \(6 + .maxy - .miny|ceil)'",
  "     preserveAspectRatio='xMinYmin meet'",
  "     xmlns='http://www.w3.org/2000/svg' >",
  path("none"; "red"; 1),
  "</svg>";

sierpinski_curve(8)
| svg


using Lindenmayer # https://github.com/cormullion/Lindenmayer.jl

scurve = LSystem(Dict("F" => "G+F+Gt", "G"=>"F-G-F"), "G")

drawLSystem(scurve,
    forward = 3,
    turn = 60,
    startingy = -350,
    iterations = 8,
    startingorientation = π/3,
    filename = "sierpinski_arrowhead_curve.png",
    showpreview = true
)


{def sierp
 {def sierp.r
  {lambda {:order :length :angle}
   {if {= :order 0}
    then M:length
    else {sierp.r {- :order 1} {/ :length 2} {- :angle}}
         T:angle
         {sierp.r {- :order 1} {/ :length 2} :angle}
         T:angle
         {sierp.r {- :order 1} {/ :length 2} {- :angle}}
 }}}
 {lambda {:order :length}
  {if {= {% :order 2} 0}
   then {sierp.r :order :length 60}
   else T60
        {sierp.r :order :length -60}
}}}

the output can be seen in http://lambdaway.free.fr/lambdawalks/?view=sierpinsky


ClearAll[DoStep]
DoStep[Line[{x_, y_}]] := Module[{diff, perp, pts},
  diff = y - x;
  perp = Cross[diff] Sqrt[3]/2;
  pts = {x, x + diff/4 + perp/2, x + 3 diff/4 + perp/2, y};
  {Line[pts[[{2, 1}]]], Line[pts[[{2, 3}]]], Line[pts[[{4, 3}]]]}
  ]
lns = {Line[{{0.0, 0.0}, {1.0, 0.0}}]};
lns = Nest[Catenate[DoStep /@ #] &, lns, 5];
Graphics[lns]


import math

const Sqrt3_2 = sqrt(3.0) / 2.0

type Point = tuple[x, y: float]

func sierpinskiArrowheadNext(points: seq[Point]): seq[Point] =
  result.setLen(3 * (points.len - 1) + 1)
  var j = 0
  for i in 0..<points.high:
    let (x0, y0) = points[i]
    let (x1, y1) = points[i + 1]
    let dx = x1 - x0
    result[j] = (x0, y0)
    if y0 == y1:
      let d = abs(dx * Sqrt3_2 / 2)
      result[j + 1] = (x0 + dx / 4, y0 - d)
      result[j + 2] = (x1 - dx / 4, y0 - d)
    elif y1 < y0:
      result[j + 1] = (x1, y0)
      result[j + 2] = (x1 + dx / 2, (y0 + y1) / 2)
    else:
      result[j + 1] = (x0 - dx / 2, (y0 + y1) / 2)
      result[j + 2] = (x0, y1)
    inc j, 3
  result[j] = points[^1]

proc writeSierpinskiArrowhead(outfile: File; size, iterations: int) =
  outfile.write "<svg xmlns='http://www.w3.org/2000/svg' width='", size, "' height='", size, "'>\n"
  outfile.write "<rect width='100%' height='100%' fill='white'/>\n"
  outfile.write "<path stroke-width='1' stroke='black' fill='none' d='"
  const Margin = 20.0
  let side = size.toFloat - 2 * Margin
  let x = Margin
  let y = 0.5 * size.toFloat + 0.5 * Sqrt3_2 * side
  var points = @[(x: x, y: y), (x: x + side, y: y)]
  for _ in 1..iterations:
    points = sierpinskiArrowheadNext(points)
  for i, point in points:
    outfile.write if i == 0: 'M' else: 'L', point.x, ',', point.y, '\n'
  outfile.write "'/>\n</svg>\n"

let outfile = open("sierpinski_arrowhead.svg", fmWrite)
outfile.writeSierpinskiArrowhead(600, 8)
outfile.close()


use strict;
use warnings;
use SVG;
use List::Util qw(max min);
use constant pi => 2 * atan2(1, 0);

my %rules = (
    X => 'YF+XF+Y',
    Y => 'XF-YF-X'
);
my $S = 'Y';
$S =~ s/([XY])/$rules{$1}/eg for 1..7;

my (@X, @Y);
my ($x, $y) = (0, 0);
my $theta   = 0;
my $r       = 6;

for (split //, $S) {
    if (/F/) {
        push @X, sprintf "%.0f", $x;
        push @Y, sprintf "%.0f", $y;
        $x += $r * cos($theta);
        $y += $r * sin($theta);
    }
    elsif (/\+/) { $theta += pi/3; }
    elsif (/\-/) { $theta -= pi/3; }
}

my ($xrng, $yrng) = ( max(@X) - min(@X),  max(@Y) - min(@Y));
my ($xt,   $yt)   = (-min(@X) + 10,      -min(@Y) + 10);

my $svg = SVG->new(width=>$xrng+20, height=>$yrng+20);
my $points = $svg->get_path(x=>\@X, y=>\@Y, -type=>'polyline');
$svg->rect(width=>"100%", height=>"100%", style=>{'fill'=>'black'});
$svg->polyline(%$points, style=>{'stroke'=>'orange', 'stroke-width'=>1}, transform=>"translate($xt,$yt)");

open my $fh, '>', 'sierpinski-arrowhead-curve.svg';
print $fh  $svg->xmlify(-namespace=>'svg');
close $fh;


include c:\cxpl\stdlib;         \standard library provides mouse routines, etc.
def Ww=40, Wh=12, Wx=(80-Ww)/2, Wy=(25-Wh)/2;           \window width, etc.
def Bw=11, Bh=4, Bx=Wx+(Ww-Bw)/2, By=Wy+3*(Wh-Bh)/4;    \button size & position
int Clicks, Mx, My;             \number of clicks and mouse coordinates

[ShowCursor(false);                             \turn off flashing cursor
Attrib($1F);                                    \bright white characters on blue
SetWind(Wx, Wy, Wx+Ww, Wy+Wh, 2, true);         \blue window with no scroll
DrawBox(Wx, Wy, Wx+Ww, Wy+Wh, 3);               \draw borders
Cursor(Wx+5, Wy+3);  Text(6, "There have been no clicks yet.");
DrawBox(Bx, By, Bx+Bw, By+Bh, 0);               \draw button
Cursor(Bx+2, By+2);  Text(6, "Click me");

OpenMouse;
ShowMouse(true);
Clicks:= 0;
repeat  if GetMouseButton(0) then               \left button down
                [while GetMouseButton(0) do []; \wait for release
                Mx:= GetMousePosition(0) / 8;   \character coordinates
                My:= GetMousePosition(1) / 8;
                if Mx>=Bx & Mx<=Bx+Bw & My>=By & My<=By+Bh then
                        [Clicks:= Clicks+1;     \mouse pointer is on the button
                        Cursor(Wx+4, Wy+3);
                        Text(6, "Times button has been clicked: ");
                        IntOut(6, Clicks);
                        ];
                ];
until   ChkKey;         \keystroke terminates program
SetVid(3);              \turn off mouse and turn on flashing cursor
]


#include "button.i"

window, 0;
btn_click = Button(text="click me", x=.395, y=.65, dx=0.04368, dy=0.0091);
btn_quit = Button(text="quit", x=.395, y=.6, dx=0.02184, dy=0.0091);
count = 0;
msg = "There have been no clicks yet";
finished = 0;
do {
    fma;
    plt, msg, .395, .7, justify="CH";
    button_plot, btn_click;
    button_plot, btn_quit;
    xy = mouse(0, 0, "");
    if(button_test(btn_click, xy(1), xy(2))) {
        count++;
        msg = swrite(format="Number of clicks: %d", count);
    } else if(button_test(btn_quit, xy(1), xy(2))) {
        finished = 1;
        winkill, 0;
    }
} while(!finished);


--
-- demo\rosetta\Sierpinski_arrowhead_curve.exw
-- ===========================================
--
--  Draws curves lo to hi (simultaneously), initially {6,6}, max {10,10}, min {1,1}
--  Press +/- to change hi, shift +/- to change lo.
--  ("=_" are also mapped to "+-", for the non-numpad +/-)
--
with javascript_semantics
include pGUI.e

Ihandle dlg, canvas
cdCanvas cddbuffer, cdcanvas

integer width, height,
        lo = 6, hi = 6
atom cx, cy, h, theta

integer iy = +1

procedure draw_line(atom l)
    cdCanvasVertex(cddbuffer, cx-width/2+h, (height-cy)*iy+2*h)
    cx += l*cos(theta*CD_DEG2RAD)
    cy += l*sin(theta*CD_DEG2RAD)
end procedure

procedure turn(integer angle)
    theta = mod(theta+angle,360)
end procedure

procedure curve(integer order, atom l, integer angle)
    if order=0 then
        draw_line(l)
    else
        curve(order-1, l/2, -angle)
        turn(angle)
        curve(order-1, l/2,  angle)
        turn(angle)
        curve(order-1, l/2, -angle)
    end if
end procedure

procedure sierpinski_arrowhead_curve(integer order, atom l)
    -- If order is even we can just draw the curve.
    if and_bits(order,1)=0 then
        curve(order, l, +60)
    else -- order is odd
        turn( +60)
        curve(order, l, -60)
    end if
    draw_line(l)
end procedure

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, /*posy*/)
    {width, height} = IupGetIntInt(canvas, "DRAWSIZE")
    cdCanvasActivate(cddbuffer)
    for order=lo to hi do
        cx = width/2
        cy = height
        h = cx/2
        theta = 0
        iy = iff(and_bits(order,1)?-1:+1)
        cdCanvasBegin(cddbuffer, CD_OPEN_LINES)
        sierpinski_arrowhead_curve(order, cx)
        cdCanvasEnd(cddbuffer)
    end for
    cdCanvasFlush(cddbuffer)
    return IUP_DEFAULT
end function

function map_cb(Ihandle ih)
    cdcanvas = cdCreateCanvas(CD_IUP, ih)
    cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
    cdCanvasSetBackground(cddbuffer, CD_WHITE)
    cdCanvasSetForeground(cddbuffer, CD_BLUE)
    return IUP_DEFAULT
end function

function key_cb(Ihandle /*ih*/, atom c)
    if c=K_ESC then return IUP_CLOSE end if
    if find(c,"+=-_") then
        bool bShift = IupGetInt(NULL,"SHIFTKEY")
        if c='+' or c='=' then
            if bShift then
                lo = min(lo+1,hi)
            else
                hi = min(10,hi+1)
            end if
        elsif c='-' or c='_' then
            if bShift then
                lo = max(1,lo-1)
            else
                hi = max(lo,hi-1)
            end if
        end if
        IupSetStrAttribute(dlg, "TITLE", "Sierpinski arrowhead curve (%d..%d)",{lo,hi})
        cdCanvasClear(cddbuffer)
        IupUpdate(canvas)
    end if
    return IUP_DEFAULT
end function

procedure main()
    IupOpen()

    canvas = IupCanvas(NULL)
    IupSetAttribute(canvas, "RASTERSIZE", "770x770")
    IupSetCallback(canvas, "MAP_CB", Icallback("map_cb"))
    IupSetCallback(canvas, "ACTION", Icallback("redraw_cb"))

    dlg = IupDialog(canvas)
    IupSetAttribute(dlg, "TITLE", "Sierpinski arrowhead curve (6..6)")
    IupSetCallback(dlg, "K_ANY", Icallback("key_cb"))

    IupMap(dlg)
    IupShowXY(dlg,IUP_CENTER,IUP_CENTER)
    if platform()!=JS then
        IupMainLoop()
        IupClose()
    end if
end procedure

main()


final PVector t = new PVector(20, 30, 60);

void setup() {
  size(450, 400);
  noLoop();
  background(0, 0, 200);
  stroke(-1);
  sc(7, 400, -60, t);
}

PVector sc(int o, float l, final int a, final PVector s) {
  if (o > 0) {
    sc(--o, l *= .5, -a, s).z += a;
    sc(o, l, a, s).z += a;
    sc(o, l, -a, s);
  } else line(s.x, s.y,
    s.x += cos(radians(s.z)) * l,
    s.y += sin(radians(s.z)) * l);
  return s;
}


import matplotlib.pyplot as plt
import math

def nextPoint(x, y, angle):
    a = math.pi * angle / 180
    x2 = (int)(round(x + (1 * math.cos(a))))
    y2 = (int)(round(y + (1 * math.sin(a))))
    return x2, y2

def expand(axiom, rules, level):
    for l in range(0, level):
        a2 = ""
        for c in axiom:
            if c in rules:
                a2 += rules[c]
            else:
                a2 += c
        axiom = a2
    return axiom

def draw_lsystem(axiom, rules, angle, iterations):
    xp = [1]
    yp = [1]
    direction = 0
    for c in expand(axiom, rules, iterations):
        if c == "F":
            xn, yn = nextPoint(xp[-1], yp[-1], direction)
            xp.append(xn)
            yp.append(yn)
        elif c == "-":
            direction = direction - angle
            if direction < 0:
                direction = 360 + direction
        elif c == "+":
            direction = (direction + angle) % 360

    plt.plot(xp, yp)
    plt.show()

if __name__ == '__main__':
    # Sierpinski Arrowhead Curve L-System Definition
    s_axiom = "XF"
    s_rules = {"X": "YF+XF+Y",
               "Y": "XF-YF-X"}
    s_angle = 60

    draw_lsystem(s_axiom, s_rules, s_angle, 7)


main:-
    write_sierpinski_arrowhead('sierpinski_arrowhead.svg', 600, 8).

write_sierpinski_arrowhead(File, Size, Order):-
    open(File, write, Stream),
    format(Stream,
           "<svg xmlns='http://www.w3.org/2000/svg' width='~d' height='~d'>\n",
           [Size, Size]),
    write(Stream, "<rect width='100%' height='100%' fill='white'/>\n"),
    write(Stream, "<path stroke-width='1' stroke='black' fill='none' d='"),
    Margin = 20.0,
    Side is Size - 2.0 * Margin,
    X = Margin,
    Y is 0.5 * Size + 0.25 * sqrt(3) * Side,
    Cursor = cursor(X, Y, 0),
    (Order mod 2 == 1 -> turn(Cursor, -60, Cursor1) ; Cursor1 = Cursor),
    format(Stream, "M~g,~g", [X, Y]),
    curve(Stream, Order, Side, Cursor1, _, 60),
    write(Stream, "'/>\n</svg>\n"),
    close(Stream).

turn(cursor(X, Y, A), Angle, cursor(X, Y, A1)):-
    A1 is (A + Angle) mod 360.

draw_line(Stream, cursor(X, Y, A), Length, cursor(X1, Y1, A)):-
    Theta is (pi * A)/180.0,
    X1 is X + Length * cos(Theta),
    Y1 is Y + Length * sin(Theta),
    format(Stream, "L~g,~g", [X1, Y1]).

curve(Stream, 0, Length, Cursor, Cursor1, _):-
    !,
    draw_line(Stream, Cursor, Length, Cursor1).
curve(Stream, Order, Length, Cursor, Cursor1, Angle):-
    Order1 is Order - 1,
    Angle1 is -Angle,
    Length2 is Length/2.0,
    curve(Stream, Order1, Length2, Cursor, Cursor2, Angle1),
    turn(Cursor2, Angle, Cursor3),
    curve(Stream, Order1, Length2, Cursor3, Cursor4, Angle),
    turn(Cursor4, Angle, Cursor5),
    curve(Stream, Order1, Length2, Cursor5, Cursor1, Angle1).


  [ $ "turtleduck.qky" loadfile ] now!

  [ stack ]                      is switch.arg (   --> [ )

  [ switch.arg put ]             is switch     ( x -->   )

  [ switch.arg release ]         is otherwise  (   -->   )

  [ switch.arg share
    != iff ]else[ done
    otherwise ]'[ do ]done[ ]    is case       ( x -->   )

  [ $ "" swap witheach
      [ nested quackery join ] ] is expand     ( $ --> $ )

  [ $ "L" ]                      is L          ( $ --> $ )

  [ $ "R" ]                      is R          ( $ --> $ )

  [ $ "BLALB" ]                  is A          ( $ --> $ )

  [ $ "ARBRA" ]                  is B          ( $ --> $ )

  $ "A"

  6 times expand

  turtle
  witheach
    [ switch
        [ char L case [ -1 6 turn ]
          char R case [  1 6 turn ]
          otherwise   [  4 1 walk ] ] ]


use SVG;

role Lindenmayer {
    has %.rules;
    method succ {
        self.comb.map( { %!rules{$^c} // $c } ).join but Lindenmayer(%!rules)
    }
}

my $arrow = 'X' but Lindenmayer( { X => 'YF+XF+Y', Y => 'XF-YF-X' } );

$arrow++ xx 7;

my $w = 800;
my $h = ($w * 3**.5 / 2).round(1);

my $scale = 6;
my @points = (400, 15);
my $dir = pi/3;

for $arrow.comb {
    state ($x, $y) = @points[0,1];
    state $d = $dir;
    when 'F' { @points.append: ($x += $scale * $d.cos).round(1), ($y += $scale * $d.sin).round(1) }
    when '+' { $d += $dir }
    when '-' { $d -= $dir }
    default { }
}

my $out = './sierpinski-arrowhead-curve-perl6.svg'.IO;

$out.spurt:  SVG.serialize(
    svg => [
        :width($w), :height($h),
        :rect[:width<100%>, :height<100%>, :fill<black>],
        :polyline[ :points(@points.join: ','), :fill<black>, :style<stroke:#FF4EA9> ],
    ],
);


/*REXX pgm computes and displays a Sierpinski Arrowhead Curve using the characters: \_/ */
parse arg order .                                /*obtain optional argument from the CL.*/
if order=='' | order==","  then order=   5       /*Not specified?  Then use the default.*/
say '  Sierpinski arrowhead curve of order'   order             /*display the   title.  */
say '═════════════════════════════════════════'                 /*   "     "  separator.*/
$= init()                                        /*initialize a bunch of variables.     */
if order//2  then do;  call turn +60;  call curve order, len, -60;  end    /*CURVE odd? */
             else                      call curve order, len, +60          /*CURVE even.*/

       do    row=Ly  to Hy;   a=                 /*show arrowhead graph 1 row at a time.*/
          do col=Lx  to Hx;   a= a || @.col.row  /*build a row of   "   " col  " "   "  */
          end   /*col*/;  say strip(a, 'T')      /*show  "  "   "   "     row  " "   "  */
       end      /*row*/
exit 0                                           /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
init:  @.=" "; #=0; len=512; x=len; y=x;Hx=x;Hy=y;Lx=x;Ly=y; return '@. # Hx Hy Lx Ly x y'
turn:  parse arg angle; #= (#+angle)//360;  if #<0  then #= #+360;  return  /*normalize.*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
curve: procedure expose ($);  parse arg order,len,angle  /*$:  list of exposed variables*/
       if order==0  then call draw len                   /*Is ORDER zero?  Then draw it.*/
                    else do;  call curve order-1, len/2, -angle;      call turn angle
                              call curve order-1, len/2, +angle;      call turn angle
                              call curve order-1, len/2, -angle
                         end
       return                                    /*The  CURVE  function is recursive.   */
/*──────────────────────────────────────────────────────────────────────────────────────*/
draw:  select                                    /*draw part of the curve using a char. */
           when #==  0  then do;    @.x.y= '_';      x= x + 1;                        end
           when #== 60  then do;    @.x.y= '/';      x= x + 1;        y= y - 1;       end
           when #==120  then do;    x= x - 1;        @.x.y= '\';      y= y - 1;       end
           when #==180  then do;    x= x - 1;        @.x.y= '_';                      end
           when #==240  then do;    x= x - 1;        y= y + 1;        @.x.y= '/';     end
           when #==300  then do;    y= y + 1;        @.x.y= '\';      x= x + 1;       end
           end   /*select*/                      /*curve character is based on direction*/
       Lx= min(Lx,x);  Hx= max(Hx,x);  Ly= min(Ly,y);  Hy= max(Hy,y)  /*min&max  of  x,y*/
       return                                    /*#:  heading in degrees of the curve. */


load_libraries :grammar
attr_reader :points

def setup
  sketch_title 'Sierpinski Arrowhead'
  sierpinski = SierpinskiArrowhead.new(Vec2D.new(width * 0.15, height * 0.7))
  production = sierpinski.generate 6 # 6 generations looks OK
  @points = sierpinski.translate_rules(production)
  no_loop
end

def draw
  background(0)
  render points
end

def render(points)
  no_fill
  stroke 200.0
  stroke_weight 3
  begin_shape
  points.each_slice(2) do |v0, v1|
    v0.to_vertex(renderer)
    v1.to_vertex(renderer)
  end
  end_shape
end

def renderer
  @renderer ||= GfxRender.new(g)
end

def settings
  size(800, 800)
end

# SierpinskiArrowhead class
class SierpinskiArrowhead
  include Processing::Proxy
  attr_reader :draw_length, :pos, :theta, :axiom, :grammar
  DELTA = PI / 3 # 60 degrees
  def initialize(pos)
    @axiom = 'XF' # Axiom
    rules = {
      'X' => 'YF+XF+Y',
      'Y' => 'XF-YF-X'
    }
    @grammar = Grammar.new(axiom, rules)
    @theta = 0
    @draw_length = 200
    @pos = pos
  end

  def generate(gen)
    @draw_length = draw_length * 0.6**gen
    grammar.generate gen
  end

  def forward(pos)
    pos + Vec2D.from_angle(theta) * draw_length
  end

  def translate_rules(prod)
    [].tap do |pts| # An array to store line vertices as Vec2D
      prod.scan(/./) do |ch|
        case ch
        when 'F'
          new_pos = forward(pos)
          pts << pos << new_pos
          @pos = new_pos
        when '+'
          @theta += DELTA
        when '-'
          @theta -= DELTA
        when 'X', 'Y'
        else
          puts("character #{ch} not in grammar")
        end
      end
    end
  end
end


// [dependencies]
// svg = "0.8.0"

const SQRT3_2: f64 = 0.86602540378444;

use svg::node::element::path::Data;

struct Cursor {
    x: f64,
    y: f64,
    angle: i32,
}

impl Cursor {
    fn new(x: f64, y: f64) -> Cursor {
        Cursor {
            x: x,
            y: y,
            angle: 0,
        }
    }
    fn turn(&mut self, angle: i32) {
        self.angle = (self.angle + angle) % 360;
    }
    fn draw_line(&mut self, data: Data, length: f64) -> Data {
        let theta = (self.angle as f64).to_radians();
        self.x += length * theta.cos();
        self.y += length * theta.sin();
        data.line_to((self.x, self.y))
    }
}

fn curve(mut data: Data, order: usize, length: f64, cursor: &mut Cursor, angle: i32) -> Data {
    if order == 0 {
        return cursor.draw_line(data, length);
    }
    data = curve(data, order - 1, length / 2.0, cursor, -angle);
    cursor.turn(angle);
    data = curve(data, order - 1, length / 2.0, cursor, angle);
    cursor.turn(angle);
    curve(data, order - 1, length / 2.0, cursor, -angle)
}

fn write_sierpinski_arrowhead(file: &str, size: usize, order: usize) -> std::io::Result<()> {
    use svg::node::element::Path;
    use svg::node::element::Rectangle;

    let margin = 20.0;
    let side = (size as f64) - 2.0 * margin;
    let y = 0.5 * (size as f64) + 0.5 * SQRT3_2 * side;
    let x = margin;
    let mut cursor = Cursor::new(x, y);
    if (order & 1) != 0 {
        cursor.turn(-60);
    }
    let mut data = Data::new().move_to((x, y));
    data = curve(data, order, side, &mut cursor, 60);
    let rect = Rectangle::new()
        .set("width", "100%")
        .set("height", "100%")
        .set("fill", "white");
    let mut document = svg::Document::new()
        .set("width", size)
        .set("height", size)
        .add(rect);
    let path = Path::new()
        .set("fill", "none")
        .set("stroke", "black")
        .set("stroke-width", "1")
        .set("d", data);
    document = document.add(path);
    svg::save(file, &document)
}

fn main() {
    write_sierpinski_arrowhead("sierpinski_arrowhead.svg", 600, 8).unwrap();
}


var rules = Hash(
    x => 'yF+xF+y',
    y => 'xF-yF-x',
)

var lsys = LSystem(
    width:  550,
    height: 500,

    xoff: -20,
    yoff: -30,

    len:   4,
    turn: -90,
    angle: 60,
    color: 'dark green',
)

lsys.execute('xF', 7, "sierpiński_arrowhead.png", rules)


import "graphics" for Canvas, Color, Point
import "dome" for Window

class Game {
    static init() {
        Window.title = "Sierpinski Arrowhead Curve"
        __width = 770
        __height = 770
        Window.resize(__width, __height)
        Canvas.resize(__width, __height)
        var order = 6
        __iy = (order&1 == 0) ? -1: 1  // apex will point upwards
        __theta = 0
        __cx = __width / 2
        __cy = __height
        __h  = __cx / 2
        __prev = Point.new(__cx-__width/2 +__h, (__height-__cy)*__iy + 2*__h)
        __col = Color.white
        arrowhead(order, __cx)
    }

    static update() {}

    static draw(alpha) {}

    static arrowhead(order, length) {
        // if order is even, we can just draw the curve
        if (order&1 == 0) {
            curve(order, length, 60)
        } else {
            turn(60)
            curve(order, length, -60)
        }
        drawLine(length) // needed to make base symmetric
    }

    static drawLine(length) {
        var curr = Point.new(__cx-__width/2 +__h, (__height-__cy)*__iy + 2*__h)
        Canvas.line(__prev.x, __prev.y, curr.x, curr.y, __col)
        var rads = __theta * Num.pi / 180
        __cx = __cx + length*(rads.cos)
        __cy = __cy + length*(rads.sin)
        __prev = curr
    }

    static turn(angle) { __theta = (__theta + angle) % 360 }

    static curve(order, length, angle) {
        if (order == 0) {
            drawLine(length)
        } else {
            curve(order-1, length/2, -angle)
            turn(angle)
            curve(order-1, length/2, angle)
            turn(angle)
            curve(order-1, length/2, -angle)
        }
    }
}


int  PosX, PosY;
real Dir;

proc Curve(Order, Length, Angle);
int  Order; real Length, Angle;
[if Order = 0 then
        [PosX:= PosX + fix(Length*Cos(Dir));
         PosY:= PosY - fix(Length*Sin(Dir));
         Line(PosX, PosY, $E \yellow\);
        ]
else    [Curve(Order-1, Length/2.0, -Angle);
         Dir:= Dir + Angle;
         Curve(Order-1, Length/2.0, +Angle);
         Dir:= Dir + Angle;
         Curve(Order-1, Length/2.0, -Angle);
        ];
];

def Order=5, Length=300.0, Pi=3.141592654, Sixty=Pi/3.0;
[SetVid($12);   \VGA graphics: 640x480x8
PosX:= 640/4;  PosY:= 3*480/4;
Move(PosX, PosY);
Dir:= 0.0;
if (Order&1) = 0 then
        Curve(Order, Length, +Sixty)
else    [Dir:= Dir + Sixty;
        Curve(Order, Length, -Sixty);
        ];
]


order:=7;
sierpinskiArrowheadCurve(order) : turtle(_,order);

fcn sierpinskiArrowheadCurve(n){   // Lindenmayer system --> Data of As & Bs
   var [const] A="BF+AF+B", B="AF-BF-A";  // Production rules
   var [const] Axiom="AF";
   buf1,buf2 := Data(Void,Axiom).howza(3), Data().howza(3);  // characters
   do(n){
      buf1.pump(buf2.clear(),fcn(c){ if(c=="A") A else if(c=="B") B else c });
      t:=buf1; buf1=buf2; buf2=t;	// swap buffers
   }
   buf1		// n=7 --> 6,560 characters
}

fcn turtle(curve,order){	// Turtle with that can turn +-60*
   const D=10.0, a60=60;
   dir:=order.isOdd and a60 or 0;	   // start direction depends on order
   img,color := PPM(1300,1200), 0x00ff00;  // green on black
   x,y := 10, 10;
   foreach c in (curve){  // A & B are no-op during drawing
      switch(c){
	 case("F"){   // draw forward
	    a,b := D.toRectangular(dir.toFloat().toRad());
	    img.line(x,y, (x+=a.round()),(y+=b.round()), color)
	 }
	 case("+"){ dir=(dir - a60)%360; } // turn left  60*
	 case("-"){ dir=(dir + a60)%360; } // turn right 60*
      }
   }
   img.writeJPGFile("sierpinskiArrowheadCurve.zkl.jpg");
}


T Item
   String name, date, category

   F (name, date, category)
      .name = name
      .date = date
      .category = category

   F String()
      R .name‘, ’(.date)‘, ’(.category)

V db_filename = ‘simdb.csv’

F load()
   [Item] db
   L(line) File(:db_filename).read().rtrim("\n").split("\n")
      V item = line.split(‘, ’)
      db.append(Item(item[0], item[1], item[2]))
   R db

F store(item)
   File(:db_filename, ‘a’).write(String(item)"\n")

F printUsage()
   print(|‘
           Usage:
             simdb cmd [categoryName]
             add     add item, followed by optional category
             latest  print last added item(s), followed by optional category
             all     print all
             For instance: add "some item name" "some category name’)

F addItem(args)
   I args.len < 2
      printUsage()
      R

   V date = Time().strftime(‘%Y-%m-%d %H:%M:%S’)
   V cat = I args.len == 3 {args[2]} E ‘none’
   store(Item(args[1], date, cat))

F printLatest(a)
   V db = load()
   I db.empty
      print(‘No entries in database.’)
      R

   I a.len == 2
      L(item) reversed(db)
         I item.category == a[1]
            print(item)
            L.break
      L.was_no_break
         print(‘There are no items for category '’a[1]‘'’)
   E
      print(db.last)

F printAll()
   V db = load()
   I db.empty
      print(‘No entries in database.’)
      R

   L(item) db
      print(item)

:start:
I :argv.len C 2..4
   S :argv[1].lowercase()
      ‘add’
         addItem(:argv[1..])
      ‘latest’
         printLatest(:argv[1..])
      ‘all’
         printAll()
      E
         printUsage()
E
   printUsage()


  whl
' ( arg$:?command
  & ( get'db
    | (db=1)&lst$(db,db,NEW)
    )
  &   !command
    : (   add
        & :?name:?tag:?date
        &   whl
          ' ( arg$:?argmnt
            & arg$:?value
            &   (!argmnt.!value)
              : ( (title|name.?name)
                | (category|tag.?tag)
                | (date.?date)
                )
            )
        & (   !name:~
            & !tag:~
            & !date:~
            & (   !db:?*!tag^(?+(!date.!name)+?)*?
                & out$"This record already exists"
              |   !tag^(!date.!name)*!db:?db
                & lst$(db,db,NEW)
              )
          | out$"invalid data"
          )
      |   latest
        & :?date
        & nothing found:?latest
        & (   !db
            :   ?
              *   ?tag
                ^ ( ?
                  + ( (>!date:?date.?name)
                    & (!name,!tag,!date):?latest
                    & ~
                    )
                  + ?
                  )
              * ?
          | out$!latest
          )
      |   latest/category
        & :?date:?latests:?latest
        & (   !db
            :   ?
              *   ( ?tag
                  & !latests !latest:?latests
                  & :?latest:?date
                  )
                ^ ( ?
                  + ( (>!date:?date.?name)
                    & (!name,!tag,!date):?latest
                    & ~
                    )
                  + ?
                  )
              * ?
          | !latests !latest:?latests&out$!latests
          )
      |   sorted
        & 0:?sorted
        & (   !db
            :   ?
              *   ?tag
                ^ ( ?
                  + ( (?date.?name)
                    & (!date.!name,!tag,!date)+!sorted:?sorted
                    & ~
                    )
                  + ?
                  )
              * ?
          |   whl
            ' (!sorted:(?.?row)+?sorted&out$!row)
          )
      )
  );


"Soon Rising","Dee","Lesace","10-12-2000","New Hat Press"
"Brave Chicken","Tang","Owe","04-01-2008","Nowhere Press"
"Aardvark Point","Dee","Lesace","5-24-2001","New Hat Press"
"Bat Whisperer, The","Tang","Owe","01-03-2004","Nowhere Press"
"Treasure Beach","Argus","Jemky","09-22-1999","Lancast"


using System;
using System.IO;

namespace Simple_database
{
	class Program
	{
		public static void Main(string[] args)
		{
			//
			// For appropriate use of this program
			// use standard Windows Command Processor or cmd.exe
			//
			// Create cmd.bat file at the same folder with executive version
			// of program Simple_database.exe, so when started, the correct
			// file path will be automatically set to cmd console.
			//
			// Start notepad, write only cmd.exe and save file as cmd.bat
			//
			// To start cmd just double click at cmd.bat file.
			//
			//
			//
			// Console application command line start command
			//
			// application name.exe [argument] [argument parameters]
			//
			//
			// Command line argument followed by parameters
			//
			// [a] - Add new entry
			//
			// ["data1"]["data2"]["data3"]...["data n"]
			//
			// ["data1"] - Data category !
			// ["data2"] - Data
			// ["data3"] - Data
			//
			//
			// NOTICE !
			//
			// First parameter is taken for data category.
			//
			//
			//
			// Command line argument with no parameters
			//
			// [p1] - Print the latest entry
			//
			// [p2] - Print the latest entry for each category
			//
			// [p3] - Print all entries sorted by a date
			//
			//
			//

			//
			// Command line example
			//
			// Small_database.exe [a] ["home"] ["+398125465458"] ["My tel number"]
			//
			// Small_database.exe [a] ["office"] ["+398222789000"] ["Boss"]
			//
			// Small_database.exe [a] [cd] ["Action movie"] ["Movie title"]
			// Small_database.exe [a] [cd] ["SF movie"] ["Movie title"]
			// Small_database.exe [a] [dvd] ["Action movie"] ["Movie title"]
			//
			//
			// NOTICE !
			//
			// Brackets and space between arguments and parameters are necessary.
			// Quotes must be used when parameters have more than one word.
			//
			// If not used as shown in examples, program will show error message.
			//
			//

			//
			// Check command line for arguments
			//
			//
			if(args.Length==0)
			{
				Console.WriteLine();
				Console.WriteLine(" Missing Argument Error. ");
				Console.WriteLine();
			}
			else
			{
				switch (args[0])
				{
						case "[a]" : Add_New_Entry(args);
						break;

						case "[p1]": Print_Document("Print the latest entry.txt");
						break;

						case "[p2]": Print_Document("Print the latest entry for each category.txt");
						break;

						case "[p3]": Print_Document("Print all entries sorted by a date.txt");
						break;

						default :
						{
							Console.WriteLine();
							Console.WriteLine(" Incorrect Argument Error. ");
							Console.WriteLine();
						}
						break;
				}
			}
		}

		static void Add_New_Entry(string [] args)
		{

			//
			// Check parameters
			//
			//
			// Minimum one parameter, category
			//
			if(args.Length==1)
			{
				Console.WriteLine();
				Console.WriteLine(" Missing Parameters Error..... ");
				Console.WriteLine();
			}
			else
			{
				bool parameters_ok = true;
				foreach (string a in args)
				{
					if(!a.StartsWith("[") || !a.EndsWith("]"))
					{
						parameters_ok = !parameters_ok;
						break;
					}
				}
				//
				// Add new entry to Data base document
				//
				if(parameters_ok)
				{
					//
					//
					//
					Console.WriteLine();
					Console.WriteLine(" Parameters are ok..... ");
					Console.WriteLine();
					Console.WriteLine(" Writing new entry to database..... ");
					//
					// Create new Data base entry
					//
					args[0] = string.Empty;
					string line = string.Empty;
					foreach (string a in args)
					{
						line+=a;
					}
					line+="[" + DateTime.Now.ToString() + "]";
					args[0] = "[" + DateTime.Now.ToString() + "]";
					//
					// Write entry to Data base
					//
					StreamWriter w = new StreamWriter("Data base.txt",true);
					w.WriteLine(line);
					//
					// Close and dispose stream writer
					//
					w.Close();
					w.Dispose();
					//
					//
					//
					Console.WriteLine();
					Console.WriteLine(" New entry is written to database. ");

					Create_Print_Documents(args);

					//
					//
					//
					Console.WriteLine();
					Console.WriteLine(" Add new entry command executed. ");
					//
					//
					//
				}
				else
				{
					Console.WriteLine();
					Console.WriteLine(" ! Parameters are not ok ! ");
					Console.WriteLine();
					Console.WriteLine(" Add new entry command is not executed. ");
					Console.WriteLine();
				}
			}
		}

		static void Create_Print_Documents(string [] args)
		{
			//
			//
			//
			Console.WriteLine();
			Console.WriteLine(" Creating new print documents. ");
			//
			// Create "Print all entries sorted by a date.txt"
			//
			File.Copy("Data base.txt","Print all entries sorted by a date.txt",true);
			//
			//
			//
			Console.WriteLine();
			Console.WriteLine(" Print all entries sorted by a date.txt created. ");
			//
			// Create "Print the latest entry.txt"
			//
			//
			// Create new entry
			//
			string line = string.Empty;
			foreach (string a in args)
			{
				line+=a;
			}
			//
			StreamWriter w = new StreamWriter("Print the latest entry.txt");
			//
			w.WriteLine(line);
			//
			w.Close();
			w.Dispose();
			//
			//
			//
			Console.WriteLine();
			Console.WriteLine(" Print the latest entry.txt created. ");
			//
			// Create "Print the latest entry for each category.txt"
			//
			string latest_entry = string.Empty;
			foreach (string a in args)
			{
				latest_entry+=a;
			}

			if(!File.Exists("Print the latest entry for each category.txt"))
			{
				File.WriteAllText("Print the latest entry for each category.txt",latest_entry);
			}
			else
			{
				StreamReader r = new StreamReader("Print the latest entry for each category.txt");
				//
				w = new StreamWriter("Print the latest entry for each category 1.txt",true);
				//
				line = string.Empty;
				//
				while(!r.EndOfStream)
				{
					line = r.ReadLine();
					if(line.Contains(args[1].ToString()))
					{
						w.WriteLine(latest_entry);
						latest_entry = "ok";
					}
					else
					{
						w.WriteLine(line);
					}
				}
				// add new category
				if(latest_entry != "ok")
					w.WriteLine(latest_entry);
				//
				w.Close();
				w.Dispose();
				//
				r.Close();
				r.Dispose();
				//
				File.Copy("Print the latest entry for each category 1.txt",
				          "Print the latest entry for each category.txt",true);
				//
				File.Delete("Print the latest entry for each category 1.txt");
				//
				//
				//
				Console.WriteLine();
				Console.WriteLine(" Print the latest entry for each category.txt created. ");
				Console.WriteLine();
			}
		}

		static void Print_Document(string file_name)
		{
			//
			// Print document
			//
			Console.WriteLine();
			Console.WriteLine(file_name.Replace(".txt","")+ " : ");
			Console.WriteLine();
			//
			StreamReader r = new StreamReader(file_name);
			//
			string line = string.Empty;
			//
			line = r.ReadToEnd();
			//
			Console.WriteLine(line);
			//
			r.Close();
			r.Dispose();
			//
			//
			//
		}
	}
}


       IDENTIFICATION DIVISION.
       PROGRAM-ID. simple-database.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT OPTIONAL database-file ASSIGN Database-Path
               ORGANIZATION INDEXED
               ACCESS SEQUENTIAL
               RECORD KEY data-title
               ALTERNATE RECORD KEY data-tag
                   WITH DUPLICATES
               ALTERNATE RECORD KEY date-added
                   WITH DUPLICATES
               FILE STATUS file-status
               .
       DATA DIVISION.
       FILE SECTION.
       FD  database-file.
       01  database-record.
           *> Date is in YYYYMMDD format.
           03  date-added          PIC 9(8).
           03  data-tag            PIC X(20).
           03  data-title          PIC X(50).
           03  data-contents       PIC X(200).
           *> Adding extra space is considered good style so the record
           *> can be expanded in the future.
           03  FILLER              PIC X(50).

       WORKING-STORAGE SECTION.
       78  Database-Path           VALUE "database.dat".

       01  file-status             PIC XX.
           88  file-ok             VALUE "00".
           88  duplicate-key       VALUE "22".
           88  key-not-found       VALUE "23".

       01  num-args                PIC 99.

       01  action                  PIC XX.
           88  create-entry        VALUE "-c".
           88  remove-entry        VALUE "-r".
           88  find-entry          VALUE "-f".
           88  print-latest        VALUE "-l".
           88  print-database      VALUES "-a", "-d", "-t".
           *> Printed by title.
           88  print-by-title      VALUE "-a".
           88  print-by-date       VALUE "-d".
           88  print-by-tag        VALUE "-t".
           88  print-help          VALUES "-h", SPACES.

       01  read-direction-flag     PIC X VALUE SPACE.
           88  read-backwards      VALUE "B".

       01  edited-date             PIC 9(4)/99/99.
       PROCEDURE DIVISION.
       DECLARATIVES.
       database-file-error SECTION.
           USE AFTER ERROR ON database-file

           DISPLAY "An error has occurred while using " Database-Path
               ". Error no. " file-status
           DISPLAY "The program will terminate."

           CLOSE database-file

           GOBACK
           .
       END DECLARATIVES.

       main-line.
           DISPLAY 1 UPON ARGUMENT-NUMBER
           ACCEPT action FROM ARGUMENT-VALUE

           ACCEPT num-args FROM ARGUMENT-NUMBER

           EVALUATE TRUE
               WHEN create-entry
                   IF num-args >= 4
                       PERFORM write-entry
                   ELSE
                       DISPLAY "-a requires arguments to enter in the "
                           "database. See help (-h) for details."
                   END-IF

               WHEN remove-entry
                   IF num-args >= 2
                       PERFORM delete-entry
                   ELSE
                       DISPLAY "-r requires the title of the entry to "
                           "delete."
                   END-IF

               WHEN find-entry
                   IF num-args >= 2
                       PERFORM display-specified-entry
                   ELSE
                       DISPLAY "-f requires the title of the entry to "
                           "find."
                   END-IF

               WHEN print-latest
                   PERFORM show-latest

               WHEN print-database
                   PERFORM show-database

               WHEN print-help
                   PERFORM show-general-help

               WHEN OTHER
                   DISPLAY action " is not a valid option."
           END-EVALUATE

           GOBACK
           .
       write-entry.
           OPEN EXTEND database-file

           DISPLAY 2 UPON ARGUMENT-NUMBER
           ACCEPT data-tag FROM ARGUMENT-VALUE
           DISPLAY 3 UPON ARGUMENT-NUMBER
           ACCEPT data-title FROM ARGUMENT-VALUE
           IF data-title = SPACES
               DISPLAY "The title cannot be blank."
               PERFORM close-and-terminate
           END-IF

           DISPLAY 4 UPON ARGUMENT-NUMBER
           ACCEPT data-contents FROM ARGUMENT-VALUE

           ACCEPT date-added FROM DATE YYYYMMDD

           WRITE database-record
               INVALID KEY
                   IF duplicate-key
                       DISPLAY "An entry in the database already has "
                           "that title. Please choose a different "
                           "title or remove the entry."
                   ELSE
                       PERFORM database-file-error
                   END-IF
           END-WRITE

           PERFORM close-database
           .
       delete-entry.
           PERFORM get-title-arg
           OPEN I-O database-file
           PERFORM read-title

           DELETE database-file

           PERFORM close-database
           .
       display-specified-entry.
           PERFORM get-title-arg
           OPEN INPUT database-file
           PERFORM read-title

           PERFORM show-record

           PERFORM close-database
           .
       get-title-arg.
           DISPLAY 2 UPON ARGUMENT-NUMBER
           ACCEPT data-title FROM ARGUMENT-VALUE
           .
       read-title.
           START database-file KEY IS = data-title
               INVALID KEY
                   IF key-not-found
                       DISPLAY "An entry with that title was not found."
                       PERFORM close-and-terminate
                   ELSE
                       PERFORM database-file-error
                   END-IF
           END-START

           READ database-file
           .
       close-and-terminate.
            PERFORM close-database
            GOBACK
            .
       show-latest.
           OPEN INPUT database-file

           PERFORM start-at-last-date
           READ database-file
           PERFORM show-record

           PERFORM close-database
           .
       show-database.
           OPEN INPUT database-file

           EVALUATE TRUE
               WHEN print-by-title
                   *> Primary key is the title.
                   CONTINUE
               WHEN print-by-tag
                   MOVE LOW-VALUES TO data-tag
                   START database-file KEY IS > data-tag
               WHEN print-by-date
                   PERFORM start-at-last-date
                   SET read-backwards TO TRUE
           END-EVALUATE

           PERFORM FOREVER
               *> The problem with statements instead of functions...
               IF NOT read-backwards
                   READ database-file NEXT
                       AT END
                           EXIT PERFORM
                   END-READ
               ELSE
                   READ database-file PREVIOUS
                       AT END
                           EXIT PERFORM
                   END-READ
               END-IF

               PERFORM show-record
               DISPLAY SPACE
           END-PERFORM

           PERFORM close-database
           .
       start-at-last-date.
           MOVE HIGH-VALUES TO date-added
           START database-file KEY IS < date-added
           .
       close-database.
           CLOSE database-file
           .
       show-record.
           MOVE date-added TO edited-date
           DISPLAY "Date added: " edited-date " Tag: " data-tag
           DISPLAY "Title: " data-title
           DISPLAY "Contents:"
           DISPLAY "  " FUNCTION TRIM(data-contents)
           .
       show-general-help.
           DISPLAY "Help: Possible options are:"
           DISPLAY "  -a - Show all the entries (sorted by title)."
           DISPLAY "  -c - Create a new entry in the database. -c needs"
               " further arguments in this format:"
           DISPLAY '    "tag" "title" "content"'
           DISPLAY "    Max argument sizes (in characters): tag - 20, "
               "title - 50, content - 200"
           DISPLAY "    The title must be unique and not be blank."
           DISPLAY "  -d - Show all the entries sorted by date added."
           DISPLAY "  -f - Finds and displays entry with the title "
               "provided. The title should be specified as shown for "
               "-c."
           DISPLAY "  -h - Show this help menu."
           DISPLAY "  -l - Show the latest entry."
           DISPLAY "  -r - Remove the entry with the title provided. "
               "The title should be specified as shown for -c."
           DISPLAY "  -t - Show all the entries sorted by tag."
           .


(defvar *db* nil)

(defvar *db-cat* (make-hash-table :test 'equal))

(defvar *db-file* "db.txt")

(defstruct item
  "this is the unit of data stored/displayed in *db*"
  (title " ")
  (category "default")
  (date (progn (get-universal-time))))

(defun set-category(new-item)
  (setf (gethash (item-category new-item) *db-cat*) 't))

(defun find-item-in-db (&optional category)
  (if (null category)
      (car *db*)
    (find category *db* :key #'item-category :test #'string=)))

(defun scan-category ()
  "scan categories from an existing database -- after reading it from disk"
  (dolist (itm *db*) (set-category itm)))

(defun pr-univ-time (utime)
  (multiple-value-bind
   (second minute hour date month year day-of-week dst-p tz)
   (decode-universal-time utime)
   (declare (ignore day-of-week dst-p tz))
   (format nil "~4,'0d-~2,'0d-~2,'0d ~2,'0d:~2,'0d:~2,'0d" year month date hour minute second)))

(defun pr (&optional (item (find-item-in-db)) (stream t))
  "print an item"
  (when item
    (format stream "~a: (~a) (~a)~%"
	    (item-title item)
	    (item-category item)
	    (pr-univ-time (item-date item)))))

(defun pr-per-category ()
  "print the latest item from each category"
  (loop for k being the hash-keys in *db-cat*
	do (pr (find-item-in-db k))))

(defun pr-all ()
  "print all the items, *db* is sorted by time."
  (dolist (itm *db*)  (pr itm)))

(defun pr-all-categories (&optional (stream t))
  (loop for k being the hash-keys in *db-cat*
       do (format stream "(~a) " k)))

(defun insert-item (item)
  "insert item into database in a time sorted list. okay for a small list, as per spec."
  (let ((first-item (car *db*)) (new-itm item))
    (set-category new-itm)
    (push new-itm *db*)
    (when (and first-item (>= (item-date new-itm) (item-date first-item)))
      (setf *db* (sort *db* #'> :key #'item-date)))
    *db*))

(defun read-db-from-file (&optional (file *db-file*))
  (with-open-file (in file :if-does-not-exist nil)
		  (when in
		    (with-standard-io-syntax (setf *db* (read in)))
		    (scan-category))))

(defun save-db-to-file (&optional (file *db-file*))
  (with-open-file (out file :direction :output :if-exists :supersede)
		  (with-standard-io-syntax
		   (print *db* out))))

(defun del-db ()
  (setf *db* nil)
  (save-db-to-file))

(defun del-item (itm)
  (read-db-from-file)
  (setf *db* (remove itm *db* :key #'item-title :test #'string=))
  (save-db-to-file))

(defun add-item-to-db (args)
  (read-db-from-file)
  (insert-item (make-item :title (first args) :category (second args)))
  (save-db-to-file))

(defun help-menu ()
  (format t "clisp db.lisp ~{~15T~a~^~% ~}"
	  '("delete <item-name> -------------------  delete an item"
	    "delete-all ---------------------------  delete the database"
            "insert <item-name> <item-category> ---  insert an item with its category"
	    "show ---------------------------------  shows the latest inserted item"
	    "show-categories ----------------------  show all categories"
	    "show-all -----------------------------  show all items"
	    "show-per-category --------------------  show the latest item per category")))

(defun db-cmd-run (args)
  (cond ((and (> (length args) 1) (equal (first args) "delete"))
	 (del-item (second args)))
	((equal (first args) "delete-all") (del-db))
	((and (> (length args) 2) (equal (first args) "insert"))
	 (add-item-to-db (rest args)))
	((equal (first args) "show") (read-db-from-file) (pr))
	((equal (first args) "show-categories") (read-db-from-file) (pr-all-categories))
        ((equal (first args) "show-all") (read-db-from-file) (pr-all))
        ((equal (first args) "show-per-category") (read-db-from-file) (pr-per-category))
        (t (help-menu))))

;; modified https://rosettacode.org/wiki/Command-line_arguments#Common_Lisp
(defun db-argv ()
  (or
   #+clisp ext:*args*
   #+sbcl (cdr sb-ext:*posix-argv*)
   #+allegro (cdr (sys:command-line-arguments))
   #+lispworks (cdr sys:*line-arguments-list*)
   nil))

(db-cmd-run  (db-argv))


import std.stdio, std.algorithm, std.string, std.conv, std.array,
       std.file, std.csv, std.datetime;

private {
    immutable filename = "simdb.csv";

    struct Item {
        string name, date, category;
    }

    void addItem(in string[] item) {
        if (item.length < 3)
            return printUsage();
        auto db = load();
        const date = (cast(DateTime)Clock.currTime).toISOExtString;
        const cat = (item.length == 4) ? item[3] : "none";
        db ~= Item(item[2], date, cat);
        store(db);
    }

    void printLatest(in string[] a) {
       auto db = load();
       if (db.empty)
           return writeln("No entries in database.");
       db.sort!q{ a.date > b.date };
       if (a.length == 3) {
           foreach (item; db)
               if (item.category == a[2])
                   writefln("%s, %s, %s", item.tupleof);
       } else {
           writefln("%s, %s, %s", db[0].tupleof);
       }
    }

    void printAll() {
        auto db = load();
        if (db.empty)
            return writeln("No entries in database.");
        db.sort!q{ a.date < b.date };
        foreach (item; db)
            writefln("%s, %s, %s", item.tupleof);
    }

    Item[] load() {
        Item[] db;
        if (filename.exists && filename.isFile) {
            try {
                const text = filename.readText;
                if (!text.empty)
                    db = csvReader!Item(text).array;
            } catch (CSVException e) {
                writeln(e.msg);
            }
        }
        return db;
    }

    void store(in Item[] db) {
        auto f = File(filename, "w+");
        foreach (item; db)
            f.writefln("%s,%s,%s", item.tupleof);
    }

    void printUsage() {
        writeln(
`Usage:
  simdb cmd [categoryName]

  add     add item, followed by optional category
  latest  print last added item(s), followed by optional category
  all     print all

  For instance: add "some item name" "some category name"`);
    }
}

void main(in string[] args) {
    if (args.length < 2 || args.length > 4)
        return printUsage();

    switch (args[1].toLower) {
        case "add":    addItem(args);     break;
        case "latest": printLatest(args); break;
        case "all":    printAll();        break;
        default:       printUsage();      break;
    }
}


#! /usr/bin/env escript

-compile({no_auto_import,[date/0]}).

main( ["add", Tag | Descriptions] ) -> add( date(), Tag, Descriptions );
main( ["add_date", Date, Tag | Descriptions] ) -> add( date_internal(string:tokens(Date, "-")), Tag, Descriptions );
main( ["print_latest"] ) -> print_latest( contents() );
main( ["print_latest_for_each"] ) -> print_latest_for_each( contents() );
main( ["print_all_date", Date] ) -> print_all_date( date_internal(string:tokens(Date, "-")), contents() );
main( _Error ) -> usage().

add( Date, Tag, Descriptions ) ->
	Contents = contents(),
	file:write_file( file(), io_lib:format("simple_database_v1.~n~p.~n", [[{Date, Tag, Descriptions} | Contents]]) ).

date() ->
	{{Date, _Time}} = calendar:local_time(),
	Date.

date_external( {Year, Month, Day} ) -> string:join( [erlang:integer_to_list(Year), erlang:integer_to_list(Month), erlang:integer_to_list(Day)], "-" );
date_external( _Error ) -> usage().

date_internal( [Year, Month, Day] ) -> {erlang:list_to_integer(Year), erlang:list_to_integer(Month), erlang:list_to_integer(Day)};
date_internal( _Error ) -> usage().

file() -> "simple_database_contents".

contents() -> contents( file:consult(file()) ).

contents( {ok, [simple_database_v1, Contents]} ) -> Contents;
contents( {error, Error} ) when is_atom(Error) -> [];
contents( {error, _Error} ) ->
	io:fwrite( "Error: ~p corrupt. Starting from scratch~n", [file()] ),
	[].

print_all_date( _Date, [] ) -> ok;
print_all_date( Date, Contents ) -> [print_latest([{D, Tag, Descriptions}]) || {D, Tag, Descriptions} <- Contents, D =:= Date].

print_latest( [] ) -> ok;
print_latest( [{Date, Tag, Descriptions} | _T] ) -> io:fwrite( "~s~n", [string:join( [date_external(Date), Tag | Descriptions], " ")] ).

print_latest_for_each( [] ) -> ok;
print_latest_for_each( Contents ) ->
	Tags = lists:usort( [Tag || {_Date, Tag, _Descriptions} <- Contents] ),
	[print_latest([lists:keyfind(X, 2, Contents)]) ||  X <- Tags].

usage() ->
	io:fwrite( "Usage: ~p [add | add_date <date>]  tag description ...~n", [escript:script_name()] ),
	io:fwrite( "Or: ~p [print_latest | print_latest_for_each | print_all_date <date>]~n", [escript:script_name()] ),
	io:fwrite( "Date format is YYYY-MM-DD~n" ),
	io:fwrite( "Data stored in ~p~n", [file()] ),
	init:stop().


+: betty 1974.03.03;coworker;reading;

+: geea 1980.01.01;friend;sketch writer;

+: tom 1991.03.07;family member;reading;

+: alice 1987.09.01;coworker;classical music;

+: gammaQ3.14 3045.09.09;friend;watch movies, star walking;


package main

import (
    "encoding/json"
    "fmt"
    "io"
    "os"
    "sort"
    "strings"
    "time"
    "unicode"
)

// Database record format.  Time stamp and name are required.
// Tags and notes are optional.
type Item struct {
    Stamp time.Time
    Name  string
    Tags  []string `json:",omitempty"`
    Notes string   `json:",omitempty"`
}

// Item implements stringer interface
func (i *Item) String() string {
    s := i.Stamp.Format(time.ANSIC) + "\n  Name:  " + i.Name
    if len(i.Tags) > 0 {
        s = fmt.Sprintf("%s\n  Tags:  %v", s, i.Tags)
    }
    if i.Notes > "" {
        s += "\n  Notes: " + i.Notes
    }
    return s
}

// collection of Items
type db []*Item

// db implements sort.Interface
func (d db) Len() int           { return len(d) }
func (d db) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
func (d db) Less(i, j int) bool { return d[i].Stamp.Before(d[j].Stamp) }

// hard coded database file name
const fn = "sdb.json"

func main() {
    if len(os.Args) == 1 {
        latest()
        return
    }
    switch os.Args[1] {
    case "add":
        add()
    case "latest":
        latest()
    case "tags":
        tags()
    case "all":
        all()
    case "help":
        help()
    default:
        usage("unrecognized command")
    }
}

func usage(err string) {
    if err > "" {
        fmt.Println(err)
    }
    fmt.Println(`usage:  sdb [command] [data]
    where command is one of add, latest, tags, all, or help.`)
}

func help() {
    usage("")
    fmt.Println(`
Commands must be in lower case.
If no command is specified, the default command is latest.

Latest prints the latest item.
All prints all items in chronological order.
Tags prints the lastest item for each tag.
Help prints this message.

Add adds data as a new record.  The format is,

  name [tags] [notes]

Name is the name of the item and is required for the add command.

Tags are optional.  A tag is a single word.
A single tag can be specified without enclosing brackets.
Multiple tags can be specified by enclosing them in square brackets.

Text remaining after tags is taken as notes.  Notes do not have to be
enclosed in quotes or brackets.  The brackets above are only showing
that notes are optional.

Quotes may be useful however--as recognized by your operating system shell
or command line--to allow entry of arbitrary text.  In particular, quotes
or escape characters may be needed to prevent the shell from trying to
interpret brackets or other special characters.

Examples:
sdb add Bookends                        // no tags, no notes
sdb add Bookends rock my favorite       // tag: rock, notes: my favorite
sdb add Bookends [rock folk]            // two tags
sdb add Bookends [] "Simon & Garfunkel" // notes, no tags
sdb add "Simon&Garfunkel [artist]"      // name: Simon&Garfunkel, tag: artist

As shown in the last example, if you use features of your shell to pass
all data as a single string, the item name and tags will still be identified
by separating whitespace.

The database is stored in JSON format in the file "sdb.json"
`)
}

// load data for read only purposes.
func load() (db, bool) {
    d, f, ok := open()
    if ok {
        f.Close()
        if len(d) == 0 {
            fmt.Println("no items")
            ok = false
        }
    }
    return d, ok
}

// open database, leave open
func open() (d db, f *os.File, ok bool) {
    var err error
    f, err = os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        fmt.Println("cant open??")
        fmt.Println(err)
        return
    }
    jd := json.NewDecoder(f)
    err = jd.Decode(&d)
    // EOF just means file was empty.  That's okay with us.
    if err != nil && err != io.EOF {
        fmt.Println(err)
        f.Close()
        return
    }
    ok = true
    return
}

// handle latest command
func latest() {
    d, ok := load()
    if !ok {
        return
    }
    sort.Sort(d)
    fmt.Println(d[len(d)-1])
}

// handle all command
func all() {
    d, ok := load()
    if !ok {
        return
    }
    sort.Sort(d)
    for _, i := range d {
        fmt.Println("-----------------------------------")
        fmt.Println(i)
    }
    fmt.Println("-----------------------------------")
}

// handle tags command
func tags() {
    d, ok := load()
    if !ok {
        return
    }
    // we have to traverse the entire list to collect tags so there
    // is no point in sorting at this point.
    // collect set of unique tags associated with latest item for each
    latest := make(map[string]*Item)
    for _, item := range d {
        for _, tag := range item.Tags {
            li, ok := latest[tag]
            if !ok || item.Stamp.After(li.Stamp) {
                latest[tag] = item
            }
        }
    }
    // invert to set of unique items, associated with subset of tags
    // for which the item is the latest.
    type itemTags struct {
        item *Item
        tags []string
    }
    inv := make(map[*Item][]string)
    for tag, item := range latest {
        inv[item] = append(inv[item], tag)
    }
    // now we sort just the items we will output
    li := make(db, len(inv))
    i := 0
    for item := range inv {
        li[i] = item
        i++
    }
    sort.Sort(li)
    // finally ready to print
    for _, item := range li {
        tags := inv[item]
        fmt.Println("-----------------------------------")
        fmt.Println("Latest item with tags", tags)
        fmt.Println(item)
    }
    fmt.Println("-----------------------------------")
}

// handle add command
func add() {
    if len(os.Args) < 3 {
        usage("add command requires data")
        return
    } else if len(os.Args) == 3 {
        add1()
    } else {
        add4()
    }
}

// add command with one data string.  look for ws as separators.
func add1() {
    data := strings.TrimLeftFunc(os.Args[2], unicode.IsSpace)
    if data == "" {
        // data must have at least some non-whitespace
        usage("invalid name")
        return
    }
    sep := strings.IndexFunc(data, unicode.IsSpace)
    if sep < 0 {
        // data consists only of a name
        addItem(data, nil, "")
        return
    }
    name := data[:sep]
    data = strings.TrimLeftFunc(data[sep:], unicode.IsSpace)
    if data == "" {
        // nevermind trailing ws, it's still only a name
        addItem(name, nil, "")
        return
    }
    if data[0] == '[' {
        sep = strings.Index(data, "]")
        if sep < 0 {
            // close bracketed list for the user.  no notes.
            addItem(name, strings.Fields(data[1:]), "")
        } else {
            // brackets make things easy
            addItem(name, strings.Fields(data[1:sep]),
                strings.TrimLeftFunc(data[sep+1:], unicode.IsSpace))
        }
        return
    }
    sep = strings.IndexFunc(data, unicode.IsSpace)
    if sep < 0 {
        // remaining word is a tag
        addItem(name, []string{data}, "")
    } else {
        // there's a tag and some data
        addItem(name, []string{data[:sep]},
            strings.TrimLeftFunc(data[sep+1:], unicode.IsSpace))
    }
}

// add command with multiple strings remaining on command line
func add4() {
    name := os.Args[2]
    tag1 := os.Args[3]
    if tag1[0] != '[' {
        // no brackets makes things easy
        addItem(name, []string{tag1}, strings.Join(os.Args[4:], " "))
        return
    }
    if tag1[len(tag1)-1] == ']' {
        // tags all in one os.Arg is easy too
        addItem(name, strings.Fields(tag1[1:len(tag1)-1]),
            strings.Join(os.Args[4:], " "))
        return
    }
    // start a list for tags
    var tags []string
    if tag1 > "[" {
        tags = []string{tag1[1:]}
    }
    for x, tag := range os.Args[4:] {
        if tag[len(tag)-1] != ']' {
            tags = append(tags, tag)
        } else {
            // found end of tag list
            if tag > "]" {
                tags = append(tags, tag[:len(tag)-1])
            }
            addItem(name, tags, strings.Join(os.Args[5+x:], " "))
            return
        }
    }
    // close bracketed list for the user.  no notes.
    addItem(name, tags, "")
}

// complete the add command
func addItem(name string, tags []string, notes string) {
    db, f, ok := open()
    if !ok {
        return
    }
    defer f.Close()
    // add the item and format JSON
    db = append(db, &Item{time.Now(), name, tags, notes})
    sort.Sort(db)
    js, err := json.MarshalIndent(db, "", "  ")
    if err != nil {
        fmt.Println(err)
        return
    }
    // time to overwrite the file
    if _, err = f.Seek(0, 0); err != nil {
        fmt.Println(err)
        return
    }
    f.Truncate(0)
    if _, err = f.Write(js); err != nil {
        fmt.Println(err)
    }
}


import Control.Monad.State
import Data.List (sortBy, nub)
import System.Environment (getArgs, getProgName)
import System.Directory (doesFileExist)
import System.IO (openFile, hGetContents, hClose, IOMode(..),
    Handle, hPutStrLn)

-- for storing dates
data Date = Date Integer Int Int deriving (Show, Read, Eq, Ord)

-- for storing database items
data Item = Item    {description :: String
                    ,category    :: [String]
                    ,date        :: Date
                    ,optional    :: [String]}
                    deriving (Show, Read)

-- a state monad transformer which wraps IO actions.
-- the database (state) is passed implicitly between functions.
type ItemList a = StateT [Item] IO a

-- add an item to the database
addItem :: Item -> ItemList ()
addItem i = modify (++ [i])

-- get the newest of a list of items
latest :: [Item] -> [Item]
latest [] = []
latest [x]= [x]
latest xs = take 1 $ sortBy newer xs

-- compare two items to see which one is newer
newer :: Item -> Item -> Ordering
newer a b = compare (date b) (date a)

-- list all different categories (no duplicates)
categories :: ItemList [String]
categories = liftM (nub . concatMap category) get

-- list only the items with the given category tag
filterByCategory :: String -> ItemList [Item]
filterByCategory c = liftM (filter (\i -> c `elem` category i)) get

-- get the newest of all items
lastOfAll :: ItemList [Item]
lastOfAll = liftM latest get

-- get the newest item in each category
latestByCategory :: ItemList [Item]
latestByCategory = do
    cats <- categories
    filt <- mapM filterByCategory cats
    return $ concatMap latest filt

-- sort all items chronologically, newest first
sortByDate :: ItemList [Item]
sortByDate = liftM (sortBy newer) get

toScreen :: Item -> IO ()
toScreen (Item desc cats (Date y m d) opt) = putStrLn $
    "Description:\t" ++ desc ++ "\nCategories:\t" ++ show cats ++
    "\nDate:\t\t" ++ show y ++ "-" ++ show m ++ "-" ++ show d ++
    "\nOther info:\t" ++ show opt

-- command line argument handling
-- if the user called the program with the option "add", the
-- new item is returned to main so that it can be saved to disk.
-- the argument "opt" is a list.
arguments :: ItemList [Item]
arguments = do
    args <- liftIO getArgs
    case args of
        ("add":desc:cat:year:month:day:opt) -> do
            let newItem = parseItem args
            addItem newItem
            return [newItem]
        ("latest":[]) -> do
            item <- lastOfAll
            lift $ mapM_ toScreen item
            return []
        ("category":[]) -> do
            items <- latestByCategory
            lift $ mapM_ toScreen items
            return []
        ("all":[]) -> do
            sorted <- sortByDate
            lift $ mapM_ toScreen sorted
            return []
        _ -> do
            lift usage
            return []

parseItem :: [String] -> Item
parseItem (_:desc:cat:year:month:day:opt) =
    Item {description = desc, category = words cat,
        date = Date (read year) (read month) (read day),
        optional = opt}

usage :: IO ()
usage = do
    progName <- getProgName
    putStrLn $ "Usage: " ++ progName ++ " add|all|category|latest \
        \OPTIONS\n\nadd \"description\" \"category1 category2\"... \
        \year month day [\"note1\" \"note2\"...]\n\tAdds a new record \
        \to the database.\n\nall\n\tPrints all items in chronological \
        \order.\n\ncategory\n\tPrints the latest item for each category.\
        \\n\nlatest\n\tPrints the latest item."

-- the program creates, reads and writes to a file in the current directory
main :: IO ()
main = do
    progName <- getProgName
    let fileName = progName ++ ".db"
    e <- doesFileExist fileName
    if e
        then do
            hr <- openFile fileName ReadMode
            f <- hGetContents hr
            v <- evalStateT arguments (map read $ lines f)
            hClose hr -- must be called after working with contents!
            hw <- openFile fileName AppendMode
            mapM_ (hPutStrLn hw . show) v
            hClose hw
        else do
            v <- evalStateT arguments []
            hw <- openFile fileName WriteMode
            mapM_ (hPutStrLn hw . show) v
            hClose hw


HELP=: 0 :0
Commands:

DBNAME add DATA
DBNAME display the latest entry
DBNAME display the latest entry where CATEGORY contains WORD
DBNAME display all entries
DBNAME display all entries order by CATEGORY

1) The first add with new DBNAME assign category names.
2) lower case arguments verbatim.
3) UPPER CASE: substitute your values.

Examples, having saved this program as a file named s :
$ jconsole s simple.db display all entries
$ jconsole s simple.db add "first field" "2nd field"
)

Q=: ''''                                NB. quote character
time=: 6!:0@:('YYYY-MM-DD:hh:mm:ss.sss'"_)

embed=: >@:{[email protected]:[ , ] , >@:{:@:[
assert '(x+y)' -: '()' embed 'x+y'

Duplicate=: 1 :'#~ m&= + 1 #~ #'
assert 0 1 2 3 3 4 -: 3 Duplicate i.5

prepare=: LF ,~ [: }:@:; (Q;Q,';')&[email protected]:(Q Duplicate)&.>@:(;~ time)
assert (-: }[email protected]:"[email protected]:}:@:prepare) 'boxed';'';'li''st';'of';'''strings'''

categorize=: dyad define
i=. x i. y
if. (1 (~: #) i) +. i (= #) x do.
 smoutput 'choose 1 of'
 smoutput x
 exit 1
end.
{. i                                  NB. "index of" frame has rank 1.
)
assert 0 -: 'abc' categorize 'a'

loadsdb=: (<'time') (<0 0)} ".;._2@:(1!:1)

Dispatch=: conjunction define
help
:
commands=. y
command=. {. commands
x (u`[email protected](command(i.~ ;:)n)) }.commands
)

NB. the long fork in show groups (": #~ (1 1 embed (1j1 }:@:# (1 #~ #))))
show=: [email protected]:(": #~ 1 1 embed 1j1 }:@:# 1 #~ #)

in=: +./@:E.
assert 'the'    in'quick brown fox jumps over the lazy dog'
assert 'the'[email protected]:in'QUICK BROWN FOX JUMPS OVER THE LAZY DOG'

where=: dyad define
'category contains word'=. 3 {. y
if. 'contains' [email protected]:-: contains do.
 help''
 exit 1
end.
i=. x ({[email protected]:[ categorize <@:]) category
j=. {: I. ; word&in&.> i {"1 x
if. 0 (= #) j do.
 smoutput 'no matches'
else.
 x ([email protected]:{~ 0&,) j
end.
)

entry=: 4 : 0
if. a: = y do.
 [email protected]:({. ,: {:) x
else.
 x ''`where Dispatch'where' y
end.
)

latest=: ''`entry Dispatch'entry'
the=: ''`latest Dispatch'latest'

by=: 4 : 0
i=. x (categorize~ {.)~ y
show ({. , (/: i&{"1)@:}.) x
)

order=: ''`by Dispatch'by'

entries=: 4 : 0
if. a: = y do.
 show x
else.
 x ''`order Dispatch'order' y
end.
)

all=: ''`entries Dispatch'entries'

help=: [email protected]:(HELP"_)
add=: 1!:3~ prepare                     NB. minimal---no error tests
display=: (the`all Dispatch'the all'~ loadsdb)~  NB. load the simple db for some sort of display

({. add`display Dispatch'add display' }.)@:(2&}.)ARGV

exit 0


import java.io.*;
import java.text.*;
import java.util.*;

public class SimpleDatabase {

    final static String filename = "simdb.csv";

    public static void main(String[] args) {
        if (args.length < 1 || args.length > 3) {
            printUsage();
            return;
        }

        switch (args[0].toLowerCase()) {
            case "add":
                addItem(args);
                break;
            case "latest":
                printLatest(args);
                break;
            case "all":
                printAll();
                break;
            default:
                printUsage();
                break;
        }
    }

    private static class Item implements Comparable<Item>{
        final String name;
        final String date;
        final String category;

        Item(String n, String d, String c) {
            name = n;
            date = d;
            category = c;
        }

        @Override
        public int compareTo(Item item){
            return date.compareTo(item.date);
        }

        @Override
        public String toString() {
            return String.format("%s,%s,%s%n", name, date, category);
        }
    }

    private static void addItem(String[] input) {
        if (input.length < 2) {
            printUsage();
            return;
        }
        List<Item> db = load();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = sdf.format(new Date());
        String cat = (input.length == 3) ? input[2] : "none";
        db.add(new Item(input[1], date, cat));
        store(db);
    }

    private static void printLatest(String[] a) {
        List<Item> db = load();
        if (db.isEmpty()) {
            System.out.println("No entries in database.");
            return;
        }
        Collections.sort(db);
        if (a.length == 2) {
            for (Item item : db)
                if (item.category.equals(a[1]))
                    System.out.println(item);
        } else {
            System.out.println(db.get(0));
        }
    }

    private static void printAll() {
        List<Item> db = load();
        if (db.isEmpty()) {
            System.out.println("No entries in database.");
            return;
        }
        Collections.sort(db);
        for (Item item : db)
            System.out.println(item);
    }

    private static List<Item> load() {
        List<Item> db = new ArrayList<>();
        try (Scanner sc = new Scanner(new File(filename))) {
            while (sc.hasNext()) {
                String[] item = sc.nextLine().split(",");
                db.add(new Item(item[0], item[1], item[2]));
            }
        } catch (IOException e) {
            System.out.println(e);
        }
        return db;
    }

    private static void store(List<Item> db) {
        try (FileWriter fw = new FileWriter(filename)) {
            for (Item item : db)
                fw.write(item.toString());
        } catch (IOException e) {
            System.out.println(e);
        }
    }

    private static void printUsage() {
         System.out.println("Usage:");
         System.out.println("  simdb cmd [categoryName]");
         System.out.println("  add     add item, followed by optional category");
         System.out.println("  latest  print last added item(s), followed by "
                 + "optional category");
         System.out.println("  all     print all");
         System.out.println("  For instance: add \"some item name\" "
                 + "\"some category name\"");
    }
}


Name,Birthdate,State,Relation,Email
Sally Whittaker,1988-12-05,Illinois,friend,[email protected]
Belinda Jameson,1994-02-17,California,family,[email protected]
Jeff Bragg,2018-10-10,Texas,family,[email protected]
Sandy Allen,2002-03-09,Colorado,friend,[email protected]
Fred Kobo,1967-10-10,Colorado,friend,[email protected]