// -*- pike -*-

// @@PLEAC@@_NAME

// @@SKIP@@ Pike


// @@PLEAC@@_WEB

// @@SKIP@@ http://pike.ida.liu.se/


// @@PLEAC@@_INTRO

// @@SKIP@@ Most examples will work with pike 7.2 or even older,

// @@SKIP@@ some may need 7.4 or even 7.6

// @@SKIP@@ In pike variables must be declared, but may not

// @@SKIP@@ be declared twice, I am not yet sure if it is

// @@SKIP@@ better to declare all variables for each individual example

// @@SKIP@@ or per section...


// @@PLEAC@@_APPENDIX

// (this section is optional; use it if you need to import very

// generic stuff for the whole code)

//

// Note: To avoid clutter each example will only include any necessary

// code. However, it should be understood that:

//

// * The following constants need to be defined:

//

//    constant FALSE = 0, TRUE = 1, PROBLEM = 1, OK = 0,

//      EOF = -1, NULL = "", NEWLINE = "\n", LF = 10, SPACE = 32;

//

// * Each example needs to be enclosed within the following block:

//

//    int main(int argc, array(string) argv)

//    {

//      ...

//    }

//

//   where a 'main' is not provided. Also:

//

//   - Any function definitions would ordinarily be placed

//     before, and outside of, 'main'

//   - Variables can be assumed to be locals residing in 'main';

//     any 'global' variables will be defined at the start of the

//     code example prior to any function definitions


// ----------------------------


string chop(string s, void|int size)
{
  int length = sizeof(s);
  return size > 0 && size < length ? s[..length - (size + 1)] : s;
}

// @@PLEAC@@_1.0

// in pike only double quotes are used for strings

// they are not interpolated.

// single quotes are used for chars (the integer value of a character)

// see chapter 1.4

//-----------------------------

string str;                     // declare a variable of type string

str = "\n";                     // a "newline" character

str = "Jon \"Maddog\" Orwant";  // literal double quotes

//-----------------------------

str =
#"This is a multiline string
terminated by a double-quote like any other string";
//-----------------------------


// @@PLEAC@@_1.1

// accessing part of a string

//------------------------------

string str, value;
int offset, count;
value = str[offset..offset+count];
value = str[offset..];

string newstring, newtail;
str = str[..offset-1]+newstring+str[offset+count..];
str = str[..offset-1]+newtail;

//------------------------------

// get a 5-byte string, skip 3, then grab 2 8-byte strings, then the rest

string leading, s1, s2, trailing;
[leading, s1, s2, trailing] = array_sscanf(str, "%5s%*3s%8s%8s%s");

// split at five byte boundaries

array(string) fivers = str/5;

// chop string into individual characters

array(string) chars = str/"";

//------------------------------


str = "This is what you have"; 

string first, start, rest, last, end, piece;
int t = str[0];
// 84

first = str[0..0];                     
// "T"          

start = str[5..5+1]; 
// "is"

rest  = str[13..];   
// "you have"

last  = str[sizeof(str)-1..sizeof(str)-1];
// "e"

end   = str[sizeof(str)-4..]; 
// "have"                 

piece = str[sizeof(str)-8..sizeof(str)-8+2];
// "you"

               
str = "This is what you have";               
str = replace(str, ([ " is ":" wasn't " ]) );
// "This wasn't what you have"

str = str[..sizeof(str)-13]+"ondrous";       
// "This wasn't wondrous"

str = str[1..]; 
// "his wasn't wondrous"               

str = str[..sizeof(str)-11];
// "his wasn'"            

str = "This is what you have";
str = replace(str[..4], ([ "is":"at" ]) )+str[5..];
// "That is what you have"    

str = "make a hat";
// "make a hat"          

[str[0], str[-1]] = ({ str[-1], str[0] });
// "take a ham"


string a, b, c;
a = "To be or not to be";    
b = a[6..11];      
// "or not"                

b = a[6..7]; c=a[3..4];            
write("%s\n%s\n", b, c);
/*
or                  
be
*/
//------------------------------------------------


string cut2fmt(int ... positions)
{ 
  string template = "";
  int lastpos  = 1;
  foreach(positions ;; int place) 
  {      
    template += "A" + (place - lastpos) + " ";
    lastpos = place;     
  }
  template += "A*";
  return template;
}

string fmt = cut2fmt(8, 14, 20, 26, 30);
write("%s\n", fmt);
//A7 A6 A6 A6 A4 A*         


