// -*- 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 //----------------------------- // @@INCLUDE@@ include/pike/ch01/sum //----------------------------- // alternate version // @@INCLUDE@@ include/pike/ch01/sum2 //----------------------------- // @@INCLUDE@@ include/pike/ch01/slowcat // @@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); //----------------------------- // @@INCLUDE@@ include/pike/ch01/palindrome // @@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/" ")[*])[*])*" "; // @@INCLUDE@@ include/pike/ch01/randcap // @@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("%-=s", text); // @@INCLUDE@@ include/pike/ch01/wrapdemo //---------------------------------------------- $ ./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 // @@INCLUDE@@ include/pike/ch02/bigfact // @@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(u->end()); res+=({x}); } return res; } write("Bree came %d days, %d:%d:%d after Nat\n", @breakdown_elapsed(difference)); // Bree came 3071 days, 0:49:36 after Nat // @@PLEAC@@_3.6 mapping day=localtime(time()); day->mday; // Result: 2 day->wday; // Result: 4 day->yday; // Result: 336 int year=1981; int month=6; int day=16; object date; date = Calendar.Day(year, month, day); // Result: Day(Tue 16 Jun 1981) date->week_day(); // Result: 3 date->week_no(); // Result: 24 date->year_day(); // Result: 167 write("%d/%d/%d was a %s\n", month, day, year, date->week_day_name()); // 6/16/1981 was a Tuesday write("in the week number %d.\n", date->week_no()); // in the week number 25. // @@PLEAC@@_3.7 string date = "1998-06-03"; int yyyy; int mm; int dd; [yyyy, mm, dd] = array_sscanf(date, "%d-%d-%d"); object day; day=Calendar.dwim_day(date); day=Calendar.parse("%Y-%M-%D", date); day->unix_time(); // Result: 896824800 day->year_no(); // Result: 1998 day->month_no(); // Result: 6 day->month_day(); // Result: 3 // @@PLEAC@@_3.8 object now=Calendar.dwim_time("Sun Sep 21 15:33:36 1997"); // Result: Second(Sun 21 Sep 1997 15:33:36 CEST) now->format_ctime(); // Result: "Sun Sep 21 15:33:36 1997\n" // there is no equivalent to scalar localtime now = Calendar.Second(1973, 1, 18, 3, 45, 50); write("strftime gives: %s %02d/%02d/%!2d\n", now->week_day_name(), now->month_no(), now->month_day(), now->year_no()); // strftime gives: Sunday 01/18/73 // pike doesn't have strftime, but hey. // instead Calendar provides a large array of predefined formats: // format_nice() and format_nicez() depend on the unit: now->format_nice(); // "18 Jan 1973 3:45:50" now->week()->format_nice(); // "w3 1973" now->format_nicez(); // "18 Jan 1973 3:45:50 CET" now->hour()->format_nicez(); // "18 Jan 1973 3:00 CET" // others are unit independant. now->format_ext_time(); // "Thursday, 18 January 1973 03:45:50" now->format_ext_ymd(); // "Thursday, 18 January 1973" now->format_iso_time(); // "1973-01-18 (Jan) -W03-4 (Thu) 03:45:50 UTC+1" now->format_iso_ymd(); // "1973-01-18 (Jan) -W03-4 (Thu)" now->format_mod(); // "03:45" now->format_month(); // "1973-01" now->format_month_short(); // "197301" now->format_mtime(); // "1973-01-18 03:45" now->format_time(); // "1973-01-18 03:45:50" now->format_time_short(); // "19730118 03:45:50" now->format_time_xshort(); // "730118 03:45:50" now->format_tod(); // "03:45:50" now->format_tod_short(); // "034550" now->format_todz(); // "03:45:50 CET" now->format_todz_iso(); // "03:45:50 UTC+1" now->format_week(); // "1973-w3" now->format_week_short(); // "1973w3" now->format_iso_week(); // "1973-W03" now->format_iso_week_short(); // "197303" now->format_xtime(); // "1973-01-18 03:45:50.000000" now->format_xtod(); // "03:45:50.000000" now->format_ymd(); // "1973-01-18" now->format_ymd_short(); // "19730118" now->format_ymd_xshort(); // "730118" now->format_ctime(); // "Thu Jan 18 03:45:50 1973\n" now->format_smtp(); // "Thu, 18 Jan 1973 3:45:50 +0100" now->format_http(); // "Thu, 18 Jan 1973 02:45:50 GMT" // @@PLEAC@@_3.9 int t=time(); // current time in unixtime seconds float t0=time(t); // higher precision time passed since t float t1=time(t); float elapsed=t1-t0; // Result: 0.009453 //------------------------------------------- write("Press return when ready: "); array(int) before=System.gettimeofday(); Stdio.stdin->gets(); array(int) after=System.gettimeofday(); int elapsed_sec=after[0]-before[0]; int elapsed_usec=after[1]-before[1]; if(elapsed_usec<0) { elapsed_sec--; elapsed_usec+=1000000; } write("You took %d.%d seconds.\n", elapsed_sec, elapsed_usec); //------------------------------------- // this is an expanded example compared to the one given for perl // to allow comparison of different types. // bignum are objects of the gmp library, which are seamlessly integrated with // regular integers. int main() { // size values are adjusted so that each run takes about the same length. gaugethis(5000000, 100, lambda(){ return random(pow(2,31)-1); }); // values to fit into a signed 32bit int. gaugethis(50000, 100, lambda(){ return pow(2,64)+random(pow(2,64)); }); // make sure values are bignum even in case 64bit ints are used. gaugethis(500000, 100, lambda(){ return random_string(10); }); // might be interresting to compare longer strings too. } void gaugethis(int size, int number_of_times, function rand) { array gauged_times = ({}); float average; int swidth=sizeof((string)size); int nwidth=sizeof((string)number_of_times); for(int i; idistance( Calendar.dwim_time("Wed, 27 May 1998 05:04:03 +0100"))->format_elapsed(); // Result: "0:06:25" // @@INCLUDE@@ include/pike/ch03/hopdelta // @@PLEAC@@_4.0 // nested arrays are supported array flat = ({ "this", "that", "the", "other" }); array nested = ({ "this", "that", ({ "the", "other" }) }); array tune = ({ "The", "Star-Spangled", "Banner" }); tune[0]; // Result: "The" tune[1]; // Result: "Star-Spangled" // the typing may be more specific // only strings allowed in the array (thus no nesting!) array(string) flat = ({ "this", "that", "the", "other" }); // allow one level of nesting array(string|array(string)) admit1 = ({ "this", "that", ({ "the", "other" }) }); // the first level may only contain arrays, other levels may contain anything array(array) require1 ({ ({ "this", "that" }), ({ "the", "other" }) }); // @@PLEAC@@_4.1 // list array(string) a = ({ "quick", "brown", "fox" }); // words array(string) a = "Why are you teasing me?"/" "; // lines array(string) lines = #"The boy stood on the burning deck, It was as hot as glass."/"\n"; // file array(string) bigarray = Stdio.read_file("mydatafile")/"\n"; // the quoting issues do not apply. array(string) ships = "Niña Pinta Santa María"/" "; // wrong array(string) ships = ({ "Niña", "Pinta", "Santa María" }); // right // @@PLEAC@@_4.2 // @@INCLUDE@@ include/pike/ch04/commify_series // @@PLEAC@@_4.3 void what_about_that_array(array list) { write("The array now has %d elements.\n", sizeof(list)); write("The index of the last element is %d.\n", sizeof(list)-1); write("Element #3 is %O.\n", list[3]); } array people = ({ "Crosby", "Stills", "Nash", "Young" }); what_about_that_array(people); // The array now has 4 elements. // The index of the last element is 3. // Element #3 is "Young". people=people[..sizeof(people)-2]; what_about_that_array(people); // The array now has 3 elements. // The index of the last element is 2. // Index 3 is out of array range -3..2. people+=allocate(10001-sizeof(people)); what_about_that_array(people); // The array now has 10001 elements. // The index of the last element is 10000. // Element #3 is 0. array people = ({ "Crosby", "Stills", "Nash", "Young" }); // resetting the array people[10000]=0; // Index 10000 is out of array range -4..3. // accessing a nonexisting index is always an error. // arrays can not be enlarged this way. // @@PLEAC@@_4.4 foreach(list; int index; mixed item) { // do something with item (and possibly index) } foreach(bad_users;; object user) { complain(user); } // for such simple cases pike provides a convenient automap feature: complain(bad_users[*]); // will do the same as the foreach above. foreach(sort(indices(getenv()));; string var) { write("%s=%s\n", var, getenv(var)); } // if you don't need an assurance that the indices are sorted (they most likely // are sorted anyways) you may use: foreach(getenv(); string var; string value) { write("%s=%s\n", var, value); } foreach(all_users;; string user) { int disk_space = get_usage(user); if(disk_space > MAX_QUOTA) complain(user); } // continue; to jump to the next // break; to stop the loop // redo can be done by doing a loop with the proper checks in the block object pipe=Stdio.File(); Process.create_process(({ "who" }), ([ "stdout":pipe->pipe() ])); foreach(pipe->line_iterator();; string line) { if(search(line, "tchrist")>-1) write(line+"\n"); } object fh=Stdio.File("somefile"); foreach(fh->line_iterator(); int linenr; string line) { foreach(Process.split_quoted_string(line);; string word)//split on whitespace { write(reverse(word)); } } array(int) list = ({ 1,2,3 }); foreach(list;; int item) { item--; } write("%{%d %}\n", list); // Result: 1 2 3 // we can still use foreach instead of for, // because foreach gives us the index as well: foreach(list; int index;) { list[index]--; } write("%{%d %}\n", list); // Result: 0 1 2 array a = ({ 0.5, 3 }); array b = ({ 0, 1 }); // foreach handles only one array so there is nothing to gain here. // better use automap: array a_ = a[*]*7; array b_ = b[*]*7; write("%{%O %}\n", a_+b_); // 3.500000 21 0 7 string scalar = " abc "; array(string) list = ({ " a ", " b " }); mapping(mixed:string) hash = ([ "a":" a ", "b":" b " ]); scalar = String.trim_whites(scalar); list = String.trim_whites(list[*]); foreach(hash; int key;) { hash[key]=String.trim_whites(hash[key]); } // @@PLEAC@@_4.5 // pike does not distinguish between arrays and array references // (they are all references anyways) so this section does not apply // @@PLEAC@@_4.6 mapping seen = ([]); array uniq = ({}); foreach(list;; mixed item) { if(!seen[item]) seen[item] = 1; else uniq += ({ item }); } mapping seen = ([]); array uniq = ({}); foreach(list;; mixed item) { if(!seen[item]++) uniq += ({ item }); } mapping seen = ([]); array uniq = ({}); foreach(list;; mixed item) { if(!seen[item]++) some_func(item); } // the following is probably the most natural for pike mapping seen = ([]); array uniq = ({}); foreach(list;; mixed item) { seen[item]++; } uniq = indices(seen); // not necessarily faster but shorter: array uniq = indices(({ list[*],1 })); // also short, and preserving the originaal order: array uniq = list&indices(({ list[*],1 })); object pipe = Stdio.File(); Process.create_process(({ "who" }), ([ "stdout":pipe->pipe() ])); mapping ucnt = ([]); foreach(pipe->line_iterator();; string line) { ucnt[(line/" ")[0]]++; } array users = sort(indices(ucnt)); write("users logged in: %s\n", users*" "); // @@PLEAC@@_4.7 // one of pikes strenghts are operators. // the following are the only idiomatic solutions to the problem array A = ({ 1, 2, 3 }); array B = ({ 2, 3, 4 }); array aonly = A-B; // Result: ({ 1 }); // @@PLEAC@@_4.8 array a = ({ 1, 3, 5, 6, 7, 8 }); array b = ({ 2, 3, 5, 7, 9 }); // union: array union = a|b; // ({ 1, 3, 5, 6, 7, 8, 2, 9 }) // intersection array intersection = a&b; // ({ 3, 5, 7 }) // difference array difference = a-b; // ({ 1, 6, 8 }) // symetric difference array symdiff= a^b; // ({ 1, 6, 8, 2, 9 }) // @@PLEAC@@_4.9 // join arrays // appending to an array will always create a new array and pike is designed to // handle this efficiently. array members = ({ "Time", "Flies" }); array initiates = ({ "An", "Arrow" }); members += initiates; // members is now ({ "Time", "Flies", "An", "Arrow" }) members = members[..1]+({ "Like" })+members[2..]; write("%s\n", members*" "); members[0] = "Fruit"; members = members[..sizeof(members)-3]+({ "A", "Banana" }); write("%s\n", members*" "); // Time Flies Like An Arrow // Fruit Flies Like A Banana // @@PLEAC@@_4.10 // almost any operation you do on the elements will add more overhead than // reversing the array, if there is any possible optimization, pike will do it // for you. array reversed = reverse(arr); // unless you were going to use for anyways then foreach(reverse( ...)) is // preferable. foreach(reverse(arr);; mixed item) { // do something with item } for(int i=sizeof(arr)-1; i<=0; i--) { // so something with arr[i] } array ascending = sort(users); array descending = reverse(sort(users)); // reverse(sort()) is faster by a magnitude array descending = Array.sort_array(users, lambda(mixed a, mixed b) { return aread(), queue->read() }); } ADT.Queue friends = ADT.Queue("Peter", "Paul", "Mary", "Jim", "Tim"); string this, that; [this, that] = shift2(friends); // this contains Peter, that has Paul, and // friends has Mary, Jim, and Tim ADT.Stack beverages = ADT.Stack(); beverages->set_stack(({ "Dew", "Jolt", "Cola", "Sprite", "Fresca" })); array pair = beverages->pop(2); // implementing pop2 would gain nothing here // pair[0] contains Sprite, pair[1] has Fresca, // and beverages has (Dew, Jolt, Cola) // to be able to shift and pop on the same list use the following: array shift2(ADT.CircularList list) { return ({ list->pop_front(), list->pop_front() }); } array pop2(ADT.CircularList list) { return reverse( ({ list->pop_back(), list->pop_back() }) ); } ADT.CircularList friends = ADT.CircularList( ({"Peter", "Paul", "Mary", "Jim", "Tim"}) ); string this, that; [this, that] = shift2(friends); // this contains Peter, that has Paul, and // friends has Mary, Jim, and Tim ADT.CircularList beverages = ADT.CircularList( ({ "Dew", "Jolt", "Cola", "Sprite", "Fresca" }) ); array pair = pop2(beverates); // pair[0] contains Sprite, pair[1] has Fresca, // and beverages has (Dew, Jolt, Cola) // @@PLEAC@@_4.12 mixed match = search(arr, element); int test(mixed element) { if(sizeof(element)==5) return 1; else return 0; } mixed match = Array.search_array(arr, test); if(match != -1) { // do something with arr[match] } else { // do something else } // another convenient way if you do many tests on the same list, // and you do not care for the position is: if( (multiset)arr[element] ) { // found } else { // not found } // @@PLEAC@@_4.13 array matching=({}); foreach(list;; mixed element) { if(test(element)) matching+=({ element }); } array matching = map(list, test)-({ 0 }); array matching = test(list[*])-({ 0 }); // apply test() on each element in list, collect the results, and remove // results that are 0. // @@PLEAC@@_4.14 // since pike has different types for strings and numbers, ints and floats are // of course sorted numerically // (sort() is destructive, the original array is changed) array(int) unsorted = ...; array(int) sorted = sort(unsorted); // but suppose you want to sort an array of strings by their numeric value then // things get a bit more interresting: array(string) unsorted = ({ "123asdf", "3poiu", "23qwert", "3ayxcv" }); sort((array(int))unsorted, unsorted); // unsorted is now sorted. // @@PLEAC@@_4.15 array unordered; int compare(mixed a, mixed b) { // return comparison of a and b } array ordered = Array.sort_array(unordered, compare); //------------------------------------------------------------- int compute(mixed element) { // return computation from element } array precomputed = map(unordered, compute); sort(precomputed, unordered); // will destructively sort unordered in the same array ordered = unordered; // manner as precomputed. //------------------------------------------------------------- sort(map(unordered, compute), unordered); // without a temp variable sort(compute(unordered[*]), unordered); // using the automap operator // both get compiled to the same code //------------------------------------------------------------- array ordered = sort(employees, lambda(mixed a, mixed b) { return a->name > b->name; } ); //------------------------------------------------------------- foreach(Array.sort_array(employees, lambda(mixed a, mixed b){ return a->name > b->name; }) ;; mixed employee) { write("%s earns $%d\n", employee->name, employee->salary); } //------------------------------------------------------------- array ordered_employees = Array.sort_array(employees, lambda(mixed a, mixed b){ return a->name > b->name; }); foreach(ordered_employees;; mixed employee) { write("%s earns $%d\n", employee->name, employee->salary); } mapping bonus; foreach(ordered_employees;; mixed employee) { // you are not supposed to use the social security number as an id if(bonus[employee->id]) write("%s got a bonus!\n", employee->name); } //------------------------------------------------------------- array sorted = Array.sort_array(employees, lambda(mixed a, mixed b) { if(a->name!=b->name) return (a->name < b->name) return (b->age < a->age); } ); //------------------------------------------------------------- array(array) users = System.get_all_users(); sort(users); // System.get_all_users() returns an array of arrays, with the name as the // first element in each inner array, sort handles multidimensional arrays, so // we can skip creating our own sort function. // if we wanted to sort on something else one could rearrange the array: array user; while(user=System.getpwent()) { users += ({ user[2], user }); } System.endpwent(); sort(users); // now we are sorting by uid. // alternative: array(array) users = System.get_all_users(); sort(users[*][2], users); write(users[*][0]*"\n"); write("\n"); //------------------------------------------------------------- array names; array sorted = Array.sort_array(names, lambda(mixed a, mixed b) { return a[1] < b[1]; } ); // faster: sort(names[*][1], names); sorted=names; //------------------------------------------------------------- array strings; array sorted = Array.sort_array(strings, lambda(mixed a, mixed b) { return sizeof(a) < sizeof(b); } ); // faster: sort(sizeof(strings[*]), strings); sorted=strings; //------------------------------------------------------------- array strings; array temp = map(strings, sizeof); sort(temp, strings); array sorted = strings; //------------------------------------------------------------- array strings; sort(map(strings, sizeof), strings); // pick one sort(sizeof(strings[*]), strings); sorted=strings; //------------------------------------------------------------- array fields; array temp = map(fields, array_sscanf, "%*s%d%*s"); sort(temp, fields); array sorted_fields=fields; //------------------------------------------------------------- sort(array_sscanf(fields[*], "%*s%d%*s"), fields); array sorted_fields=fields; //------------------------------------------------------------- array passwd_lines = (Stdio.read_file("/etc/passwd")/"\n")-({""}); array(array) passwd = passwd_lines[*]/":"; int compare(mixed a, mixed b) { if(a[3]!=b[3]) return (int)a[3]<(int)b[3]; if(a[2]!=b[2]) return (int)a[2]<(int)b[2]; return a[0]push_front(circular->pop_back()); circular->push_back(circular->pop_front()); //------------------------------------------------------------- mixed grab_and_rotate(ADT.CircularList list) { mixed element = list->pop_front(); list->push_back(element); return element; } ADT.CircularList processes = ADT.CircularList( ({ 1, 2, 3, 4, 5 }) ); while(1) { int process = grab_and_rotate(processes); write("Handling process %d\n", process); sleep(1); } // @@PLEAC@@_4.17 array arr; Array.shuffle(arr); // this uses the fisher-yates shuffle //------------------------------------------------------------- // being creative with the algorithm, this is not as memory efficient, // but it shows the utility of multisets. array set_shuffle(array list) { multiset elements=(multiset)list; list=({}); // reset the list while(sizeof(elements)) // while we still have elements left { mixed pick=random(elements); // pick a random element list+=({ pick }); // add it to the new list elements[pick]--; // remove the element we picked } return list; } array list; list=set_shuffle(list); //------------------------------------------------------------- inherit "mjd_permute"; int permutations = factorial(sizeof(list)); array shuffle = list[n2perm(random(permutations)+1, sizeof(list))[*]]; //------------------------------------------------------------- void naive_shuffle(array list) { for(int i=0; iNat = 24; age->Jules = 25; age->Josh = 17; // the traditional way to initialize mappings mapping age = ([ "Nat":24, "Jules":25, "Josh":17 ]); mapping(string:string) food_color = ([ "Apple":"red", "Banana":"yellow", "Lemon":"yellow", "Carrot":"orange" ]); // a index may be of any type mapping any = ([ "1":"a string", 1:"an int", 1.0:"a float" ]); // you may use other types too, but be aware that they are matched by // reference, and not by value. // @@PLEAC@@_5.1 mapping[mixed] = mixed; mapping->string = mixed; //any string that is a valid identifier // food_color as per section 5.0 food_color->Raspberry = "pink"; write("Known foods:\n"); foreach(food_color; string food; ) { write(food+"\n"); } // Lemon // Banana // Apple // Carrot // Raspberry // @@PLEAC@@_5.2 // an undefined value in a mapping gets turned to 0. // assigning 0 as a value is allowed and will not remove the index. // checking for the index will of course return 0 and be interpreted as false. // to check if the index is really there, use zero_type() if(!zero_type(mapping->index)) { // it exists } else { // it doesn't } // food_color as per section 5.0 foreach( ({ "Banana", "Milk" }) ;; string name) { if(!zero_type(food_color[name])) write("%s is a food.\n", name); else write("%s is a drink.\n", name); } // Banana is a food. // Milk is a drink. // --------------------------------------------------------- mapping age = ([ "Toddler":3, "Unborn":0, "Newborn":0.0, "Phantasm":UNDEFINED ]); foreach( ({ "Toddler", "Unborn", "Newborn", "Phantasm", "Relic" });; string thing) { write(thing+":"); if(!zero_type(age[thing])) write(" Exists"); if(age[thing]) write(" True"); write("\n"); } // Toddler: Exists True // Unborn: Exists // Newborn: Exists True // Phantasm: Exists // Relic: // age->Toddler exists, because zero_type() is only true if the index is not in // the mapping. it is true because the value is not 0. // age->Unborn exists, but is false because 0 is false // age->Newborn exists and is true, because 0.0 is not false // age->Phantasm exists and is false, like Unborn // age->Relic does not exist // we can not test for defined. UNDEFINED is a special value used internally by // the compiler. it gets converted to 0 as soon as it is assigned in a mapping // however we can create something equivalent that can be treated like any // other value, except that it is false: class Nil { // this is a minimal example. // a more complete one would also handle casting int `!() {return 1;} string _sprintf() {return "Nil";} // we could have this function externally, but this is more convenient int defined(mixed var) { return !zero_type(var) && var!=this; } } Nil NIL = Nil(); // create an instance so we can use it function defined = NIL->defined; // just for symetry mapping age = ([ "Toddler":3, "Unborn":0, "Phantasm":NIL ]); foreach( ({ "Toddler", "Unborn", "Phantasm", "Relic" });; string thing) { write(thing+":"); if(!zero_type(age[thing])) write(" Exists"); if(defined(age[thing])) write(" Defined"); if(age[thing]) write(" True"); write("\n"); } // Toddler: Exists Defined True // Unborn: Exists Defined // Phantasm: Exists // Relic: // age->Toddler exists, because zero_type() is only true if the index is not in // the mapping. it is defined because it exists an is not NIL. // it is true because the value is not 0. // age->Unborn exists, and is defined, but is false because 0 is false // age->Phantasm exists, is not defined because it is NIL, // it is not true because NIL is false. // age->Relic does not exist, it is not defined even though it is not NIL // because it doesn't exist. it is also not true, because it does not exist. // ---------------------------------------------------- mapping size = ([]); string filename; while(filename = Stdio.stdin->gets()) { filename -= "\n"; if(size[filename]) // wrong continue; if(!zero_type(size[filename])) // right continue object stat = file_stat(filename) if(stat) size[filename] = stat->size; else size[filename] = -1; // since sizes can't be negative, this will do. // if -1 is a valid value, use NIL } // @@PLEAC@@_5.3 // users occasionally may get the idea that mapping[index]=0; may remove index // from mapping. the normal way to remove a index from a mapping is to // subtract: mapping -= ([ index:0 ]); the following shall demonstrate the // difference between subtracting a index and assigning 0 to it. // food_color as per section 5.0 void print_foods() { write("Foods:%{ %s%}\n", indices(food_color)); write("Values: "); foreach(food_color; string food; string color) { if(color) write(color+" "); else write("(no value) "); } write("\n"); } write("Initially:\n"); print_foods(); write("\nWith Banana set to 0\n"); food_color->Banana = 0; print_foods(); write("\nWith Banana deleted\n"); food_color -= ([ "Banana":"the value is irrelevant" ]); print_foods(); // Initially: // Foods: Lemon Banana Apple Carrot // Values: yellow yellow red orange // // With Banana set to 0 // Foods: Banana Lemon Apple Carrot // Values: (no value) yellow red orange // // With Banana deleted // Foods: Lemon Carrot Apple // Values: yellow orange red // you can also subtract multiple indices: food_color -= ([ "Banana":0, "Apple":0, "Cabbage":0 ]); // note that subtracting a mapping from another creates a new mapping. // thus any references you have to a mapping will be broken. // in most cases this is what you want anyways. if it is not, you can also // remove indices using m_delete(); m_delete(food_color, "Banana"); // @@PLEAC@@_5.4 foreach( mapping; type index; type value) { //do something with index and value } // food_color as per 5.0 foreach(food_color; string food; string color) { write("%s is %s.\n", food, color); } // Banana is yellow. // Lemon is yellow. // Carrot is orange. // Apple is red. foreach(sort(indices(food_color));; string food) { write("%s is %s.\n", food, food_color[food]); } // Apple is red. // Banana is yellow. // Carrot is orange. // Lemon is yellow. // since pike does not have any equivalent to each() its problems do not apply // @@INCLUDE@@ include/pike/ch05/countfrom // @@PLEAC@@_5.5 // perls problems and solutions do not apply to pike // here are a few ways to print a mapping: // food_color as per 5.0 // debugging style write("%O\n", food_color); // ([ /* 4 elements */ // "Apple": "red", // "Banana": "yellow", // "Carrot": "orange", // "Lemon": "yellow" // ]) // one element at a time: foreach(food_color; string food; string color) { write("%s is %s\n", food, color); } // Lemon is yellow // Carrot is orange // Banana is yellow // Apple is red // with the help of an array write("%{%s is %s\n%}", sort((array)food_color)); // Apple is red // Banana is yellow // Carrot is orange // Lemon is yellow // @@PLEAC@@_5.6 // for this we need to first create an OrderedMapping class. // work for this is in progress // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_5.7 mapping(string:array(string)) ttys = ([]); object pipe = Stdio.File(); Process.create_process(({ "who" }), ([ "stdout":pipe->pipe() ])); foreach(pipe->line_iterator();; string line) { array tty=(line/" ")-({ "" }); if(!ttys[tty[0]]) ttys[tty[0]] = ({ tty[1] }); else ttys[tty[0]] += ({ tty[1] }); } foreach(sort(indices(ttys));; string user) { write("%s: %{%s %}\n", user, ttys[user]); } foreach(sort(indices(ttys));; string user) { write("%s: %d ttys.\n", user, sizeof(ttys[user])); foreach(ttys[user];; string tty) { object stat = file_stat("/dev/"+tty); string user; if(stat) user = getpwuid(stat->uid)[0]; else user = "(not available)"; write("\t%s (owned by %s)\n", tty, user); } } mapping multihash_delete(mapping hash, mixed key, mixed value) { if(arrayp(hash[key])) hash[key]-=({ value }); if(!sizeof(hash[key])) m_delete(hash, key); return hash; } // @@PLEAC@@_5.8 // search for a value in a mapping mapping lookup; mixed value; mixed key = search(lookup, value); // transposing: (this will break if there are multiple occurances of a value) mapping lookup; mapping reverse = mkmapping(values(lookup), indices(lookup)); //---------------------------------------------------------- mapping surname = ([ "Mickey":"Mantle", "Babe":"Ruth" ]); write("%s\n", search(surname, "Mantle")); // Mikey // with a transposed mapping (only worth doing if you'd have to search a lot) mapping first_name = mkmapping(values(surname), indices(surname)); write("%s\n", first_name->Mantle); // Mikey //---------------------------------------------------------- // @@INCLUDE@@ include/pike/ch05/foodfind //---------------------------------------------------------- // food_color as per 5.0 mapping foods_with_color = ([]); foreach(food_color; string food; string color) { if(!foods_with_color[color]) foods_with_color[color] = ({ food }); else foods_with_color[color] += ({ food }); } write("%{%s %}were yellow foods.\n", foods_with_color->yellow); // @@PLEAC@@_5.9 mapping hash; foreach(sort(indices(hash));; mixed key) { mixed value = hash[key]; // do something with key, value } foreach(sort(indices(food_color));; string food) { write("%s is %s.\n", food, food_color[food]); } array foods = indices(food_color); sort(sizeof(values(food_color)[*]), foods); foreach(foods;; string food) { write("%s is %s.\n", food, food_color[food]); } // @@PLEAC@@_5.10 // the natural way to merge two mappings is to use + or | // for mappings both operations are equivalent. mapping A, B; mapping merged = A + B; mapping merged = A | B; // if an index is in both mappings, the value will be taken from the second // one. mapping drink_color = ([ "Milk":"white", "Tomato juice":"red" ]); mapping ingested_color = drink_color + food_color; // Result: ([ /* 6 elements */ // "Apple": "red", // "Banana": "yellow", // "Carrot": "orange", // "Lemon": "yellow", // "Milk": "white", // "Tomato juice": "red" // ]) // @@PLEAC@@_5.11 // create a mapping where indices are in both A and B mapping both = A & B; // in A or B, but not in both mapping one = A ^ B; // in A, but not in B mapping exA = A - B; mapping citrus_color = ([ "Lemon":"yellow", "Orange":"orange", "Lime":"green" ]); array non_citrus = indices(food_color - citrus_color); // @@PLEAC@@_5.12 // this problem does not apply to pike // any value may be used as an index, if you get an object reference from // anywhere, if will work, if the index in the mapping is actually the same // object. // however note that the same value is not the same reference: array a = ({ 1,2 }); array b = ({ 1,2 }); mapping m = ([ a:"a" ]); m[b]; // Result: 0 (b will not be found.) m[b]="b"; m; // Result: ([ ({ 1, 2 }): "a", // ({ 1, 2 }): "b" // ]) // this looks as if the mapping has the same index twice // but since they are references this is not the case. // @@PLEAC@@_5.13 // this problem does not apply to pike // pike uses a smart preallocation algorythm that will avoid the need to // allocate memory everytime an element is added // @@PLEAC@@_5.14 mapping count = ([]); foreach(ARRAY;; mixed element) { count[element]++; } // @@PLEAC@@_5.15 mapping father = ([ "Cain":"Adam", "Abel":"Adam", "Seth":"Adam", "Enoch":"Cain", "Irad":"Enoch", "Mehujael":"Irad", "Methusael":"Mehujael", "Lamech":"Methusael", "Jabal":"Lamech", "Jubal":"Lamech", "Tubalcain":"Lamech", "Enos":"Seth" ]); foreach(Stdio.stdin;; string name) { do { write("%s ", name); } while(name = father[name]); write("\n"); } mapping children = ([]); foreach(father; string k; string v) { if(!children[v]) children[v] = ({ k }); else children[v] += ({ k }); } foreach(Stdio.stdin;; string name) { write("%s begat %s.\n", name, (children[name]||({ "nobody" }))*", "); } //----------------------------------------------------- mapping includes = ([]); foreach(files, string file) { string F = Stdio.read_file(file); if(!F) werror("Couldn't read %s; skipping.\n", file); foreach(F/"\n";; string line) { array included = array_sscanf(line, "%*[ \t]#include%*[ \t]%*[<\"]%s%*[>\"]%*[ \t]"); if(sizeof(included)) { if(!includes[included[0]]) includes[included[0]] = ({ file }); else includes[included[0]] += ({ file }); } } } //----------------------------------------------------- array uniq = `|( @values(includes) ); array include_free = sort( uniq - indices(includes) ); // values(includes) is an array of arrays. // @ splices values(includes) as arguments into `|() // `|() is a function that represents the | operator: a|b|c is `|(a,b,c) // | on arrays creates a new array with elements in a or b // the resulting array is unique as long as each array only has unique elements // from the uniq array we remove all those that are used as indices in the // includes mapping. // at last we sort the remaining list. // @@PLEAC@@_5.16 // @@INCLUDE@@ include/pike/ch05/dutree // @@PLEAC@@_6.1 // @@INCOMPLETE@@ // by Scott McCoy [tag at cpan.org] /* Copying and Substituting Simultaniously */ dst = Regexp("this")->replace(src, "that"); /* Copying and replacing...Same thing */ dst = Regexp("this")->replace(src, "that"); /* Strip to basename */ // Note this is best done with string manipulation functions, regexp here is // wasteful. Same is true in perl. dst = Regexp("^.*/")->replace(argv[0], ""); string capword = Regexp("[a-z]+")->replace ("foo.bar", lambda (string c) { c[0] = upper_case(c[0]); return c; } ); /* For the captured substitution, I'll have to figure out PCRE. */ // @@PLEAC@@_7.0 // if you are going to read the whole file, the most common way is to use // Stdio.read_file() string INPUT = Stdio.read_file("/usr/local/widgets/data"); if(!INPUT) { werror("Couldn't open /usr/local/widgets/data for reading\n"); exit(1); } foreach(INPUT/"\n";; string line) { if(search(line, "blue")!=-1) write(line+"\n"); } // if you need more control over the process you can get a filehandle with // Stdio.File() Stdio.File INPUT = Stdio.File("/usr/local/widgets/data", "r"); if(!INPUT) { werror("Couldn't open /usr/local/widgets/data for reading\n"); exit(1); } foreach(INPUT->line_iterator();; string line) { if(search(line, "blue")!=-1) write(line+"\n"); } INPUT->close(); //--------------------------------------------------------- foreach(Stdio.stdin;; string line) // reads from STDIN { if(!sizeof(array_sscanf(line, "%*s%d"))) werror("No digit found.\n"); // writes to STDERR write("Read: %s\n", line); // writes ot STDOUT } Stdio.stdout->close() || werror("couldn't close STDOUT\n") && exit(1); // just as with Stdio.read_file(), there are convenience functions for writing: // Stdio.write_file() and Stdio.append_file() Stdio.File logfile = Stdio.File("/tmp/log", "w"); // access modes are "r" for reading, "w" for writing and "a" for append // default mode is "rw" // to read a line you may use Stdio.File()->gets() or get a line_iterator() and // read lines from it. object LOGFILE = logfile->line_iterator(); do { string line=LOGFILE->value(); } while(LOGFILE->next()) logfile->close(); // or use foreach as shown above. // write() is actually a shortcut for Stdio.stdout->write() // you could get yourself a different shortcut by assigning that to a variable: function write = logfile->write; // switch to LOGFILE for write(); write("Countdown initiated ...\n"); write = Stdio.stdout->write; // return to stdout write("You have 30 seconds to reach minimum safety distance.\n"); // Stdio.File is unbuffered. a buffered version is provided by Stdio.FILE // @@PLEAC@@_7.1 // use Stdio.read_file(), Stdio.write_file() and Stdio.append_file() for // convenience, or Stdio.File for precision and to get a filehandle. string path; // open file for reading string file = Stdio.read_file(path); Stdio.File file = Stdio.File(path, "r"); // open file for writing, create new file if needed, or else truncate old file Stdio.write_file(path, "content"); Stdio.File file = Stdio.File(path, "wc"); // same with setting access permissions Stdio.write_file(path, "content", 0600); Stdio.File file = Stdio.File(path, "wc", 0600); // open file for writing, create new file, file must not exist if(!file_stat(path)) Stdio.write_file(path, "content"); Stdio.File file = Stdio.File(path, "wcx"); if(!file_stat(path)) Stdio.write_file(path, "content", 0600); Stdio.File file = Stdio.File(path, "wcx", 0600); // open file for appending, create if necessary Stdio.append_file(path, "content"); Stdio.File file = Stdio.File(path, "wac"); Stdio.append_file(path, "content", 0600); Stdio.File file = Stdio.File(path, "wac", 0600); // open file for appending, file must exist Stdio.File file = Stdio.File(path, "wacx"); // open file for update, file must exist string file = Stdio.read_file(path); string updated = file+"foo" // update contents of file Stdio.write_file(path, updated); Stdio.File file = Stdio.File(path); // this is the default operation // open file for update, file must not exist Stdio.File file = Stdio.File(path, "rwcx"); // @@PLEAC@@_7.2 // since the filename is contained in a string, this problem does not apply // @@PLEAC@@_7.3 string filename; if(filename[0] == "~") { string user, path, home; [ user, path ] = array_sscanf(filename, "~%[^/]%s"); if(user == "") home = getenv("HOME") || getenv("LOGDIR") || getpwuid(geteuid())[5]; else home = getpwnam(user)[5]; filename = home+path; } // @@PLEAC@@_7.4 string path = "/tmp/fooo"; mixed error = catch { Stdio.File file = Stdio.File(path, "r"); }; if(error) { werror("Couldn't open %s for reading:\n", path); werror(error[0]); } // Couldn't open /tmp/fooo for reading: // Failed to open "/tmp/fooo" mode "r" : No such file or directory // @@PLEAC@@_7.5 Stdio.File fh; string name; do { name = "/tmp/"+MIME.encode_base64(random_string(10)); fh = Stdio.File(name, "rwcx"); } while(!fh) atexit(lambda(){ fh->close(); rm(name); }); // if you don't really need the file to be on disk (or if /tmp is a ramdisk) // but you need an object that behaves like a file, then use Stdio.FakeFile fh = Stdio.FakeFile(); // and use fh like any other filehandle. // @@PLEAC@@_7.6 // since the usual way to handle files is to read them into a string, then just // assign your data to a string and work from there: string data = "your data goes here"; // or for convenient multiline data: string data = #"your data goes here and here and ends here"; // or use Stdio.FakeFile for a Stdio.File compatible interface // see 7.5 //----------------------------------------------------------------- object stat = file_stat(__FILE__); int raw_time = stat->ctime; int size = stat->size; int kilosize = size/1024; write("