// @@PLEAC@@_1.2

// set a default, ie, only set the value if no other value is set.

//-----------------------------

// use b if b is true, else c

a = b || c;

// set x to y unless x is already true

if(!x)
  x = y;

// use b if b is defined, else c

// an undefined variable would be a compile time error so this

// does not really apply. 


// return b if b is defined (was supplied by the caller), else c

int foo(int c, int|void b)
{
  return zero_type(b) ? c : b;
}

foo = bar || "DEFAULT VALUE";
argv = argv[1..];              // remove program, as that is always set.

dir = argv[0] || "/tmp";       // and see if anything is left...

dir = sizeof(argv) ? argv[0] : "/tmp";
count[shell||"/bin/sh"]++;     

user = getenv("USER") || getenv("LOGNAME") || getpwuid(getuid())[0] ||
"Unknown uid number "+getuid();

if(!starting_point)
  starting_point = "Greenwich";

if(!sizeof(a))                  
  a = b;              // copy only if empty              

a = (sizeof(b)?b:c);  // assign b if nonempty, else c


// @@PLEAC@@_1.3

[var1, var2] = ({ var2, var1 });  // gee, i love this example.        

                                  // it didn't even occur to me before

                                  // :-)

temp = a;
a    = b;             
b    = temp;

a = "alpha";
b = "omega";
[a, b] = ({ b, a });

[alpha, beta, production] = "January March August"/" ";
[alpha, beta, production] = ({ beta, production, alpha });


// @@PLEAC@@_1.4

// print the ascii value of a char, or the char from its ascii value

int i;                          // declare a variable of type int             

i = 'a';                        // the ascii value of "a"

i = '\n';                       // the ascii value of a "newline"

//-----------------------------

string char = "foo";
int num = char[0];   // gets the ascii value from the first char (that's

                     // what ord() in perl does)

char = String.int2char(num);

char = sprintf("%c",num);   // the same as String.int2char(num) :-)

write("Number %d is character %[0]c\n", num);

Number 101 is character e

string str;
array(int) arr;
arr = (array)str;
str = (string)arr;
int ascii_value = 'e';                      // now 101

string character = String.int2char(101);    // now "e"                  


write("Number %d is character %[0]c\n", 101);


array(int) ascii_character_numbers = (array(int))"sample";
write("%s\n", (array(string))ascii_character_numbers*" ");    

string word = (string)ascii_character_numbers;
string word = (string)({ 115, 97, 109, 112, 108, 101 });  // same

write(word+"\n");
// sample           


string hal ="HAL";
array(int) ascii = (array)hal;
array(int) ibm = ascii[*]+1;       // add 1 to each element in the array.