Script size is %dk\n", kilosize); write("

Last script update: %s\n", Calendar.Second(raw_time)->format_nicez()); // @@PLEAC@@_8.2 // Does not check file existence but return a correct value with empty (size=0) files // Does count paragraphs correctly (does not count empty lines (\n\n)) int main(int argc, array(string) argv) { int count=0; object f = Stdio.FILE(argv[1]); foreach(f->line_iterator(f); int number; string paragraph) { count++; // write(number+" "+paragraph+"\n"); } write("number of paragraphs= "+count+"\n"); return 0; } // @@PLEAC@@_9.0 Stdio.Stat entry; entry = file_stat("/bin/vi"); entry = file_stat("/usr/bin"); entry = file_stat(argv[1]); // ------------ Stdio.Stat entry; int ctime, size; entry = file_stat("/bin/vi"); ctime = entry->ctime; size = entry->size; // ------------ // A routine detecting whether a file is a 'text' file doesn't appear // to exist, so have implemented the following [crude] function(s) // which search for a LF / NEWLINE in the file: // Usable with any file int(0..1) containsText(Stdio.File file) { string c; while ((c = file->read(1)) != NULL) { (c == NEWLINE) && return 1; } return 0; } // Alternate version, expects a buffered file [usually containing text] int(0..1) containsText(Stdio.FILE file) { int c; while ((c = file->getchar()) != EOF) { (c == LF) && return 1; } return 0; } // Yet another alternative - this time we cheat and use the *NIX 'file' // utility :) ! int(0..1) isTextFile(string filename) { return chop(Process.popen("file -bN " + filename), 1) == "ASCII text"; } // ---- containsText(Stdio.File(argv[1])) || write("File %s doesn't have any text in it\n", argv[1]); isTextFile(argv[1]) || write("File %s doesn't have any text in it\n", argv[1]); // ------------ Filesystem.Traversion dirtree = Filesystem.Traversion("/usr/bin"); foreach(dirtree; string dir; string file) { write("Inside %s is something called %s\n", chop(dir, 1), file); } // @@PLEAC@@_9.1 string filename = "example.txt"; Stdio.Stat fs = file_stat(filename); int readtime = fs->atime, writetime = fs->mtime; System.utime(filename, readtime, writetime); // ---------------------------- constant SECONDS_PER_DAY = 60 * 60 * 24; string filename = "example.txt"; Stdio.Stat fs = file_stat(filename); int atime = fs->atime, mtime = fs->mtime; atime -= 7 * SECONDS_PER_DAY; mtime -= 7 * SECONDS_PER_DAY; System.utime(filename, atime, mtime); // ---------------------------- argc != 1 || die("usage: " + argv[0] + " filename"); Stdio.Stat fs = file_stat(argv[1]); int atime = fs->atime, mtime = fs->mtime; Process.system(getenv("EDITOR") || "vi" + " " + argv[1]); mixed result = catch { System.utime(argv[1], atime, mtime); }; (result == OK) || write("Error updating timestamp on file, %s!\n", argv[1]); // @@PLEAC@@_9.2 string filename = "..."; rm(filename) || write("Can't delete, %s!\n", filename); // ------------ int(0..1) rmAll(array(string) filelist) { mixed result = catch { foreach(filelist, string filename) { rm(filename) || throw(PROBLEM); } }; return result == OK; } // ---- array(string) filelist = ({"/tmp/x", "/tmp/y", "/tmp/z"}); rmAll(filelist) || write("Can't delete all files in array!\n"); // ---------------------------- void die(string msg, void|int(1..256) rc) { werror(msg + NEWLINE); exit(rc ? rc : PROBLEM); } // ---- string filename = "..."; rm(filename) || die("Can't delete " + filename); // ---------------------------- array(string) filelist = ({"/tmp/x", "/tmp/y", "/tmp/z"}); int deleted, count = sizeof(filelist); foreach(filelist, string filename) { rm(filename) && ++deleted; } (deleted == count) || write("Could only delete %d of %d files\n", deleted, count); // @@PLEAC@@_9.3 string oldfile = "/tmp/old", newfile = "/tmp/new"; Stdio.cp(oldfile, newfile) || write("Error copying file\n"); // ---------------------------- string oldfile = "/tmp/old", newfile = "/tmp/new"; mixed result = catch { Stdio.write_file(newfile, Stdio.read_file(oldfile)); }; (result == OK) || write("Problem copying file %s to file %s\n", oldfile, newfile); // ---------------------------- // Note: This is a cross between, 'Process.system', which displays // output on stdout, and, 'Process.popen', which does not display // output [it returns it as a string] but does not return the status // code int system(string cmd) { Stdio.File fout = Stdio.File(), ferr = Stdio.File(); Stdio.File pout = fout->pipe(Stdio.PROP_IPC), perr = ferr->pipe(Stdio.PROP_IPC); int rc = Process.spawn(cmd, 0, pout, perr)->wait(); pout->close(); destruct(pout); fout->close(); destruct(fout); perr->close(); destruct(perr); ferr->close(); destruct(ferr); return rc; } int(0..1) unixFileCopy(string oldfile, string newfile) { string cmd = "cp --force --reply=yes " + oldfile + " " + newfile; return system(cmd) == OK; } int(0..1) vmsFileCopy(string oldfile, string newfile) { string cmd = "copy " + oldfile + " " + newfile; return system(cmd) == OK; } // ---- string oldfile = "/tmp/old", newfile = "/tmp/new"; unixFileCopy(oldfile, newfile) || write("Problem copying file %s to file %s\n", oldfile, newfile); // ---------------------------- string oldfile = "/tmp/old", newfile = "/tmp/new"; mv(oldfile, newfile) || write("Problem moving / renaming file %s to file %s\n", oldfile, newfile); // @@PLEAC@@_9.4 mapping(array(int):int) seen = ([]); // ---- void do_my_thing(string filename) { Stdio.Stat fs = file_stat(filename); array(int) arr = aggregate(fs->inode, fs->dev); // Could do this [apply a lambda assigned to variable 'p']: // // ... || (p(arr), seen[arr] = 1); // // function p = lambda(array(int) arr) { ... }; // // to process a file that has not previously been seen (seen[arr] && (seen[arr] += 1)) || (seen[arr] = 1); } // ---------------------------- constant SEP = ":"; mapping(array(int):string) seen = ([]); // ---- array(string) files = ({"f1.txt", "f2.txt", "f3.txt"}); foreach(files, string filename) { Stdio.Stat fs = file_stat(filename); array(int) arr = aggregate(fs->inode, fs->dev); (seen[arr] && (seen[arr] += (SEP + filename))) || (seen[arr] = filename); } // ---- array(array(int)) idxarr = indices(seen); sort(idxarr); foreach(idxarr, array(int) inodev) { foreach(seen[inodev] / SEP, string filename) { // ... do stuff with each filename ... write("%s\n", filename); } } // @@PLEAC@@_9.5 string dirname = "..."; array(string) DIR = get_dir(dirname); foreach(DIR, string filename) { string path = dirname + "/" + filename; // ... do something with 'path' ... } // ---------------------------- string dirname = "/usr/local/bin"; int|array(string) DIR = get_dir(dirname); DIR || die("Can't open " + dirname); write("Text files in %s are:\n", dirname); foreach(DIR, string filename) { string path = dirname + "/" + filename; // 'isTextFile' defined in an earlier section isTextFile(path) && write("%s\n", filename); } // ---------------------------- // '.' and '..' don't show up in a 'get_dir'-generated array // ---------------------------- array(string) plain_files(string dirname) { // 'filter' procedure function fp = lambda(string filename, string dirname) { // 'isTextFile' defined in an earlier section return !has_prefix(filename, ".") && isTextFile(dirname + "/" + filename); }; // 'map' procedure function mp = lambda(string filename, string dirname) { return dirname + "/" + filename; }; array(string) paths = map(filter(get_dir(dirname), fp, dirname), mp, dirname); sort(paths); return paths; } // @@PLEAC@@_9.6 // A 'glob' workalike that filters using regular expressions // // Note: Pike offers many non-regexp-based string pattern matching // functions [e.g. 'has_prefix' and other 'has_...' functions, // 'search', etc]. These are preferable in many situations as they are // much faster than regexprs. However, code shown here mostly uses // regexprs in order to better match the Perl examples // int(0..1)|array(string) grep(string regexp, string|array(string) arr) { if (stringp(arr)) return Regexp.match(regexp, arr); if (arrayp(arr)) { function fp = lambda(string filename, string regexp) { return Regexp.match(regexp, filename); }; return filter(arr, fp, regexp); } return 0; } // ---------------------------- string dirname = "..."; int|array(string) filenames = glob("*.c", get_dir(dirname)); // ------------ string dirname = "..."; int|array(string) filenames = grep("\.c$", get_dir(dirname)); // ------------ string dirname = "..."; int|array(string) filenames = grep("\.[CHch]$", get_dir(dirname)); // ---------------------------- string dirname = "..."; int|array(string) dir = get_dir(dirname); dir || die("Couldn't open " + dirname + " for reading"); // // Note: Pike arrays are immutable, so we use a mapping to emulate // mutable arrays by using a numeric index as the key :) // mapping(int:string) files = ([]); int idx = -1; string path; foreach(dir, string file) { if (!grep("\.[CHch]$", file)) continue; path = dirname + "/" + file; isTextFile(path) && (files[++idx] = path); } // // Note: Traverse a mapping-based, emulated array in index order: // // foreach(sort(indices(files)), int i) // { // write("%d -> %s\n", i, files[i]); // } // // @@PLEAC@@_9.7 // // Routine inspired by library function, 'Stdio.recursive_rm'. A little // extra code helped make it more generally useful // void|mixed process_directory(string path, function(string, mixed ... : void|mixed) op, mixed ... extra_args) { Stdio.Stat file = file_stat(path, 1); if (!file) return 0; if (file->isdir) if (array(string) files = get_dir(path)) foreach(files, string file) process_directory(path + "/" + file, op, @extra_args); return op(path, @extra_args); } // ---------------------------- array(string) dirlist = ({ "/tmp/d1", "/tmp/d2", "/tmp/d3" }); // Do something with each directory in the list foreach(dirlist, string dir) { // Delete directory [if empty] -> rm(dir); // Make it the 'current directory' -> cd(dir); // Get list of files it contains -> array(string) filelist = get_dir(dir); // Get directory metadata -> Stdio.Stat ds = file_stat(dir); } // ------------ array(string) dirlist = ({ "/tmp/d1", "/tmp/d2", "/tmp/d3" }); function pf = lambda(string path) { // ... do something to the file or directory ... write("%s\n", path); }; // For each directory in the list ... foreach(dirlist, string dir) { int|array(string) filelist = get_dir(dir); if (!filelist) { write("%s does not exist\n", dir); continue; } if (sizeof(filelist) == 0) { write("%s is empty\n", dir); continue; } // For each file / directory in the directory ... foreach(filelist, string filename) { // Apply function to process the file / directory pf(dir + "/" + filename); } } // ------------ // Special steps need to be taken in above routines to distinguish // between files and directories. Easiest to abstract out directory // traversal into a single routine [so allowing for recursive traversal // of entire tree], and have it apply a lambda to each file array(string) dirlist = ({ "/tmp/d1", "/tmp/d2", "/tmp/d3" }); function pf = lambda(string path) { // ... do something to the file or directory ... write("%s\n", path); }; // For each directory in the list ... foreach(dirlist, string dir) { process_directory(dir, pf); } // ---------------------------- void accum_filesize(string path, array(int) accum) { int|Stdio.Stat fs = file_stat(path); // Accumulate size only if it is a regular file (fs && fs->isreg) && (accum[0] += fs->size); } // ------------ // Verify arguments ... argc == 2 || die("usage: " + argv[0] + " dir"); Stdio.Stat fs; string dir = argv[1]; ((fs = file_stat(dir)) && fs->isdir) || die(dir + " does not exist / not a directory"); // Collect data [use an array to accumulate results] array(int) dirsize = ({0}); process_directory(dir, accum_filesize, dirsize); // Report results write("%s contains %d bytes\n", dir, dirsize[0]); // ---------------------------- void biggest_file(string path, array(mixed) biggest) { int|Stdio.Stat fs = file_stat(path); if (fs && fs->isreg && biggest[1] < fs->size) { biggest[0] = path; biggest[1] = fs->size; } } // ------------ // Verify arguments ... argc == 2 || die("usage: " + argv[0] + " dir"); Stdio.Stat fs; string dir = argv[1]; ((fs = file_stat(dir)) && fs->isdir) || die(dir + " does not exist / not a directory"); // Collect data [use an array to store results] array(mixed) biggest = ({"", 0}); process_directory(dir, biggest_file, biggest); // Report results write("Biggest file is %s containing %d bytes\n", biggest[0], biggest[1]); // ---------------------------- void youngest_file(string path, array(mixed) youngest) { int|Stdio.Stat fs = file_stat(path); if (fs && fs->isreg && youngest[1] > fs->ctime) { youngest[0] = path; youngest[1] = fs->ctime; } } // ------------ // Verify arguments ... argc == 2 || die("usage: " + argv[0] + " dir"); Stdio.Stat fs; string dir = argv[1]; ((fs = file_stat(dir)) && fs->isdir) || die(dir + " does not exist / not a directory"); // Collect data [use an array to store results] array(mixed) youngest = ({"", Int.NATIVE_MAX}); process_directory(dir, youngest_file, youngest); // Report results write("Youngest file is %s dating %s\n", youngest[0], ctime(youngest[1])); // ---------------------------- void print_name_if_dir(string path) { int|Stdio.Stat fs = file_stat(path); if (fs && fs->isdir) write("%s\n", path); } // ------------ // Verify arguments ... argc == 2 || die("usage: " + argv[0] + " dir"); Stdio.Stat fs; string dir = argv[1]; ((fs = file_stat(dir)) && fs->isdir) || die(dir + " does not exist / not a directory"); // Print directory names process_directory(dir, print_name_if_dir); // @@PLEAC@@_9.8 // Easy way - recommended int(0..1) rmTree(string dirname) { return Stdio.recursive_rm(dirname); } // ---- string dirtree = "/tmp/dirtree"; rmTree(dirtree) || write("Problem removing directory tree %s\n", dirtree); // ---------------------------- // Another way, but unnecessary - probably for customised deletions only int(0..1) rmTree(string dirname) { return process_directory(dirname, rm); } // ---- string dirtree = "/tmp/dirtree"; rmTree(dirtree) || write("Problem removing directory tree %s\n", dirtree); // @@PLEAC@@_9.9 // A list of file names array(string) names = ({"f1.txt", "f2.txt", "f3.txt"}); // Dynamically assigned 'rename' procedure - can be reassigned at any time function rename = lambda(string name) { return replace(name, ".txt", ".text"); }; // Process all files foreach(names, string name) { // Generate new name from existing name by applying 'rename' procedure string newname = rename(name); // Perform actual rename task on file mv(name, newname) || write("Could not rename %s to %s\n", name, newname); } // ---------------------------- // Slightly different to the Perl example, though it does use regexp // and intent is roughly the same. // // pike SCRIPTNAME '\.txt$' '.text' f1.txt f2.txt df3.txg // // f1.txt -> f1.text // f2.txt -> f2.text // df3.txg -> df3.txg [no change] // argc > 2 || die("usage: " + argv[0] + " expr repl files..."); string expr = argv[1], repl = argv[2]; foreach(argv[3..], string name) { string newname = Regexp.replace(expr, name, repl); if (!equal(name, newname)) mv(name, newname) || write("Could not rename %s to %s\n", name, newname); } // @@PLEAC@@_9.10 string file_extension(string filename, void|string separator) { return (filename / (separator || "."))[-1]; } mapping(string:string) file_parse(string path) { return mkmapping(({"dirname", "basename", "extension"}), ({dirname(path), basename(path), file_extension(basename(path))})); } // ---------------------------- string path = "/tmp/dirtree/s/s1/s1.txt"; // ---- string dir = dirname(path); string base = basename(path); mapping(string:string) pm = file_parse(path); write("%s\n", pm["dirname"]); write("%s\n", pm["basename"]); write("%s\n", pm["extension"]); // ------------ string path = "/usr/lib/libc.a"; // ---- string dir = dirname(path); string base = basename(path); write("dir is %s, file is %s\n", dir, base); // ------------ string path = "/usr/lib/libc.a"; // ---- mapping(string:string) pm = file_parse(path); write("dir is %s, name is %s, extension is %s\n", pm["dirname"], pm["basename"], "." + pm["extension"]); // ---------------------------- // Handle as a general purpose parse task string path = "Hard%20Drive:System%20Folder:README.txt"; // ---- mapping(string:string) pm = mkmapping(({"drive", "folder", "filename"}), replace(path, "%20", " ") / ":"), fm = mkmapping(({"name", "extension"}), pm["filename"] / "."); write("dir is %s, name is %s, extension is %s\n", pm["drive"] + ":" + pm["folder"], fm["name"], "." + fm["extension"]); // ---------------------------- // See implementation for 'file_extension' function above // @@PLEAC@@_9.11 @@INCOMPLETE@@ @@INCOMPLETE@@ // @@PLEAC@@_9.12 @@INCOMPLETE@@ @@INCOMPLETE@@ // @@PLEAC@@_10.0 // Here, in this simple example, 'greeted', is used as a 'global' // variable. In a more complex program, however, this would not be // the case [subsequent sections exlain why] int greeted; // ---- void hello() { write("hi there!, this procedure has been called %d times\n", ++greeted); } int how_many_greetings() { return greeted; } // ------------ int main() { hello(); int greetings = how_many_greetings(); write("bye there!, there have been %d greetings so far\n", greetings); } // ---------------------------- // Alternate means of defining functions [could, optionally, have also // included type information in 'function' declaration]; could also // have been done within scope of 'main' int greeted; // ---- function hello = lambda() { write("hi there!, this procedure has been called %d times\n", ++greeted); }; function how_many_greetings = lambda() { return greeted; }; // ------------ int main() { hello(); int greetings = how_many_greetings(); write("bye there!, there have been %d greetings so far\n", greetings); } // @@PLEAC@@_10.1 // Subroutine parameters are named, that is, access to these items from // within a function is reliant on their being named in the parameter // list [together with mandatory type information], something which is // in line with many other commonly-used languages float hypotenuse(float side1, float side2) { // Arguments passed to this function are accessable as, 'side1', // and 'side2', respectively, and each is expected to be a 'float' // type return side1 * side1 + side2 * side2; } // ---- // 'side1' -> 3.0 // 'side2' -> 4.0 float diag = hypotenuse(3.0, 4.0); // ------------ // However, Pike also allows parameters [and return types where applicable]: // * To have one of a set of types [see (1)] // * To have a generic type [see (2)] // * To be optional, in which case any arguments are packaged as an // array, and array notation needed to access each item [see (3)] // (1). Here the function will accept either 'int' or 'float' // arguments, and perform runtime type checking to identify what is // supplied float hypotenuse(int|float side1, int|float side2) { // If 'int' arguments passed. convert to 'float' float s1 = intp(side1) ? (float) side1 : side1; float s2 = intp(side2) ? (float) side2 : side2; return s1 * s1 + s2 * s2; } // ---- // Both are legal calls float diag = hypotenuse(3.0, 4.0); float diag = hypotenuse(3, 4); // ------------ // (2). Here the function still expects to be called with two arguments // but each may be of *any* type [admittedly a very contrived example // of little utility except for illustrative value]. Such a function // is almost entirely reliant on careful runtime type checking if it // is to behave reliably float hypotenuse(mixed side1, mixed side2) { if (stringp(side1)) { ... } if (arrayp(side1)) { ... } if (objectp(side1)) { ... } ... } // ---- // All are legal calls float diag = hypotenuse(3.0, 4.0); float diag = hypotenuse(3, 4); float diag = hypotenuse("3", "4"); float diag = hypotenuse(({3}), ({4})); // ------------ // (3). Here, the function is defined to accept two, mandatory // parameters [still accessable via name], then a set of zero or more // optional parameters, which are accessable within the function body // via an array [the placeholder, 'args', represents an array of zero // or more elements each corresponding to one of the passed arguments float hypotenuse(float side1, mixed side2, mixed ... args) { // Mandatory parameters still accessable as usual ... side1 ... side2 ... // Total number of arguments passed to function determinable via: int total_passed_args = query_num_arg(); // 'args' contains all optional arguments: 0 - N int optional_args = sizeof(args); // Process variable arguments ... foreach(args, mixed arg) { ... if (strinp(arg)) { ... } } ... } // ---- // All are legal calls float diag = hypotenuse(3.0, 4.0); float diag = hypotenuse(3.0, 4.0, "a"); float diag = hypotenuse(3.0, 4.0, lambda(){ return 5; }, "fff"); float diag = hypotenuse(3.0, 4.0, 1, "x", ({ 6, 7, 9 })); // ---------------------------- // Modifies copy array(int|float) int_all(array(int|float) arr) { array(int|float) retarr = copy_value(arr); int i; for(int i; i < sizeof(retarr); ++i) { retarr[i] = (int) arr[i]; } return retarr; } // Modifies original array(int|float) trunc_all(array(int|float) arr) { int i; for(int i; i < sizeof(arr); ++i) { arr[i] = (int) arr[i]; } return arr; } // ---- array(int|float) nums = ({1.4, 3.5, 6.7}); // Copy modified - 'ints' and 'nums' separate arrays array(int|float) ints = int_all(nums); write("%O\n", nums); write("%O\n", ints); // Original modified - 'ints' acts as alias for 'nums' ints = trunc_all(nums); write("%O\n", nums); write("%O\n", ints); // @@PLEAC@@_10.2 void some_func() { // Variables declared within a function are local to that function mixed variable = something; } // ---------------------------- // Assuming these are defined at file level, that is, outside of 'main' // or any other function they are accessable by every other member of // the same file [and if this file (read: class or program) is the // only one comprising the 'system', they are effectively 'global'] string name = argv[1]; int age = (int) argv[2]; int c = fetch_time(); int condition; // ------------ int run_check() { ... condition = 1; } int check_x(int x) { string y = "whatever"; // Whilst 'run_check' has access to 'name', 'age', and 'c' [because // these are declared at a higher scope], it does not have access to // 'y' or any other locally defined variable run_check(); // 'run_check' will have updated 'condition' if (condition) write("got x: %d\n", x); } // @@PLEAC@@_10.3 // Pike does not implement C style 'static' variables [i.e. persisent // local variables], nor does it implement C++ style 'class variables' // [oddly enough, also implemented in C++ via use of the 'static' // keyword], both of which could be used to implement solutions to the // problems presented in this section. Also, there is no direct // equivalent to Perl's 'BEGIN' block [closest equivalent is the // class 'create' method]. So, to solve a problem like implementing a // 'counter': // // * Use Pike's OOP facilities [simple, natural] // * Use closures [somewhat unwieldly, but possible] // OOP Approach class Counter { private int counter; static void create(int start) { counter = start; } public int next() { return ++counter; } public int prev() { return --counter; } } // ---- int main() { Counter counter = Counter(42); write("%d\n", counter->next()); write("%d\n", counter->prev()); } // ---------------------------- // A refinement of the previous implementation that mimics 'static' // variables class Static { // 'static' variable that is shared by all instance of 'Counter' int counter; class Counter { public int next() { return ++counter; } public int prev() { return --counter; } } Counter make() { return Counter(); } public void create(int counter_) { counter = counter_; } } // ---- int main() { Static mkst = Static(42); Static.Counter counter_1 = mkst->make(); Static.Counter counter_2 = mkst->make(); // Same value of, 'counter', is accessed by each object write("%d\n", counter_1->next()); write("%d\n", counter_1->next()); write("%d\n", counter_2->next()); write("%d\n", counter_2->prev()); } // ---------------------------- // Closure Approach [Admittedly somewhat contrived: a Scheme overdose ;) !] function(string : function(void : int)) make_counter(int start) { int counter = start; int next_counter() { return ++counter; }; int prev_counter() { return --counter; }; return lambda(string op) { if (op == "next") return next_counter; if (op == "prev") return prev_counter; return 0; }; } int next_counter(function(string : function(void : int)) counter) { return counter("next")(); } int prev_counter(function(string : function(void : int)) counter) { return counter("prev")(); } // ---- int main() { function(string : function(void : int)) counter = make_counter(42); write("%d\n", next_counter(counter)); write("%d\n", prev_counter(counter)); } // @@PLEAC@@_10.4 // It's possible to obtain a great deal of program metadata through the // following sets of library functions: // // * 'this_object', and the sets of 'object_...' and 'program_...' // functions // * 'Program' module [provides object inheritance metadata] // // The use of, 'this_object', in particular, allows the current object // instance to be interrogated like a hash table i.e. all variables and // methods are accessable as hash table entries. // // Unfortunately, however, it doesn't appear possible to obtain the // current method / function name, at least, not without resorting // to tricks like embedding a string in each method explicitly naming // it. // // An example of program metadata use appears in chapter 'Objects and // Ties'. Since the function name cannot, AFAICT, be obtained, the // current section is not implemented. // // @@PLEAC@@_10.5 // Procedure parameters are passed by reference [read: the handle or // address (or whatever) of an object is passed and is used to uniquely // identify that object], so there is no special treatment required. // If an argument represents a mutable object then care should be taken // to not mutate the object within the function, either by making a copy // of the object [e.g. use 'copy_value' to clone it], or by using a // 'read-only' control structure like 'foreach' [if applicable] to // access it int|array(int) array_diff(array(int) a, array(int) b) { // Ensure an array copy is made ... int|array(int) ret = sizeof(a) != sizeof(b) ? 0 : copy_value(a); if (!ret) ret; // ... transformed, and returned for (int i; i < sizeof(ret); ++i) { ret[i] -= b[i]; }; return ret; } // ---------------------------- int|array(int) add_vec_pair(array(int) a, array(int) b) { int vecsize = sizeof(a); // Ensure an array copy is made ... int|array(int) ret = vecsize != sizeof(b) ? 0 : allocate(vecsize); if (!ret) ret; // ... transformed, and returned for (int i; i < vecsize; ++i) { ret[i] = a[i] + b[i]; }; return ret; } // ---- array(int) a = ({1, 2}), b = ({5, 8}); array(int) c = add_vec_pair(a, b); write("%O\n", c); // @@PLEAC@@_10.6 // Just as for subroutine parameters, Pike allows variation in return // types, where it may be of a specific type, one of a set of types, or // a generic return type. Whilst the Perl examples require that the // user to nominate a return type when the function is called, in Pike // it is handled in one of two ways: // // * Ensure function and receiving variable type match [see (1)] // * Use generic receiving variable, and type check [see (2)] // (1). Subroutine has set of return types. Whilst type checking // does occur [thus user code only need handle these known types // because other types won't be allowed], caller/ receiver needs to // type check so as correctly handle known cases int|array(int)|string mysub() { ... return 5; ... return ({5}); ... return "5"; } // ---- int|array(int)|string receiver = mysub(); if (intp(receiver)) { ... } if (arrayp(receiver)) { ... } if (stringp(receiver)) { ... } ... // ---------------------------- // (2). Subroutine has generic return type, so no type checking occurs. // It is up to the caller / receiver to thoroughly type check lest // some unforseen type be returned and possibly mishandled mixed mysub() { ... return 5; ... return ({5}); ... return "5"; } // ---- mixed receiver = mysub(); if (intp(receiver)) { ... } if (arrayp(receiver)) { ... } if (stringp(receiver)) { ... } ... // @@PLEAC@@_10.7 // Pike doesn't directly support named / keyword parameters, but these // can be easily mimiced using an array of mappings as function arguments // The mappings, themselves, could be implemented via a custom class // [see (1)], or via the 'mapping'type [see (2) - Perl examples] // (1) class KeyedValue { string key; mixed value; static void create(string key_, mixed value_) { key = key_; value = value_; } } void the_func(array(KeyedValue) keyargs) { foreach(keyargs, KeyedValue kv) { write("Key: %10s|Value: %10O\n", kv->key, kv->value); } } // ---- int main() { array(KeyedValue) keyargs = aggregate(KeyedValue("name", "Bob"), KeyedValue("age", 36), KeyedValue("income", 51000)); the_func(keyargs); } // ---------------------------- // (2) class RaceTime { int time; string dim; static void create(int time_, string dim_) { time = time_; dim = dim_; } } void the_func(mapping(string : RaceTime) ... args) { int start_time, finish_time, increment_time; string start_dim, finish_dim, increment_dim; foreach(args, mapping(string : RaceTime) arg) { arg["start"] && (start_time = arg["start"]->time, start_dim = arg["start"]->dim); arg["finish"] && (finish_time = arg["finish"]->time, finish_dim = arg["finish"]->dim); arg["increment"] && (increment_time = arg["increment"]->time, increment_dim = arg["increment"]->dim); } write("times: start %d, finish %d, increment %d\n", start_time, finish_time, increment_time); } // ---- int main() { array(mapping(string : RaceTime)) named_args = ({ (["increment" : RaceTime(20, "s")]), (["start" : RaceTime(5, "m")]), (["finish" : RaceTime(3, "m")]) }); // Package arguments as array for passing to function the_func(@named_args); named_args = ({ (["start" : RaceTime(5, "m")]), (["finish" : RaceTime(30, "m")]) }); // Ditto the_func(@named_args); // Pass argument(s) directly in argument list the_func((["finish" : RaceTime(30, "m")])); } // @@PLEAC@@_10.8 // It is languages that support pattern matching, such as Prolog, Oz and // SML, that tend to offer such facilities. These languages all offer // a 'match all and throw away' operator that can be used in place of // an identifier name(s), and have the resultant value(s) be discarded. // Such a facility helps keep code uncluttered because only values // that are required need to be named. // // Pike does not implement pattern matching, so does not sport such an // operator, nor any equivalent facility. Thus, none of the examples in // this section are directly implementable. // // @@PLEAC@@_10.9 // Pike supports the return of a single value from a function. Where // multiple values need to be returned, they can be packaged as an // aggregate such as an array or mapping, and *that* item returned. // The caller / receiver would, of course, be responsible for // appropriately extracting required elements from that returned item // (this process could be hardcoded, or generalised using extensive // runtime type checking). Alternatively, a custom class encapsulating // the return values can be used and an instance of that item returned // and processed // array(mixed) somefunc() { array(int) arr = ({1, 2, 3}); mapping(string : int) hash = (["x" : 1, "y" : 2, "z" : 3]); // Return an array containing an array and a hash return aggregate(arr, hash); } // ---- // Get return array array(mixed) arr = somefunc(); // Extract and process elements foreach(arr, mixed item) { write("Return item has type: %t, value: %O\n", item, item); } // ---------------------------- class RetValues { array(int) arr; mapping(string : int) hash; static void create() { arr = aggregate(1, 2, 3); hash = aggregate_mapping("x", 1, "y", 2, "z", 3); } } RetValues somefunc() { return RetValues(); } // ---- RetValues rv = RetValues(); write("Return item has type: %t, value: %O\n", rv->arr, rv->arr); write("Return item has type: %t, value: %O\n", rv->hash, rv->hash); // @@PLEAC@@_10.10 // Pike offers a very simple, consistent means of 'returning failure': // return 0 [representing 'false'] when a task does not succeed, otherwise // return whatever was the expected value. This design is extensively // used in the Pike library, and is well supported by the language in // that: // * Alternate return types may be specified // * A 'mixed' return type, indicating possible return of any type // value, may be used void die(string msg, void|int(1..256) rc) { werror(msg + NEWLINE); exit(rc ? rc : PROBLEM); } // ---- int|array(string) afunc() { ... if (ok) // ... return an array ... else // failure, so return 0 return 0; } // ---- int main() { int|array(string) arr = afunc(); if (!arr) die("Error with 'afunc' ..."); // ok, so use 'arr' ... } // @@PLEAC@@_10.11 // Whether Pike is seen to support prototyping depends on the definition // of this term used: // // * Prototyping along the lines used in Ada, Modula X, and even C / C++, // in which a procedure's interface is declared separately from its // implementation, is *not* supported // // * Prototyping in which, as part of the procedure definition, parameter // information must be supplied. This is a requirement in Pike in that // parameter number, names and type, must be given. Return types must // also be specified, but there is an exeption when using lambdas void func_with_no_arg() { ... } void func_with_one_arg(int arg1) { ... } void func_with_two_arg(int arg1, string arg2) { ... } void func_with_three_arg(int arg1, string arg2, float arg3) { ...} // ---- // Return type, 'void', specified function f = lambda(int arg1 : void) { ... } // Return type not specified, defaults to 'mixed' function g = lambda(int arg1) { ... } // @@PLEAC@@_10.12 // Like so many modern languages, Pike implements exception handling // using the 'catch' and 'throw' keywords // ---- // Not exactly like the Perl example, but a way of immediately exiting // from an application [note: using, 'exit', prevents any of the 'atexit' // call backs from executing, so application cleanup may be compromised] void die(string msg, void|int(1..256) rc) { werror(msg + NEWLINE); exit(rc ? rc : PROBLEM); } // ---- die("some message"); // ---------------------------- int(0..1) rmAll(array(string) filelist) { // 'result' will be 0 if the 'catch' block succeeds mixed result = catch { foreach(filelist, string filename) { rm(filename) || throw(PROBLEM); } }; // Return value of 'catch' block can be tested, and appropriate // action taken; here, a non-zero return code will be returned // [like many library functions] to indicate failure return result == OK; } // ---- // Attempt to remove the following files ... array(string) files = ({"...", "...", "..."}); rmAll(files) || die("Could not remove all files - exiting"); // @@PLEAC@@_10.13 // Global variable int age = 18; // ---- void print_age() { // Global value, 'age', is accessed write("Age is %d\n", age); } // ------------ int main() { // A local variable named, 'age' will act to 'shadow' the globally // defined version, thus any changes to, 'age', will not affect // the global version int age = 5; // Prints 18, the current value of the global version print_age(); // Local version is altered, *not* global version age = 23; // Prints 18, the current value of the global version print_age(); } // ---------------------------- // Global variable int age = 18; // ---- void print_age() { // Global value, 'age', is accessed write("Age is %d\n", age); } // ------------ int main() { // Here no local version declared: any changes affect global version age = 5; // Prints 5, the new value of the global version print_age(); // Global version again altered age = 23; // Prints 23, the new value of the global version print_age(); } // ---------------------------- // Global variable int age = 18; // ---- void print_age() { // Global value, 'age', is accessed write("Age is %d\n", age); } // ------------ int main() { // Global version value saved into local version int age = global::age; // Prints 18, the new value of the global version print_age(); // Global version this time altered global::age = 23; // Prints 23, the new value of the global version print_age(); // Global version value restored from saved local version global::age = age; // Prints 18, the restored value of the global version print_age(); } // @@PLEAC@@_10.14 // Define functions - will be considered constant / unchangeable void grow() { write("grow\n"); } void shrink() { write("shrink\n"); } // ---- // Execute functions: 'grow', 'shrink' output respectively grow(); shrink(); // Attempt to redefine, 'grow' fails because it is considered a // constant value: // // grow = shrink; // // However, it is possible to bind 'shrink' to a new local variable // called, 'grow' function grow = shrink; // Execute functions: 'shrink', 'shrink' output respectively because // local 'grow' shadows global version, and it is referencing the // code for 'shrink' grow(); shrink(); // ------------ // Define functions by assigning lambdas to global variables function(void : void) grow = lambda() { write("grow\n"); } function(void : void) shrink = lambda() { write("shrink\n"); } // ---- // Execute functions: 'grow', 'shrink' output respectively grow(); shrink(); // Attempt to redefine, 'grow' successful since a simple variable // assignment is being performed grow = shrink; // Execute functions: 'shrink', 'shrink' output respectively - 'grow' // has, effectively, been 'redefined' [note: reference to original // 'grow' code has been lost (but it could have been saved, then // restored)] grow(); shrink(); // ---------------------------- function barney = lambda() { ... }; // ... // 'fred' is now an alias for the code attached to 'barney' function fred = barney; // ---------------------------- function red = lambda(string text) { return "" + text + ""; }; // ---- write("%s\n", red("careful here")); // ------------ function colour_font = lambda(string colour, string text) { return "" + text + ""; }; function red = lambda(string text) { return colour_font("red", text); }; function green = lambda(string text) { return colour_font("green", text); }; // ... more 'colour' functions ... // ---- write("%s\n", red("careful here")); write("%s\n", green("careful there")); // ... // ------------ // Pike offers the 'compile' family of functions that allow for the // runtime compilation and [in combinaton with other Pike functions] // the subsequent execution, of Pike code, obtained either as a // dynamically-generated string, or loaded from file / URL. This, // AFAICT, is the Pike feature closest to that of the 'eval' function // found in languages like Scheme. The example here is rather // contrived and unwieldly, but it does show how code is generated, // compiled, and executed, and it *does* closely follow the Perl code // // Assemble text needed to build function string build_colour_func(string colour) { // Could also use library function, 'sprintf', to build string string bodytext = "\"\" + text + \"\""; return "string " + colour + "(string text) { return " + bodytext + "; };"; } int main(int argc, array(string) argv) { // 1. Generate source code. A function is built for each colour by // calling, 'build_colour_func', and all the text collected into // 'cf_text' array(string) colours = ({"red", "blue", "green", "yellow", "orange", "purple", "violet"}); string cf_text = ""; foreach(colours, string colour) { cf_text += build_colour_func(colour); } // 2. Compile generated source code, and make it accessable to the // current program. These two steps are, here, combined for brevity, // but consist of the following: // // program prog = compile_string(cf_text); // object cf_code = prog(); // // The latter step sees the code's 'create' method called for // initialisation, and also makes items accessable via: // // cf_code[ITEMNAME] // // For example, the function, 'red', may be: // referenced -> cf_code["red"] // applied -> cf_code["red"](" ... ") // object cf_code = compile_string(cf_text)(); // 3. Apply the generated functions mapping(string:string) colours_and_text = (["red":"baron", "blue":"zephyr", "green":"beret", "yellow":"ribbon", "orange":"county", "purple":"haze", "violet":"temper"]); foreach(indices(colours_and_text), string colour) { // Get relevant function function colour_func = cf_code[colour]; // Apply function with relevant argument(s) write("%s\n", colour_func(colours_and_text[colour])); // Or, above lines can be replaced with: // write("%s\n", cf_code[colour](colours_and_text[colour])); } } // @@PLEAC@@_10.15 // @@INCOMPLETE@@ // in your class mapping functions = ([]); function `->(string fun) { if(!functions->fun) functions[fun]=lambda(mixed|void ... args) { return sprintf("%{%s %}", fun, args); }; return functions[fun]; } // then outside write(object_of_your_class->chartreuse("stuff")); // @@PLEAC@@_10.16 // Alternate, though identically-behaving, nested subroutine definitions int outer(int arg) { int x = arg + 35; int inner() { return x * 19; }; return x + inner(); } // ------------ int outer(int arg) { int x = arg + 35; function(void : int) inner = lambda() { return x * 19; }; return x + inner(); } // ------------ function outer = lambda(int arg) { int x = arg + 35; return (lambda() { return x * 19; })() + x; }; // ------------ function(int : int) outer = lambda(int arg) { int x = arg + 35; return (lambda() { return x * 19; })() + x; }; // @@PLEAC@@_10.17 @@INCOMPLETE@@ @@INCOMPLETE@@