array(int) ibm = map(ascii, `+, 1) // apply the function +, with the argument

                                   // 1, to each element in the array.

write(ibm+"\n");                   // prints "IBM"


// @@PLEAC@@_1.5

string hello = "Hello world!";
array(string) chars = hello/"";     // array of characters as strings


foreach(chars;; string char)        // this also matches newlines

  ;  // do stuff with char

//-----------------------------

string data = "an apple a day";
array(string) chars = data/"";
mapping(string:int) seen = ([]);

foreach(chars ;; string char)
  seen[char]++; 

write("unique chars are: %s\n", sort(indices(seen))*"");
// unique chars are:  adelnpy

//-----------------------------

string data = "an apple a day";
string result = sort(indices(mkmapping(data/"", allocate(sizeof(data))))*"";

write("unique chars are: %s\n", result);
// unique chars are:  adelnpy

//-----------------------------

string data = "an apple a day";
int sum;

foreach(data ;; int char)
  sum += char;

write("sum is %d\n", sum);
// sum is 1248

//-----------------------------

string data = "an apple a day";
int sum=`+(@(array)data);  

write("sum is %d\n", sum);
// sum is 1248

//-----------------------------

// download the following standalone program
#/usr/bin/pike
// chapter 1.5
void main(int argc, array(string) argv)
{
  string data = Stdio.read_file(argv[1]);
  int checksum;

  foreach(data ;; int char)
    checksum += char;

  checksum %= pow(2,16)-1;
  write("%d\n", checksum);
}

//-----------------------------

// alternate version

// download the following standalone program
#!/usr/bin/pike
// chapter 1.5
void main(int argc, array(string) argv)
{
  string data=Stdio.read_file(argv[1]);
  int checksum = `+(@(array)data) % ((1<<16)-1);
  write("%d\n", checksum);
}

//-----------------------------

// download the following standalone program
#!/usr/bin/pike
// chapter 1.5
// slowcat - emulate a   s l o w  line printer
// usage: slowcat [-DELAY] [files ...]
void main(int argc, array argv)
{
  array(string) files;
  int delay = 1;

  if(argv[1][0] == '-')
  {
    files = argv[2..];
    delay = (int)argv[1][1..];
  }
  else
    files = argv[1..];

  foreach(files, string file)
  {
    string data = Stdio.read_file(file);
    foreach(data/"", string char)
    {
      write(char);
      sleep(0.005*delay);
    }
  }
}

// @@PLEAC@@_1.6

// #1.6 (reverse a string by char/word)

// by Olivier Girondel


string s = "This is  a string";
// Result: "This is  a string"


reverse(s);
// Result: "gnirts a  si sihT"


reverse(s/" ") * " ";               // preserve whitespace

// Result: "string a  is This"


(reverse(s/" ")-({ "" })) * " ";    // collapse whitespace

// Result: "string a is This"

//-----------------------------

string word = "reviver";
int is_palindrome = word==reverse(word);
//-----------------------------

// download the following standalone program
#!/usr/bin/pike
// chapter 1.6
void main(int argc, array(string) argv)
{
  string data=Stdio.read_file(argv[1]);
  foreach(data/"\n", string line)
  {
    if(line==reverse(line) && sizeof(line)>5)
    write("%s\n", line);
  }
}

// @@PLEAC@@_1.7

string s = "This         is                a    \n   string";

string notabs=String.expand_tabs(s);
// Result: "This     is                a    \n   string"


string notabs=String.expand_tabs(s, 4);
// Result: "This     is            a    \n   string"


string notabs=String.expand_tabs(s, 4, "-");
// Result: "This   - is --------   a ---\n   string"

//-----------------------------

string s = "This     is      a     string";
string tabs="";

foreach(s/8.0 ;; string stop)
{ 
  int spaces=sizeof(String.common_prefix(({ reverse(stop), "        "}))); 
  tabs+=stop[..7-spaces]; 
  if(spaces)
    tabs+="^I";
}
// Result: "This\t is\t a     string"


string notabs=""; 
foreach(tabs/"^I" ;; string stop)
{ 
  notabs+=stop; 
  if(sizeof(stop)<8) 
    notabs+=" "*(8-sizeof(stop)); 
}
// Result: "This     is      a     string"


// @@PLEAC@@_1.8

// since variable names in pike do not have a special notation we need to 

// "invent" one for this.

// there are a few ways to solve this problem.

// here is one:


mapping(string:string) vars = ([ "$fruit$":"apple", "$desert$":"pudding" ]);
string template  = "Todays fruit is $fruit$, and for desert we have $desert$";
string menu = replace(template, vars);

// Result: "Todays fruit is apple, and for desert we have pudding"



// @@PLEAC@@_1.9

string upper, lower, result;
upper = "DON'T SHOUT!";
result = lower_case(upper);
// Result: "don't shout!"

//-----------------------------

lower = "speak up";
result = upper_case(lower);
// Result: "SPEAK UP"

//-----------------------------

result = String.capitalize(lower);
// Result: "Speak up"


//-----------------------------

string text = "thIS is a loNG liNE";
array(string) words = text/" ";       // splits the line into words

words = lower_case(words[*]);         // lower_case each word

words = String.capitalize(words[*]);  // capitalize each word

text = words*" ";                     // join back

// you may do the same in one short line:

text = String.capitalize(lower_case((text/" ")[*])[*])*" ";

// download the following standalone program
#!/usr/bin/pike
// chapter 1.9
// randcap: filter to randomly capitalize 20% of the letters

void main()
{
  string input;
  while(input=Stdio.stdin.read(1))
    write(randcap(input));
}

string randcap(string char)
{
  if(random(100)<20)
    char=String.capitalize(char);
  return char;
}


// @@PLEAC@@_1.10

// since pike does not provide any string interpolation 

// there are no sneaky tricks here.

// a solution could be similar to the one in chapter 1.8

// putting functions into the mapping instead of string values, or use xml and

// callbacks

// TODO: provide an example of using the xml parser here


// @@PLEAC@@_1.11

// we believe that indenting the string and then removing that indent does not

// actually enhance readability of the code.

// but if you insist the following will remove all whitespace at the beginning

// of each line:

string here=#"your text
              goes here";
    
string there=array_sscanf((here/"\n")[*], "%*[\t ]%s")[*][0]*"\n";

// expanded version:

array tmp=({});
foreach(here/"\n";; string line)
{
  tmp+=array_sscanf(line, "%*[\t ]%s");
}
string there=tmp*"\n";

// @@PLEAC@@_1.12

// pike sprintf() provides a facility for wrapping (column mode):

// sprintf("%-=<int width>s", text);

// download the following standalone program
#!/usr/bin/pike
// chapter 1.12
// wrapdemo - show how wrapping with sprintf works
void main()
{
  array(string) input = ({ "Folding and splicing is the work of an editor,",
                           "not a mere collection of silicon",
                           "and",
                           "mobile electrons!"});
  int columns = 20;

  write("0123456789"*2+"\n");
  write(wrap(input*" ", 20, "  ", "  ")+"\n");
}

// unlike the perl version here leadtab is relative to nexttab, 
// to get a shorter lead use a negative int value. this allows the default of 0
// to be a lead indent that is the same as nexttab, and it also has the
// advantage of allowing you to change the indent without having to worry about
// the lead getting messed up.
// a negative lead will cut away from the nexttab which will be visible if you
// use something other than spaces
string wrap(string text, void|int width, 
            void|string|int nexttab, void|string|int leadtab)
{
  string leadindent="";
  string indent=""; 
  string indent2="";

  if(!width)
    width=Stdio.stdout->tcgetattr()->columns;

  if(stringp(nexttab))
  {
    indent=nexttab;
    width-=sizeof(nexttab);  // this will be off if there are chars that have a
                             // different width than 1.
  }
  else if(intp(nexttab))
  {
    indent=" "*nexttab;
    width-=nexttab;
  }

  if(stringp(leadtab))
    leadindent=leadtab;
  else if(intp(leadtab))
    if(leadtab > 0)
      leadindent=" "*leadtab;
    else if(leadtab < 0)
    {
      write(indent+".\n");
      indent=indent[..(sizeof(indent)+leadtab)-1];
      write(indent+".\n");
      indent2=text[..-leadtab-1]; 
      text=text[-leadtab..];
    }
  return sprintf("%^s%=*s%-=*s", indent, sizeof(indent2), indent2, 
                                 width, leadindent+text);
}

//----------------------------------------------

$ ./wrapdemo 
01234567890123456789
    Folding and     
  splicing is the   
  work of an editor,
  not a mere        
  collection of     
  silicon and mobile
  electrons!        

// merge multiple lines into one, then wrap one long line

inherit "wrapdemo.pike";
wrap(replace(text, "\n", " "));

// read stdin and split by paragraph,

// remove \n in paragraphs

// reformat

// add paragraph break

foreach(Stdio.stdin->read()/"\n\n";; string para)
  write(wrap(replace(para, "\n", " "))+"\n\n");

// @@PLEAC@@_1.13

// we need to escape the \ for this example, ironic, eh?

array(string) charlist=({ "%", "\\" }); 
string var="some input % text with \\";

// backslash

var=replace(var, charlist, "\\"+charlist[*]);

// double

var=replace(var, charlist, charlist[*]+charlist[*]);

// @@PLEAC@@_1.14

string line=" foo\n\t ";
array(string) many=({ " bar\n\t ", " baz\t " });

// remove spaces and tabs

line=String.trim_whites(line);
many=String.trim_whites(many[*]);

//remove spaces, tabs, newlines and carriage returns

line=String.trim_all_whites(line);
many=String.trim_all_whites(many[*]);

// @@PLEAC@@_1.16

// contributed by martin nilsson


write("Lookup user: ");
string user = String.soundex(Stdio.stdin.gets());
foreach(get_all_users(), array u) 
{
  string firstname="", lastname="";
  sscanf(u[4], "%s %s,", firstname, lastname);
  if( user==String.soundex(u[0]) ||
      user==String.soundex(firstname) ||
      user==String.soundex(lastname) )
    write("%s: %s %s\n", u[0], firstname, lastname);
}


// @@PLEAC@@_2.1

string number="123.3asdf";

int|float realnumber= (int)number;  // casting to int will throw away all

                                    // nonnumber parts

string rest;
[realnumber, rest] = array_sscanf(number, "%d%s"); // scan for an integer

// if rest contains anything but the empty string, then there was more than a

// number in the string

// use %f to scan for float, %x for hex or %o for octal


// @@PLEAC@@_2.2

int same(float one, float two, int accuracy)
{
  return sprintf("%.*f", accuracy, one) == sprintf("%.*f", accuracy, two);
}

int wage=536;
int week=40*wage;
write("one week's wage is: $%.2f\n", week/100.0);

// @@PLEAC@@_2.3

float unrounded=3.5;
string rounded=sprintf("%.*f", accuracy, unrounded);

float a=0.255;
string b=sprintf("%.2f", a);

write("Unrounded: %f\nRounded: %s\n", a, b);
write("Unrounded: %f\nRounded: %.2f\n", a, a);

// dec to bin

string bin=sprintf("%b", 5);

int dec=array_sscanf("0000011111111111111", "%b")[0]; 
                // array_sscanf returns an array


int num = array_sscanf("0110110", "%b")[0];  // num is 54

string binstr = sprintf("%b", 54);           // binstr is 110110



// @@PLEAC@@_2.4

// contributed by martin nilsson.


string dec2bin(int n) 
{ 
  return sprintf("%b",n); 
}

int bin2dec(string n) 
{ 
  return array_sscanf(n, "%b")[0];
}

// @@PLEAC@@_2.5

// foreach(enumerate(int count, int step, int start);; int val)

// {

//   // val is set to each of count integers starting at start

// }


foreach(enumerate(y-x+1,1,x);; int val)
{
  // val is set to every integer from X to Y, inclusive

}

for(int i=x; i<=y; i++)
{
  // val is set to every integer from X to Y, inclusive

}

for(int i=x; i<=y; i+=7)
{
  // val is set to every integer from X to Y, stepsize = 7

}

foreach(enumerate(y-x+1,7,x);; int val)
{
  // val is set to every integer from X to Y, stepsize = 7

}

//----------------------------------------

write("Infancy is: ");
foreach(enumerate(3);; int val) 
{
  write("%d ", val);
}
write("\n");

write("Toddling is: %{%d %}\n", enumerate(2,1,3));

write("Childhood is: ");
for (int i = 5; i <= 12; i++) 
{
  write("%d ", i);
}
write("\n");

// Infancy is: 0 1 2 

// Toddling is: 3 4 

// Childhood is: 5 6 7 8 9 10 11 12 


// @@PLEAC@@_2.6

int arabic;
string roman = String.int2roman(arabic);        // handles values up to 10000


array nums=enumerate(10001);                
array romans=String.int2roman(nums[*]);     
mapping roman2int = mkmapping(romans, nums);                   

int arabic = roman2int[roman];

//------------------------------------------------

string roman_fifteen = String.int2roman(15);    //  "XV"

write("Roman for fifteen is %s\n", roman_fifteen);

int arabic_fifteen = roman2int[roman_fifteen];
write("Converted back, %s is %d\n", roman_fifteen, arabic_fifteen);

// Roman for fifteen is XV

// Converted back, XV is 15


// @@PLEAC@@_2.7

int y,x;
int rand = random(y-x+1)+x;

float y,x;
float rand = random(y-x+1)+x;

int rand = random(51)+25;
write("%d\n", rand);

array arr;
mixed elt = arr[random(sizeof(arr))];
mixed elt = random(arr);

array chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@$%^&*"/"";
string password = "";
for(int i=1; i<=8; i++)
{
  password+=random(chars);
}

string password = random((({chars})*8)[*])*"";

string password = random_string(8);      // creates an untypable string


// turn the string into something typable using the base64 charset

string password = MIME.encode_base64(random_string(8))[..7];

// @@PLEAC@@_2.8

random_seed(int seed);
random_seed((int)argv[1]);

// @@PLEAC@@_2.9

// Crypto.Random.random(int max)

// Crypto.Random.random_string(int length)

// Crypto.Random.blocking_random_string(int length)

// Crypto.Random.add_entropy(string random_data, int entropy)


// @@PLEAC@@_2.10

float gaussian_rand()
{
  float u1, u2, w, g1, g2;
  
  do
  {
    u1 = 2.0 * random(1.0) - 1.0; u2 = 2.0 * random(1.0) - 1.0;
    w = u1 * u1 + u2 * u2;
  } while (w > 1.0);
  
  w = sqrt((-2.0 * log(w)) / w); g2 = u1 * w; g1 = u2 * w;

  return g1;
}

// ----


float mean = 25.0, sdev = 2.0;
float salary = gaussian_rand() * mean + sdev;

write("You have been hired at: %.2f\n", salary);

// @@PLEAC@@_2.11

float deg2rad(float deg)
{
  return (deg / 180.0) * Math.pi;
}

float rad2deg(float rad)
{
  return (rad / Math.pi) * 180.0;
}

// ----


write("%f\n", Math.convert_angle(180, "deg", "rad"));
write("%f\n", deg2rad(180.0));

// ----------------------------


float degree_sin(float deg)
{
  return sin(deg2rad(deg));
}

// ----


float rad = deg2rad(380.0);
write("%f\n", sin(rad));
write("%f\n", degree_sin(380.0));

// @@PLEAC@@_2.12

float my_tan(float theta)
{
  return sin(theta) / cos(theta);
}

// ----


float theta = 3.7;

write("%f\n", my_tan(theta));
write("%f\n", tan(theta));

// @@PLEAC@@_2.13

float value = 100.0;

float log_e = log(value);
float log_10 = Math.log10(value);

// ----------------------------


float log_base(float base, float value)
{
  return log(value) / log(base);
}

// ----


float answer = log_base(10.0, 10000.0);

write("log(10, 10,000) = %f\n", answer);

// @@PLEAC@@_2.14

// Pike offers a solid matrix implementation; highlights:

// * Operator overloading makes matrix operations succinct

// * Matrices may be of various types, thus allowing user to

//   choose between range representation and speed

// * Wide variety of operations available


Math.Matrix a = Math.Matrix( ({ ({3, 2, 3}), ({5, 9, 8})  }) ),
            b = Math.Matrix( ({ ({4, 7}), ({9, 3}), ({8, 1}) }) );

Math.Matrix c = a * b;

// ------------


Math.Matrix t = c->transpose();

// @@PLEAC@@_2.15

// @@INCOMPLETE@@


// @@PLEAC@@_2.16

// Like C, Pike supports decimal-alternate notations. Thus, for example,

// the integer value, 867, is expressable in literal form as:

//

//   Hexadecimal -> 0x363

//   Octal       -> 01543

//

// For effecting such conversions using strings there is 'sprintf' and

// 'sscanf'.


int dec = 867;
string hex = sprintf("%x", dec);
string oct = sprintf("%o", dec);

// ------------


int dec;
string hex = "363"; sscanf(hex, "%x", dec);

// ------------


int dec;
string oct = "1543"; sscanf(oct, "%o", dec);

// ----------------------------


int number;

write("Gimme a number in decimal, octal, or hex: ");
sscanf(Stdio.stdin->gets(), "%D", number);

write("%d %x %o\n", number, number, number);

// @@PLEAC@@_2.17

string commify_series(int series)
{
  return reverse((reverse((string)series) / 3.0) * ",");
}

// ----


int hits = 3452347;

write("Your website received %s accesses last month.\n", commify_series(hits));

// ----------------------------


string commify(string s)
{
  function t = lambda(string m) { return reverse((reverse(m) / 3.0) * ","); };
  return Regexp.PCRE("([0-9]+)")->replace(s, t);
}

// ----


int hits = 3452347;
string output = sprintf("Your website received %d accesses last month.", hits);

write("%s\n", commify(output));

// @@PLEAC@@_2.18

string pluralise(int value, string root, void|string singular_, void|string plural_)
{
  string singular = singular_ ? singular_ : "";
  string plural = plural_ ? plural_ : "s";

  return root + ( (value > 1) ? plural : singular );
}

// ----


int duration = 1;
write("It took %d %s\n", duration, pluralise(duration, "hour"));
write("%d %s %s enough.\n", duration, pluralise(duration, "hour"),
      pluralise(duration, "", "is", "are"));

duration = 5;
write("It took %d %s\n", duration, pluralise(duration, "hour"));
write("%d %s %s enough.\n", duration, pluralise(duration, "hour"),
      pluralise(duration, "", "is", "are"));

// ----------------------------


// Non-regexp implementation, uses the string-based, 'has_prefix'

// and 'replace' library functions

string plural(string singular)
{
  mapping(string : string) e2 =
    (["ss":"sses", "ph":"phes", "sh":"shes", "ch":"ches",
      "ey":"eys", "ix":"ices", "ff":"ffs"]);

  mapping(string : string) e1 =
    (["z":"zes", "f":"ves", "y":"ies", "s":"ses", "x":"xes"]);

  foreach(({e2, e1}), mapping(string : string) endings) 
  {
    foreach(indices(endings), string ending)
    {
      if (has_suffix(singular, ending))
      {
        return replace(singular, ending, endings[ending]);
      }
    }
  }

  return singular;
}

// ----


int main()
{
  foreach(aggregate("mess", "index", "leaf", "puppy"), string word)
    write("%6s -> %s\n", word, plural(word));
}

// @@PLEAC@@_2.19

// download the following standalone program
#!/usr/bin/pike
// contributed by martin nilsson

void main(int n, array args) 
{
  foreach(args[1..], string arg) 
  {
    mapping r = ([]);
    foreach(Math.factor((int)arg), int f)
      r[f]++;
    write("%-10s", arg);
    if(sizeof(r)==1)
      write(" PRIME");
    else 
    {
      foreach(sort(indices(r)), int f) 
      {
        write(" %d", f);
        if(r[f]>1) write("**%d", r[f]);
      }
    }
    write("\n");
  }
}



// @@PLEAC@@_3.0

// Pike has an extensive Calendar module that provides all manners of

// manipulating dates and times.

write("Today is day %d of the current year.\n", localtime(time())->yday+1);
// Today is day 325 of the current year.


write("Today is day %d of the current year.\n", Calendar.now()->year_day());
// Today is day 325 of the current year.


// @@PLEAC@@_3.1

int day, month, year;
mapping now=localtime(time());
year  = now->year+1900;
month = now->mon+1;
day   = now->mday;

write("The current date is %04d %02d %02d\n", year, month, day);

object now=Calendar.now();
year  = now->year_no();
month = now->month_no();
day   = now->month_day();

write("The current date is %04d %02d %02d\n", year, month, day);

write("The current date is %04d %02d %02d\n", @lambda(){ return ({ now->year_no(), now->month_no(), now->month_day() }); }(Calendar.now()));
// this is essentially the same as the respective perl code:

// lambda creates an anonymous function, which in this case takes one argument 

// and returns an array. the array is the spliced into the arguments of write().

// if the goal is to get by without a temporary variable this is a rather

// pointless exercise, as there is still a temporary variable (in the function)

// and the temporary function on top of that. more interresting is the

// functional approach aspect.


// @@PLEAC@@_3.2

// dwim_time() handles most common date and time formats.

Calendar.dwim_time("2:40:25 23.11.2004");
// Result: Second(Tue 23 Nov 2004 2:40:25 CET)

Calendar.dwim_time("2:40:25 23.11.2004")->unix_time();
// Result: 1101174025


Calendar.dwim_time("2:40:25 UTC 23.11.2004");
// Result: Second(Tue 23 Nov 2004 2:40:25 UTC)


// faster, because there is no need for guessing:

Calendar.parse("%Y-%M-%D %h:%m:%s %z","2004-11-23 2:40:25 UTC");
// Result: Second(Tue 23 Nov 2004 2:40:25 UTC)


// without parsing

Calendar.Second(2004, 11, 23, 2, 40, 25);
// Result: Second(Tue 23 Nov 2004 2:40:25 CET)


// functional

Calendar.Year(2004)->month(11)->day(23)->hour(2)->minute(40)->second(25);
// Result: Second(Tue 23 Nov 2004 2:40:25 CET)


Calendar.Day(2004, 11, 23)->set_timezone("UTC")->hour(2)->minute(40)->second(25);
// Result: Second(Tue 23 Nov 2004 2:40:25 UTC)


// set a time today

Calendar.dwim_time("2:40:25");    
// Result: Second(Tue 23 Nov 2004 2:40:25 CET)

Calendar.dwim_time("2:40:25 UTC");           
// Result: Second(Tue 23 Nov 2004 2:40:25 UTC)


Calendar.parse("%h:%m:%s %z","2:40:25 UTC");                    
// Result: Second(Tue 23 Nov 2004 2:40:25 UTC)

Calendar.Day()->set_timezone("UTC")->hour(2)->minute(40)->second(25);
// Result: Second(Tue 23 Nov 2004 2:40:25 UTC)


// @@PLEAC@@_3.3

int unixtime=1101174025;
int day, month, year;
mapping then=localtime(unixtime);
year  = then->year+1900;
month = then->mon+1;
day   = then->mday;

write("Dateline: %02d:%02d:%02d-%04d/%02d/%02d\n", then->hour, then->min, then->sec, then->year+1900, then->mon+1, then->mday);
// Dateline: 02:40:25-2004/11/23


object othen=Calendar.Second(unixtime);
// Result: Second(Tue 23 Nov 2004 2:40:25 CET)


write("Dateline: %02d:%02d:%02d-%04d/%02d/%02d\n", othen->hour_no(), 
      othen->minute_no(), othen->second_no(), othen->year_no(), 
      othen->month_no(), othen->month_day());
// Dateline: 02:40:25-2004/11/23


// @@PLEAC@@_3.4

int days_offet=55;
int hour_offset=2;
int minute_offset=17;
int second_offset=5;

object then=Calendar.parse("%D/%M/%Y, %h:%m:%s %p","18/Jan/1973, 3:45:50 pm")
            +Calendar.Day()*days_offet
            +Calendar.Hour()*hour_offset
            +Calendar.Minute()*minute_offset
            +Calendar.Second()*second_offset;
write("Then is %s\n", then->format_ctime());
// Then is Wed Mar 14 18:02:55 1973

write("To be precise: %d:%d:%d, %d/%d/%d\n", 
             then->hour_no(), then->minute_no(), then->second_no(),
             then->month_no(), then->month_day(), then->year_no());
// To be precise: 18:2:55, 3/14/1973


int years   = 1973;
int months  = 1;
int days    = 18;
int offset  = 55;
object then = Calendar.Day(years, months, days)+offset;
write("Nat was 55 days old on: %d/%d/%d\n", then->month_no(), then->month_day(),then->year_no());
// Nat was 55 days old on: 3/14/1973


// @@PLEAC@@_3.5

int bree = 361535725;         // 16 Jun 1981, 4:35:25

int nat  = 96201950;          // 18 Jan 1973, 3:45:50


int difference = bree-nat;
write("There were %d seconds between Nat and Bree\n", difference);
// There were 265333775 seconds between Nat and Bree


int seconds =  difference                % 60;
int minutes = (difference / 60)          % 60;
int hours   = (difference / (60*60) )    % 24;
int days    = (difference / (60*60*24) ) % 7;
int weeks   =  difference / (60*60*24*7);

write("(%d weeks, %d days, %d:%d:%d)\n", weeks, days, hours, minutes, seconds);
// (438 weeks, 4 days, 23:49:35)


object bree = Calendar.dwim_time("16 Jun 1981, 4:35:25");
// Result: Second(Tue 16 Jun 1981 4:35:25 CEST)

object nat  = Calendar.dwim_time("18 Jan 1973, 3:45:50");
// Result: Second(Thu 18 Jan 1973 3:45:50 CET)

object difference = nat->range(bree);
// Result: Second(Thu 18 Jan 1973 3:45:50 CET - Tue 16 Jun 1981 4:35:26 CEST)


write("There were %d days between Nat and Bree\n", difference/Calendar.Day());
// There were 3071 days between Nat and Bree


int days=difference/Calendar.Day();
object left=difference->add(days,Calendar.Day)->range(difference->end());

// Calendar handles timezone differences, and since the range crosses from

// normal to daylight savings time, there is one day which has only 23 hours.

// by adding the number of days we effectively move the beginning of the range

// to the same day as the end, leaving us with a range that is less than a day

// long. when adding the days, the daylight savings switch will be taken into

// account.


write("Bree came %d days, %d:%d:%d after Nat\n", 
                   days, 
                   (left/Calendar.Hour())%24,
                   (left/Calendar.Minute())%60,
                   (left/Calendar.Second())%60,
                   );

// Bree came 3071 days, 0:49:36 after Nat


// the following is more accurate, taking into account that the days where

// daylight savings time is switched do not have 24, but 23 and 25 hours.

// thanks to mirar on the pike list for pointing this out and providing a

// correct solution.


array(int) breakdown_elapsed(object u, void|array on)
{  
  array res=({});
  if (!on) on=({Day,Hour,Minute,Second});
  foreach (on;;program|TimeRange p)
  {  
    if (u==u->end()) { res+=({0}); continue; }
    int x=u/p;
    u=u->add(x,p)->range