From: Dave Rolsky Date: Sun, 7 Jun 2009 19:48:16 +0000 (-0500) Subject: exercises for part 1 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=gitmo%2Fmoose-presentations.git;a=commitdiff_plain;h=ddd87d751a8c055d95240d6af73109770c743466 exercises for part 1 --- diff --git a/moose-class/exercises/answers/01-classes/Employee.pm b/moose-class/exercises/answers/01-classes/Employee.pm new file mode 100644 index 0000000..6c301fc --- /dev/null +++ b/moose-class/exercises/answers/01-classes/Employee.pm @@ -0,0 +1,21 @@ +package Employee; + +use Moose; + +extends 'Person'; + +has position => ( is => 'rw' ); +has salary => ( is => 'rw' ); +has ssn => ( is => 'ro' ); + +override full_name => sub { + my $self = shift; + + return super() . q[ (] . $self->position . q[)]; +}; + +no Moose; + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/moose-class/exercises/answers/01-classes/Person.pm b/moose-class/exercises/answers/01-classes/Person.pm new file mode 100644 index 0000000..5e79748 --- /dev/null +++ b/moose-class/exercises/answers/01-classes/Person.pm @@ -0,0 +1,18 @@ +package Person; + +use Moose; + +has first_name => ( is => 'rw' ); +has last_name => ( is => 'rw' ); + +sub full_name { + my $self = shift; + + return join q{ }, $self->first_name, $self->last_name; +} + +no Moose; + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/moose-class/exercises/lib/Employee.pm b/moose-class/exercises/lib/Employee.pm new file mode 100644 index 0000000..0f67f08 --- /dev/null +++ b/moose-class/exercises/lib/Employee.pm @@ -0,0 +1,4 @@ +package Employee; + + +1; diff --git a/moose-class/exercises/lib/Person.pm b/moose-class/exercises/lib/Person.pm new file mode 100644 index 0000000..9ec8c1b --- /dev/null +++ b/moose-class/exercises/lib/Person.pm @@ -0,0 +1,4 @@ +package Person; + + +1; diff --git a/moose-class/exercises/t/00-prereq.t b/moose-class/exercises/t/00-prereq.t new file mode 100644 index 0000000..92a507f --- /dev/null +++ b/moose-class/exercises/t/00-prereq.t @@ -0,0 +1,39 @@ +use strict; +use warnings; + +my %prereqs = ( + 'Test::More' => '0', + 'Moose' => '0.80', + 'Class::MOP' => '0.85', +); + +my @missing; +for my $mod ( keys %prereqs ) { + eval "require $mod"; + + if ($@) { + push @missing, "$mod is not installed"; + next; + } + + if ( $mod->VERSION < $prereqs{$mod} ) { + push @missing, "$mod must be version $prereqs{$mod} or greater"; + } +} + +if (@missing) { + warn "\n# ***********************************************************\n"; + warn "#\n"; + warn "# Found the following prereq problems ...\n"; + warn "# $_\n" for @missing; + warn "#\n"; + warn "# ***********************************************************\n"; + + exit 255; +} + +Test::More->import; + +plan( tests => 1 ); +ok( 'Looks like you have all the prereqs' ); + diff --git a/moose-class/exercises/t/01-classes.t b/moose-class/exercises/t/01-classes.t new file mode 100644 index 0000000..2e71127 --- /dev/null +++ b/moose-class/exercises/t/01-classes.t @@ -0,0 +1,41 @@ +# Your task ... +# +# Create a Person class in lib/Person.pm +# +# A Person has the following attributes: +# +# * first_name - read-write +# * last_name - read-write +# +# This class should also have a method named "printable_name". This +# method should return the first and last name separated by a string +# ("Jon Smith"). +# +# +# Create an Employee class in lib/Employee.pm +# +# The Employee class is a subclass of Person +# +# An Employee has the following read-write attributes: +# +# * position - read-write +# * salary - read-write +# * ssn - read-only +# +# The Employee class should override the "printable_name" method to +# append the employee's title in parentheses ("Jon Smith +# (Programmer)"). Use override() and super() for this. +# +# Finally, both classes should be free of Moose droppings, and should be +# immutable. + +use strict; +use warnings; + +use lib 't/lib'; +use MooseClass::Tests; + +use Person; +use Employee; + +MooseClass::Tests::tests01(); diff --git a/moose-class/exercises/t/lib/Lingua/EN/Inflect.pm b/moose-class/exercises/t/lib/Lingua/EN/Inflect.pm new file mode 100755 index 0000000..72c65fd --- /dev/null +++ b/moose-class/exercises/t/lib/Lingua/EN/Inflect.pm @@ -0,0 +1,2661 @@ +package Lingua::EN::Inflect; + +use strict; +use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS @ISA); +use Env; + +require Exporter; +@ISA = qw(Exporter); + +$VERSION = '1.89'; + +%EXPORT_TAGS = +( + ALL => [ qw( classical inflect + PL PL_N PL_V PL_ADJ NO NUM A AN + PL_eq PL_N_eq PL_V_eq PL_ADJ_eq + PART_PRES + ORD + NUMWORDS + def_noun def_verb def_adj def_a def_an )], + + INFLECTIONS => [ qw( classical inflect + PL PL_N PL_V PL_ADJ PL_eq + NO NUM A AN PART_PRES )], + + PLURALS => [ qw( classical inflect + PL PL_N PL_V PL_ADJ NO NUM + PL_eq PL_N_eq PL_V_eq PL_ADJ_eq )], + + COMPARISONS => [ qw( classical + PL_eq PL_N_eq PL_V_eq PL_ADJ_eq )], + + ARTICLES => [ qw( classical inflect NUM A AN )], + + NUMERICAL => [ qw( ORD NUMWORDS )], + + USER_DEFINED => [ qw( def_noun def_verb def_adj def_a def_an )], +); + +Exporter::export_ok_tags(qw( ALL )); + +# SUPPORT CLASSICAL PLURALIZATIONS + +my %def_classical = ( + all => 0, + zero => 0, + herd => 0, + names => 1, + persons => 0, + ancient => 0, +); + +my %all_classical = ( + all => 1, + zero => 1, + herd => 1, + names => 1, + persons => 1, + ancient => 1, +); + +my %classical = %def_classical; + +my $classical_mode = join '|', keys %all_classical; + $classical_mode = qr/^(?:$classical_mode)$/; + +sub classical +{ + if (!@_) { + %classical = %all_classical; + return; + } + if (@_==1 && $_[0] !~ $classical_mode) { + %classical = $_[0] ? %all_classical : (); + return; + } + while (@_) { + my $arg = shift; + if ($arg !~ $classical_mode) { + die "Unknown classical mode ($arg)\n"; + } + if (@_ && $_[0] !~ $classical_mode) { $classical{$arg} = shift; } + else { $classical{$arg} = 1; } + + if ($arg eq 'all') { + %classical = $classical{all} ? %all_classical : (); + } + } +} + +my $persistent_count; + +sub NUM # (;$count,$show) +{ + if (defined $_[0]) + { + $persistent_count = $_[0]; + return $_[0] if !defined($_[1]) || $_[1]; + } + else + { + $persistent_count = undef; + } + return ''; +} + + +# 0. PERFORM GENERAL INFLECTIONS IN A STRING + +sub enclose { "(?:$_[0])" } + +sub inflect +{ + my $save_persistent_count = $persistent_count; + my @sections = split /(NUM\([^)]*\))/, $_[0]; + my $inflection = ""; + + foreach ( @sections ) + { + unless (s/NUM\(\s*?(?:([^),]*)(?:,([^)]*))?)?\)/ NUM($1,$2) /xe) + { + 1 while + s/\bPL \( ([^),]*) (, ([^)]*) )? \) / PL($1,$3) /xeg + || s/\bPL_N \( ([^),]*) (, ([^)]*) )? \) / PL_N($1,$3) /xeg + || s/\bPL_V \( ([^),]*) (, ([^)]*) )? \) / PL_V($1,$3) /xeg + || s/\bPL_ADJ \( ([^),]*) (, ([^)]*) )? \) / PL_ADJ($1,$3) /xeg + || s/\bAN? \( ([^),]*) (, ([^)]*) )? \) / A($1,$3) /xeg + || s/\bNO \( ([^),]*) (, ([^)]*) )? \) / NO($1,$3) /xeg + || s/\bORD \( ([^)]*) \) / ORD($1) /xeg + || s/\bNUMWORDS \( ([^)]*) \) / NUMWORDS($1) /xeg + || s/\bPART_PRES \( ([^)]*) \) / PART_PRES($1) /xeg + } + + $inflection .= $_; + } + + $persistent_count = $save_persistent_count; + return $inflection; +} + + +# 1. PLURALS + +my %PL_sb_irregular_s = +( + "corpus" => "corpuses|corpora", + "opus" => "opuses|opera", + "genus" => "genera", + "mythos" => "mythoi", + "penis" => "penises|penes", + "testis" => "testes", + "atlas" => "atlases|atlantes", +); + +my %PL_sb_irregular = +( + "child" => "children", + "brother" => "brothers|brethren", + "loaf" => "loaves", + "hoof" => "hoofs|hooves", + "beef" => "beefs|beeves", + "money" => "monies", + "mongoose" => "mongooses", + "ox" => "oxen", + "cow" => "cows|kine", + "soliloquy" => "soliloquies", + "graffito" => "graffiti", + "prima donna" => "prima donnas|prime donne", + "octopus" => "octopuses|octopodes", + "genie" => "genies|genii", + "ganglion" => "ganglions|ganglia", + "trilby" => "trilbys", + "turf" => "turfs|turves", + "numen" => "numina", + "atman" => "atmas", + "occiput" => "occiputs|occipita", + + %PL_sb_irregular_s, +); + +my $PL_sb_irregular = enclose join '|', keys %PL_sb_irregular; + +# CLASSICAL "..is" -> "..ides" + +my @PL_sb_C_is_ides = +( +# GENERAL WORDS... + + "ephemeris", "iris", "clitoris", + "chrysalis", "epididymis", + +# INFLAMATIONS... + + ".*itis", + +); + +my $PL_sb_C_is_ides = enclose join "|", map { substr($_,0,-2) } @PL_sb_C_is_ides; + +# CLASSICAL "..a" -> "..ata" + +my @PL_sb_C_a_ata = +( + "anathema", "bema", "carcinoma", "charisma", "diploma", + "dogma", "drama", "edema", "enema", "enigma", "lemma", + "lymphoma", "magma", "melisma", "miasma", "oedema", + "sarcoma", "schema", "soma", "stigma", "stoma", "trauma", + "gumma", "pragma", +); + +my $PL_sb_C_a_ata = enclose join "|", map { substr($_,0,-1) } @PL_sb_C_a_ata; + +# UNCONDITIONAL "..a" -> "..ae" + +my $PL_sb_U_a_ae = enclose join "|", +( + "alumna", "alga", "vertebra", "persona" +); + +# CLASSICAL "..a" -> "..ae" + +my $PL_sb_C_a_ae = enclose join "|", +( + "amoeba", "antenna", "formula", "hyperbola", + "medusa", "nebula", "parabola", "abscissa", + "hydra", "nova", "lacuna", "aurora", ".*umbra", + "flora", "fauna", +); + +# CLASSICAL "..en" -> "..ina" + +my $PL_sb_C_en_ina = enclose join "|", map { substr($_,0,-2) } +( + "stamen", "foramen", "lumen" +); + +# UNCONDITIONAL "..um" -> "..a" + +my $PL_sb_U_um_a = enclose join "|", map { substr($_,0,-2) } +( + "bacterium", "agendum", "desideratum", "erratum", + "stratum", "datum", "ovum", "extremum", + "candelabrum", +); + +# CLASSICAL "..um" -> "..a" + +my $PL_sb_C_um_a = enclose join "|", map { substr($_,0,-2) } +( + "maximum", "minimum", "momentum", "optimum", + "quantum", "cranium", "curriculum", "dictum", + "phylum", "aquarium", "compendium", "emporium", + "enconium", "gymnasium", "honorarium", "interregnum", + "lustrum", "memorandum", "millennium", "rostrum", + "spectrum", "speculum", "stadium", "trapezium", + "ultimatum", "medium", "vacuum", "velum", + "consortium", +); + +# UNCONDITIONAL "..us" -> "i" + +my $PL_sb_U_us_i = enclose join "|", map { substr($_,0,-2) } +( + "alumnus", "alveolus", "bacillus", "bronchus", + "locus", "nucleus", "stimulus", "meniscus", +); + +# CLASSICAL "..us" -> "..i" + +my $PL_sb_C_us_i = enclose join "|", map { substr($_,0,-2) } +( + "focus", "radius", "genius", + "incubus", "succubus", "nimbus", + "fungus", "nucleolus", "stylus", + "torus", "umbilicus", "uterus", + "hippopotamus", +); + +# CLASSICAL "..us" -> "..us" (ASSIMILATED 4TH DECLENSION LATIN NOUNS) + +my $PL_sb_C_us_us = enclose join "|", +( + "status", "apparatus", "prospectus", "sinus", + "hiatus", "impetus", "plexus", +); + +# UNCONDITIONAL "..on" -> "a" + +my $PL_sb_U_on_a = enclose join "|", map { substr($_,0,-2) } +( + "criterion", "perihelion", "aphelion", + "phenomenon", "prolegomenon", "noumenon", + "organon", "asyndeton", "hyperbaton", +); + +# CLASSICAL "..on" -> "..a" + +my $PL_sb_C_on_a = enclose join "|", map { substr($_,0,-2) } +( + "oxymoron", +); + +# CLASSICAL "..o" -> "..i" (BUT NORMALLY -> "..os") + +my @PL_sb_C_o_i = +( + "solo", "soprano", "basso", "alto", + "contralto", "tempo", "piano", "virtuoso", +); +my $PL_sb_C_o_i = enclose join "|", map { substr($_,0,-1) } @PL_sb_C_o_i; + +# ALWAYS "..o" -> "..os" + +my $PL_sb_U_o_os = enclose join "|", +( + "albino", "archipelago", "armadillo", + "commando", "crescendo", "fiasco", + "ditto", "dynamo", "embryo", + "ghetto", "guano", "inferno", + "jumbo", "lumbago", "magneto", + "manifesto", "medico", "octavo", + "photo", "pro", "quarto", + "canto", "lingo", "generalissimo", + "stylo", "rhino", "casino", + "auto", "macro", 'zero', + + @PL_sb_C_o_i, +); + + +# UNCONDITIONAL "..[ei]x" -> "..ices" + +my $PL_sb_U_ex_ices = enclose join "|", map { substr($_,0,-2) } +( + "codex", "murex", "silex", +); + +my $PL_sb_U_ix_ices = enclose join "|", map { substr($_,0,-2) } +( + "radix", "helix", +); + +# CLASSICAL "..[ei]x" -> "..ices" + +my $PL_sb_C_ex_ices = enclose join "|", map { substr($_,0,-2) } +( + "vortex", "vertex", "cortex", "latex", + "pontifex", "apex", "index", "simplex", +); + +my $PL_sb_C_ix_ices = enclose join "|", map { substr($_,0,-2) } +( + "appendix", +); + +# ARABIC: ".." -> "..i" + +my $PL_sb_C_i = enclose join "|", +( + "afrit", "afreet", "efreet", +); + +# HEBREW: ".." -> "..im" + +my $PL_sb_C_im = enclose join "|", +( + "goy", "seraph", "cherub", +); + +# UNCONDITIONAL "..man" -> "..mans" + +my $PL_sb_U_man_mans = enclose join "|", +qw( + human + Alabaman Bahaman Burman German + Hiroshiman Liman Nakayaman Oklahoman + Panaman Selman Sonaman Tacoman Yakiman + Yokohaman Yuman +); + +my @PL_sb_uninflected_s = +( +# PAIRS OR GROUPS SUBSUMED TO A SINGULAR... + "breeches", "britches", "clippers", "gallows", "hijinks", + "headquarters", "pliers", "scissors", "testes", "herpes", + "pincers", "shears", "proceedings", "trousers", + +# UNASSIMILATED LATIN 4th DECLENSION + + "cantus", "coitus", "nexus", + +# RECENT IMPORTS... + "contretemps", "corps", "debris", + ".*ois", "siemens", + +# DISEASES + ".*measles", "mumps", + +# MISCELLANEOUS OTHERS... + "diabetes", "jackanapes", "series", "species", "rabies", + "chassis", "innings", "news", "mews", +); + +my $PL_sb_uninflected_herd = enclose join "|", +# DON'T INFLECT IN CLASSICAL MODE, OTHERWISE NORMAL INFLECTION +( + "wildebeest", "swine", "eland", "bison", "buffalo", + "elk", "moose", "rhinoceros", +); + +my $PL_sb_uninflected = enclose join "|", +( +# SOME FISH AND HERD ANIMALS + ".*fish", "tuna", "salmon", "mackerel", "trout", + "bream", "sea[- ]bass", "carp", "cod", "flounder", "whiting", + + ".*deer", ".*sheep", + +# ALL NATIONALS ENDING IN -ese + "Portuguese", "Amoyese", "Borghese", "Congoese", "Faroese", + "Foochowese", "Genevese", "Genoese", "Gilbertese", "Hottentotese", + "Kiplingese", "Kongoese", "Lucchese", "Maltese", "Nankingese", + "Niasese", "Pekingese", "Piedmontese", "Pistoiese", "Sarawakese", + "Shavese", "Vermontese", "Wenchowese", "Yengeese", + ".*[nrlm]ese", + +# SOME WORDS ENDING IN ...s (OFTEN PAIRS TAKEN AS A WHOLE) + + @PL_sb_uninflected_s, + +# DISEASES + ".*pox", + + +# OTHER ODDITIES + "graffiti", "djinn" +); + +# SINGULAR WORDS ENDING IN ...s (ALL INFLECT WITH ...es) + +my $PL_sb_singular_s = enclose join '|', +( + ".*ss", + "acropolis", "aegis", "alias", "asbestos", "bathos", "bias", + "bronchitis", "bursitis", "caddis", "cannabis", + "canvas", "chaos", "cosmos", "dais", "digitalis", + "epidermis", "ethos", "eyas", "gas", "glottis", + "hubris", "ibis", "lens", "mantis", "marquis", "metropolis", + "pathos", "pelvis", "polis", "rhinoceros", + "sassafras", "trellis", ".*us", "[A-Z].*es", + + @PL_sb_C_is_ides, +); + +my $PL_v_special_s = enclose join '|', +( + $PL_sb_singular_s, + @PL_sb_uninflected_s, + keys %PL_sb_irregular_s, + '(.*[csx])is', + '(.*)ceps', + '[A-Z].*s', +); + +my %PL_sb_postfix_adj = ( + 'general' => ['(?!major|lieutenant|brigadier|adjutant)\S+'], + 'martial' => [qw(court)], +); + +foreach (keys %PL_sb_postfix_adj) { + $PL_sb_postfix_adj{$_} = enclose + enclose(join('|', @{$PL_sb_postfix_adj{$_}})) + . "(?=(?:-|\\s+)$_)"; +} + +my $PL_sb_postfix_adj = '(' . join('|', values %PL_sb_postfix_adj) . ')(.*)'; + +my $PL_sb_military = 'major|lieutenant|brigadier|adjutant|quartermaster'; +my $PL_sb_general = '((?!'.$PL_sb_military.').*?)((-|\s+)general)'; + +my $PL_prep = enclose join '|', qw ( + about above across after among around at athwart before behind + below beneath beside besides between betwixt beyond but by + during except for from in into near of off on onto out over + since till to under until unto upon with +); + +my $PL_sb_prep_dual_compound = '(.*?)((?:-|\s+)(?:'.$PL_prep.'|d[eu])(?:-|\s+))a(?:-|\s+)(.*)'; + +my $PL_sb_prep_compound = '(.*?)((-|\s+)('.$PL_prep.'|d[eu])((-|\s+)(.*))?)'; + + +my %PL_pron_nom = +( +# NOMINATIVE REFLEXIVE + +"i" => "we", "myself" => "ourselves", +"you" => "you", "yourself" => "yourselves", +"she" => "they", "herself" => "themselves", +"he" => "they", "himself" => "themselves", +"it" => "they", "itself" => "themselves", +"they" => "they", "themself" => "themselves", + +# POSSESSIVE + +"mine" => "ours", +"yours" => "yours", +"hers" => "theirs", +"his" => "theirs", +"its" => "theirs", +"theirs" => "theirs", +); + +my %PL_pron_acc = +( +# ACCUSATIVE REFLEXIVE + +"me" => "us", "myself" => "ourselves", +"you" => "you", "yourself" => "yourselves", +"her" => "them", "herself" => "themselves", +"him" => "them", "himself" => "themselves", +"it" => "them", "itself" => "themselves", +"them" => "them", "themself" => "themselves", +); + +my $PL_pron_acc = enclose join '|', keys %PL_pron_acc; + +my %PL_v_irregular_pres = +( +# 1st PERS. SING. 2ND PERS. SING. 3RD PERS. SINGULAR +# 3RD PERS. (INDET.) + +"am" => "are", "are" => "are", "is" => "are", +"was" => "were", "were" => "were", "was" => "were", +"have" => "have", "have" => "have", "has" => "have", +"do" => "do", "do" => "do", "does" => "do", +); + +my $PL_v_irregular_pres = enclose join '|', keys %PL_v_irregular_pres; + +my %PL_v_ambiguous_pres = +( +# 1st PERS. SING. 2ND PERS. SING. 3RD PERS. SINGULAR +# 3RD PERS. (INDET.) + +"act" => "act", "act" => "act", "acts" => "act", +"blame" => "blame", "blame" => "blame", "blames" => "blame", +"can" => "can", "can" => "can", "can" => "can", +"must" => "must", "must" => "must", "must" => "must", +"fly" => "fly", "fly" => "fly", "flies" => "fly", +"copy" => "copy", "copy" => "copy", "copies" => "copy", +"drink" => "drink", "drink" => "drink", "drinks" => "drink", +"fight" => "fight", "fight" => "fight", "fights" => "fight", +"fire" => "fire", "fire" => "fire", "fires" => "fire", +"like" => "like", "like" => "like", "likes" => "like", +"look" => "look", "look" => "look", "looks" => "look", +"make" => "make", "make" => "make", "makes" => "make", +"reach" => "reach", "reach" => "reach", "reaches" => "reach", +"run" => "run", "run" => "run", "runs" => "run", +"sink" => "sink", "sink" => "sink", "sinks" => "sink", +"sleep" => "sleep", "sleep" => "sleep", "sleeps" => "sleep", +"view" => "view", "view" => "view", "views" => "view", +); + +my $PL_v_ambiguous_pres = enclose join '|', keys %PL_v_ambiguous_pres; + + +my $PL_v_irregular_non_pres = enclose join '|', +( +"did", "had", "ate", "made", "put", +"spent", "fought", "sank", "gave", "sought", +"shall", "could", "ought", "should", +); + +my $PL_v_ambiguous_non_pres = enclose join '|', +( +"thought", "saw", "bent", "will", "might", "cut", +); + +# "..oes" -> "..oe" (the rest are "..oes" -> "o") + +my $PL_v_oes_oe = enclose join "|", +qw( + .*shoes .*hoes .*toes + canoes floes oboes roes throes woes +); + +my $PL_count_zero = enclose join '|', +( +0, "no", "zero", "nil" +); + +my $PL_count_one = enclose join '|', +( +1, "a", "an", "one", "each", "every", "this", "that", +); + +my %PL_adj_special = +( +"a" => "some", "an" => "some", +"this" => "these", "that" => "those", +); +my $PL_adj_special = enclose join '|', keys %PL_adj_special; + +my %PL_adj_poss = +( +"my" => "our", +"your" => "your", +"its" => "their", +"her" => "their", +"his" => "their", +"their" => "their", +); +my $PL_adj_poss = enclose join '|', keys %PL_adj_poss; + + +sub checkpat +{ +local $SIG{__WARN__} = sub {0}; +do {$@ =~ s/at.*?$//; + die "\nBad user-defined singular pattern:\n\t$@\n"} + if (!eval "'' =~ m/$_[0]/; 1;" or $@); +return @_; +} + +sub checkpatsubs +{ +checkpat($_[0]); +if (defined $_[1]) +{ + local $SIG{__WARN__} = sub {0}; + do {$@ =~ s/at.*?$//; + die "\nBad user-defined plural string: '$_[1]'\n\t$@\n"} + if (!eval "qq{$_[1]}; 1;" or $@); +} +return @_; +} + +my @PL_sb_user_defined = (); +my @PL_v_user_defined = (); +my @PL_adj_user_defined = (); +my @A_a_user_defined = (); + +sub def_noun +{ + unshift @PL_sb_user_defined, checkpatsubs(@_); + return 1; +} + +sub def_verb +{ + unshift @PL_v_user_defined, checkpatsubs(@_[4,5]); + unshift @PL_v_user_defined, checkpatsubs(@_[2,3]); + unshift @PL_v_user_defined, checkpatsubs(@_[0,1]); + return 1; +} + +sub def_adj +{ + unshift @PL_adj_user_defined, checkpatsubs(@_); + return 1; +} + +sub def_a +{ +unshift @A_a_user_defined, checkpat(@_,'a'); +return 1; +} + +sub def_an +{ +unshift @A_a_user_defined, checkpat(@_,'an'); +return 1; +} + +sub ud_match +{ +my $word = shift; +for (my $i=0; $i < @_; $i+=2) +{ + if ($word =~ /^(?:$_[$i])$/i) + { + last unless defined $_[$i+1]; + return eval '"'.$_[$i+1].'"'; + } +} +return undef; +} + +do +{ +local $SIG{__WARN__} = sub {0}; +my $rcfile; + +$rcfile = $INC{'Lingua//EN/Inflect.pm'} || ''; +$rcfile =~ s/Inflect.pm$/.inflectrc/; +do $rcfile or die "\nBad .inflectrc file ($rcfile):\n\t$@\n" +if $rcfile && -r $rcfile && -s $rcfile; + +$rcfile = "$ENV{HOME}/.inflectrc" || ''; +do $rcfile or die "\nBad .inflectrc file ($rcfile):\n\t$@\n" +if $rcfile && -r $rcfile && -s $rcfile; +}; + +sub postprocess # FIX PEDANTRY AND CAPITALIZATION :-) +{ +my ($orig, $inflected) = @_; +$inflected =~ s/([^|]+)\|(.+)/ $classical{all}?$2:$1 /e; +return $orig =~ /^I$/ ? $inflected + : $orig =~ /^[A-Z]+$/ ? uc $inflected + : $orig =~ /^[A-Z]/ ? ucfirst $inflected + : $inflected; +} + +sub PL +# PL($word,$number) +{ +my ($str, $count) = @_; +my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/); +return $str unless $word; +my $plural = postprocess $word, _PL_special_adjective($word,$count) + || _PL_special_verb($word,$count) + || _PL_noun($word,$count); +return $pre.$plural.$post; +} + +sub PL_N +# PL_N($word,$number) +{ +my ($str, $count) = @_; +my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/); +return $str unless $word; +my $plural = postprocess $word, _PL_noun($word,$count); +return $pre.$plural.$post; +} + +sub PL_V +# PL_V($word,$number) +{ +my ($str, $count) = @_; +my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/); +return $str unless $word; +my $plural = postprocess $word, _PL_special_verb($word,$count) + || _PL_general_verb($word,$count); +return $pre.$plural.$post; +} + +sub PL_ADJ +# PL_ADJ($word,$number) +{ +my ($str, $count) = @_; +my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/); +return $str unless $word; +my $plural = postprocess $word, _PL_special_adjective($word,$count) + || $word; +return $pre.$plural.$post; +} + +sub PL_eq { _PL_eq(@_, \&PL); } +sub PL_N_eq { _PL_eq(@_, \&PL_N); } +sub PL_V_eq { _PL_eq(@_, \&PL_V); } +sub PL_ADJ_eq { _PL_eq(@_, \&PL_ADJ); } + +sub _PL_eq +{ +my ( $word1, $word2, $PL ) = @_; +my %classval = %classical; +%classical = %all_classical; +my $result = ""; +$result = "eq" if !$result && $word1 eq $word2; +$result = "p:s" if !$result && $word1 eq &$PL($word2); +$result = "s:p" if !$result && &$PL($word1) eq $word2; +%classical = (); +$result = "p:s" if !$result && $word1 eq &$PL($word2); +$result = "s:p" if !$result && &$PL($word1) eq $word2; +%classical = %classval; + +if ($PL == \&PL || $PL == \&PL_N) +{ + $result = "p:p" + if !$result && _PL_check_plurals_N($word1,$word2); + $result = "p:p" + if !$result && _PL_check_plurals_N($word2,$word1); +} +if ($PL == \&PL || $PL == \&PL_ADJ) +{ + $result = "p:p" + if !$result && _PL_check_plurals_ADJ($word1,$word2,$PL); +} + +return $result; +} + +sub _PL_reg_plurals +{ + $_[0] =~ /($_[1])($_[2]\|\1$_[3]|$_[3]\|\1$_[2])/ +} + +sub _PL_check_plurals_N +{ +my $pair = "$_[0]|$_[1]"; +foreach ( values %PL_sb_irregular_s ) { return 1 if $_ eq $pair; } +foreach ( values %PL_sb_irregular ) { return 1 if $_ eq $pair; } + +return 1 if _PL_reg_plurals($pair, $PL_sb_C_a_ata, "as","ata") + || _PL_reg_plurals($pair, $PL_sb_C_is_ides, "is","ides") + || _PL_reg_plurals($pair, $PL_sb_C_a_ae, "s","e") + || _PL_reg_plurals($pair, $PL_sb_C_en_ina, "ens","ina") + || _PL_reg_plurals($pair, $PL_sb_C_um_a, "ums","a") + || _PL_reg_plurals($pair, $PL_sb_C_us_i, "uses","i") + || _PL_reg_plurals($pair, $PL_sb_C_on_a, "ons","a") + || _PL_reg_plurals($pair, $PL_sb_C_o_i, "os","i") + || _PL_reg_plurals($pair, $PL_sb_C_ex_ices, "exes","ices") + || _PL_reg_plurals($pair, $PL_sb_C_ix_ices, "ixes","ices") + || _PL_reg_plurals($pair, $PL_sb_C_i, "s","i") + || _PL_reg_plurals($pair, $PL_sb_C_im, "s","im") + + || _PL_reg_plurals($pair, '.*eau', "s","x") + || _PL_reg_plurals($pair, '.*ieu', "s","x") + || _PL_reg_plurals($pair, '.*tri', "xes","ces") + || _PL_reg_plurals($pair, '.{2,}[yia]n', "xes","ges"); + + +return 0; +} + +sub _PL_check_plurals_ADJ +{ +my ( $word1a, $word2a ) = @_; +my ( $word1b, $word2b ) = @_; + +$word1a = '' unless $word1a =~ s/'s?$//; +$word2a = '' unless $word2a =~ s/'s?$//; +$word1b = '' unless $word1b =~ s/s'$//; +$word2b = '' unless $word2b =~ s/s'$//; + +if ($word1a) +{ + return 1 if $word2a && ( _PL_check_plurals_N($word1a, $word2a) + || _PL_check_plurals_N($word2a, $word1a) ); + return 1 if $word2b && ( _PL_check_plurals_N($word1a, $word2b) + || _PL_check_plurals_N($word2b, $word1a) ); +} +if ($word1b) +{ + return 1 if $word2a && ( _PL_check_plurals_N($word1b, $word2a) + || _PL_check_plurals_N($word2a, $word1b) ); + return 1 if $word2b && ( _PL_check_plurals_N($word1b, $word2b) + || _PL_check_plurals_N($word2b, $word1b) ); +} + + +return ""; +} + +sub _PL_noun +{ +my ( $word, $count ) = @_; +my $value; # UTILITY VARIABLE + +# DEFAULT TO PLURAL + +$count = $persistent_count + if !defined($count) && defined($persistent_count); + +$count = (defined $count and $count=~/^($PL_count_one)$/io + or defined $count and $classical{zero} + and $count=~/^($PL_count_zero)$/io) + ? 1 + : 2; + +return $word if $count==1; + +# HANDLE USER-DEFINED NOUNS + +return $value if defined($value = ud_match($word, @PL_sb_user_defined)); + + +# HANDLE EMPTY WORD, SINGULAR COUNT AND UNINFLECTED PLURALS + +$word eq '' and return $word; + +$word =~ /^($PL_sb_uninflected)$/i + and return $word; + +$classical{herd} and $word =~ /^($PL_sb_uninflected_herd)$/i + and return $word; + + +# HANDLE COMPOUNDS ("Governor General", "mother-in-law", "aide-de-camp", ETC.) + +$word =~ /^(?:$PL_sb_postfix_adj)$/i + and $value = $2 + and return _PL_noun($1,2) + . $value; + +$word =~ /^(?:$PL_sb_prep_dual_compound)$/i + and $value = [$2,$3] + and return _PL_noun($1,2) + . $value->[0] + . _PL_noun($value->[1]); + +$word =~ /^(?:$PL_sb_prep_compound)$/i + and $value = $2 + and return _PL_noun($1,2) + . $value; + +# HANDLE PRONOUNS + +$word =~ /^((?:$PL_prep)\s+)($PL_pron_acc)$/i + and return $1.$PL_pron_acc{lc($2)}; + +$value = $PL_pron_nom{lc($word)} + and return $value; + +$word =~ /^($PL_pron_acc)$/i + and return $PL_pron_acc{lc($1)}; + +# HANDLE ISOLATED IRREGULAR PLURALS + +$word =~ /(.*)\b($PL_sb_irregular)$/i + and return $1 . $PL_sb_irregular{lc $2}; +$word =~ /($PL_sb_U_man_mans)$/i + and return "$1s"; +$word =~ /(\S*)(person)$/i and return $classical{persons}?"$1persons":"$1people"; + +# HANDLE FAMILIES OF IRREGULAR PLURALS + +$word =~ /(.*)man$/i and return "$1men"; +$word =~ /(.*[ml])ouse$/i and return "$1ice"; +$word =~ /(.*)goose$/i and return "$1geese"; +$word =~ /(.*)tooth$/i and return "$1teeth"; +$word =~ /(.*)foot$/i and return "$1feet"; + +# HANDLE UNASSIMILATED IMPORTS + +$word =~ /(.*)ceps$/i and return $word; +$word =~ /(.*)zoon$/i and return "$1zoa"; +$word =~ /(.*[csx])is$/i and return "$1es"; +$word =~ /($PL_sb_U_ex_ices)ex$/i and return "$1ices"; +$word =~ /($PL_sb_U_ix_ices)ix$/i and return "$1ices"; +$word =~ /($PL_sb_U_um_a)um$/i and return "$1a"; +$word =~ /($PL_sb_U_us_i)us$/i and return "$1i"; +$word =~ /($PL_sb_U_on_a)on$/i and return "$1a"; +$word =~ /($PL_sb_U_a_ae)$/i and return "$1e"; + +# HANDLE INCOMPLETELY ASSIMILATED IMPORTS + +if ($classical{ancient}) +{ + $word =~ /(.*)trix$/i and return "$1trices"; + $word =~ /(.*)eau$/i and return "$1eaux"; + $word =~ /(.*)ieu$/i and return "$1ieux"; + $word =~ /(.{2,}[yia])nx$/i and return "$1nges"; + $word =~ /($PL_sb_C_en_ina)en$/i and return "$1ina"; + $word =~ /($PL_sb_C_ex_ices)ex$/i and return "$1ices"; + $word =~ /($PL_sb_C_ix_ices)ix$/i and return "$1ices"; + $word =~ /($PL_sb_C_um_a)um$/i and return "$1a"; + $word =~ /($PL_sb_C_us_i)us$/i and return "$1i"; + $word =~ /($PL_sb_C_us_us)$/i and return "$1"; + $word =~ /($PL_sb_C_a_ae)$/i and return "$1e"; + $word =~ /($PL_sb_C_a_ata)a$/i and return "$1ata"; + $word =~ /($PL_sb_C_is_ides)is$/i and return "$1ides"; + $word =~ /($PL_sb_C_o_i)o$/i and return "$1i"; + $word =~ /($PL_sb_C_on_a)on$/i and return "$1a"; + $word =~ /$PL_sb_C_im$/i and return "${word}im"; + $word =~ /$PL_sb_C_i$/i and return "${word}i"; +} + + +# HANDLE SINGULAR NOUNS ENDING IN ...s OR OTHER SILIBANTS + +$word =~ /^($PL_sb_singular_s)$/i and return "$1es"; +$word =~ /^([A-Z].*s)$/ and $classical{names} and return "$1es"; +$word =~ /(.*)([cs]h|[zx])$/i and return "$1$2es"; +# $word =~ /(.*)(us)$/i and return "$1$2es"; + +# HANDLE ...f -> ...ves + +$word =~ /(.*[eao])lf$/i and return "$1lves"; +$word =~ /(.*[^d])eaf$/i and return "$1eaves"; +$word =~ /(.*[nlw])ife$/i and return "$1ives"; +$word =~ /(.*)arf$/i and return "$1arves"; + +# HANDLE ...y + +$word =~ /(.*[aeiou])y$/i and return "$1ys"; +$word =~ /([A-Z].*y)$/ and $classical{names} and return "$1s"; +$word =~ /(.*)y$/i and return "$1ies"; + +# HANDLE ...o + +$word =~ /$PL_sb_U_o_os$/i and return "${word}s"; +$word =~ /[aeiou]o$/i and return "${word}s"; +$word =~ /o$/i and return "${word}es"; + + +# OTHERWISE JUST ADD ...s + +return "${word}s"; +} + + +sub _PL_special_verb +{ +my ( $word, $count ) = @_; +$count = $persistent_count + if !defined($count) && defined($persistent_count); +$count = (defined $count and $count=~/^($PL_count_one)$/io or + defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1 + : 2; + +return undef if $count=~/^($PL_count_one)$/io; + +my $value; # UTILITY VARIABLE + +# HANDLE USER-DEFINED VERBS + +return $value if defined($value = ud_match($word, @PL_v_user_defined)); + +# HANDLE IRREGULAR PRESENT TENSE (SIMPLE AND COMPOUND) + +$word =~ /^($PL_v_irregular_pres)((\s.*)?)$/i + and return $PL_v_irregular_pres{lc $1}.$2; + +# HANDLE IRREGULAR FUTURE, PRETERITE AND PERFECT TENSES + +$word =~ /^($PL_v_irregular_non_pres)((\s.*)?)$/i + and return $word; + +# HANDLE PRESENT NEGATIONS (SIMPLE AND COMPOUND) + +$word =~ /^($PL_v_irregular_pres)(n't(\s.*)?)$/i + and return $PL_v_irregular_pres{lc $1}.$2; + +$word =~ /^\S+n't\b/i + and return $word; + +# HANDLE SPECIAL CASES + +$word =~ /^($PL_v_special_s)$/ and return undef; +$word =~ /\s/ and return undef; + +# HANDLE STANDARD 3RD PERSON (CHOP THE ...(e)s OFF SINGLE WORDS) + +$word =~ /^(.*)([cs]h|[x]|zz|ss)es$/i and return "$1$2"; + +$word =~ /^(..+)ies$/i and return "$1y"; + +$word =~ /($PL_v_oes_oe)$/ and return substr($1,0,-1); +$word =~ /^(.+)oes$/i and return "$1o"; + +$word =~ /^(.*[^s])s$/i and return $1; + +# OTHERWISE, A REGULAR VERB (HANDLE ELSEWHERE) + +return undef; +} + +sub _PL_general_verb +{ +my ( $word, $count ) = @_; +$count = $persistent_count + if !defined($count) && defined($persistent_count); +$count = (defined $count and $count=~/^($PL_count_one)$/io or + defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1 + : 2; + +return $word if $count=~/^($PL_count_one)$/io; + +# HANDLE AMBIGUOUS PRESENT TENSES (SIMPLE AND COMPOUND) + +$word =~ /^($PL_v_ambiguous_pres)((\s.*)?)$/i + and return $PL_v_ambiguous_pres{lc $1}.$2; + +# HANDLE AMBIGUOUS PRETERITE AND PERFECT TENSES + +$word =~ /^($PL_v_ambiguous_non_pres)((\s.*)?)$/i + and return $word; + +# OTHERWISE, 1st OR 2ND PERSON IS UNINFLECTED + +return $word; + +} + +sub _PL_special_adjective +{ +my ( $word, $count ) = @_; +$count = $persistent_count + if !defined($count) && defined($persistent_count); +$count = (defined $count and $count=~/^($PL_count_one)$/io or + defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1 + : 2; + +return $word if $count=~/^($PL_count_one)$/io; + + +# HANDLE USER-DEFINED ADJECTIVES + +my $value; +return $value if defined($value = ud_match($word, @PL_adj_user_defined)); + +# HANDLE KNOWN CASES + +$word =~ /^($PL_adj_special)$/i + and return $PL_adj_special{lc $1}; + +# HANDLE POSSESSIVES + +$word =~ /^($PL_adj_poss)$/i + and return $PL_adj_poss{lc $1}; + +$word =~ /^(.*)'s?$/ and do { my $pl = PL_N($1); + return "$pl'" . ($pl =~ m/s$/ ? "" : "s"); + }; + +# OTHERWISE, NO IDEA + +return undef; + +} + + +# 2. INDEFINITE ARTICLES + +# THIS PATTERN MATCHES STRINGS OF CAPITALS STARTING WITH A "VOWEL-SOUND" +# CONSONANT FOLLOWED BY ANOTHER CONSONANT, AND WHICH ARE NOT LIKELY +# TO BE REAL WORDS (OH, ALL RIGHT THEN, IT'S JUST MAGIC!) + +my $A_abbrev = q{ +(?! FJO | [HLMNS]Y. | RY[EO] | SQU + | ( F[LR]? | [HL] | MN? | N | RH? | S[CHKLMNPTVW]? | X(YL)?) [AEIOU]) +[FHLMNRSX][A-Z] +}; + +# THIS PATTERN CODES THE BEGINNINGS OF ALL ENGLISH WORDS BEGINING WITH A +# 'y' FOLLOWED BY A CONSONANT. ANY OTHER Y-CONSONANT PREFIX THEREFORE +# IMPLIES AN ABBREVIATION. + +my $A_y_cons = 'y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)'; + +# EXCEPTIONS TO EXCEPTIONS + +my $A_explicit_an = enclose join '|', +( +"euler", +"hour(?!i)", "heir", "honest", "hono", +); + +sub A +{ +my ($str, $count) = @_; +my ($pre, $word, $post) = ( $str =~ m/\A(\s*)(?:an?\s+)?(.+?)(\s*)\Z/i ); +return $str unless $word; +my $result = _indef_article($word,$count); +return $pre.$result.$post; +} + +sub AN { goto &A } + +sub _indef_article +{ +my ( $word, $count ) = @_; + +$count = $persistent_count + if !defined($count) && defined($persistent_count); + +return "$count $word" + if defined $count && $count!~/^($PL_count_one)$/io; + +# HANDLE USER-DEFINED VARIANTS + +my $value; +return $value if defined($value = ud_match($word, @A_a_user_defined)); + +# HANDLE SPECIAL CASES + +$word =~ /^($A_explicit_an)/i and return "an $word"; + +# HANDLE ABBREVIATIONS + +$word =~ /^($A_abbrev)/ox and return "an $word"; +$word =~ /^[aefhilmnorsx][.-]/i and return "an $word"; +$word =~ /^[a-z][.-]/i and return "a $word"; + +# HANDLE CONSONANTS + +$word =~ /^[^aeiouy]/i and return "a $word"; + +# HANDLE SPECIAL VOWEL-FORMS + +$word =~ /^e[uw]/i and return "a $word"; +$word =~ /^onc?e\b/i and return "a $word"; +$word =~ /^uni([^nmd]|mo)/i and return "a $word"; +$word =~ /^u[bcfhjkqrst][aeiou]/i and return "a $word"; + +# HANDLE SPECIAL CAPITALS + +$word =~ /^U[NK][AIEO]?/ and return "a $word"; + +# HANDLE VOWELS + +$word =~ /^[aeiou]/i and return "an $word"; + +# HANDLE y... (BEFORE CERTAIN CONSONANTS IMPLIES (UNNATURALIZED) "i.." SOUND) + +$word =~ /^($A_y_cons)/io and return "an $word"; + +# OTHERWISE, GUESS "a" + return "a $word"; +} + +# 2. TRANSLATE ZERO-QUANTIFIED $word TO "no PL($word)" + +sub NO +{ +my ($str, $count) = @_; +my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/); + +$count = $persistent_count + if !defined($count) && defined($persistent_count); +$count = 0 unless $count; + +return "$pre$count " . PL($word,$count) . $post + unless $count =~ /^$PL_count_zero$/; +return "${pre}no ". PL($word,0) . $post ; +} + + +# PARTICIPLES + +sub PART_PRES +{ + local $_ = PL_V(shift,2); + s/ie$/y/ + or s/ue$/u/ + or s/([auy])e$/$1/ + or s/ski$/ski/ + or s/i$// + or s/([^e])e$/$1/ + or m/er$/ + or s/([^aeiou][aeiouy]([bdgmnprst]))$/$1$2/; + return "${_}ing"; +} + + + +# NUMERICAL INFLECTIONS + +my %nth = +( + 0 => 'th', + 1 => 'st', + 2 => 'nd', + 3 => 'rd', + 4 => 'th', + 5 => 'th', + 6 => 'th', + 7 => 'th', + 8 => 'th', + 9 => 'th', + 11 => 'th', + 12 => 'th', + 13 => 'th', +); + + +my %ordinal; +@ordinal{qw(ty one two three five eight nine twelve )}= + qw(tieth first second third fifth eighth ninth twelfth); + +my $ordinal_suff = join '|', keys %ordinal, ""; + +$ordinal{""} = 'th'; + +sub ORD($) +{ + my $num = shift; + if ($num =~ /\d/) { + return $num . ($nth{$num%100} || $nth{$num%10}); + } + else { + $num =~ s/($ordinal_suff)\Z/$ordinal{$1}/; + return $num; + } +} + + +my %default_args = +( + 'group' => 0, + 'comma' => ',', + 'and' => 'and', + 'zero' => 'zero', + 'one' => 'one', + 'decimal' => 'point', +); + +my @unit = ('',qw(one two three four five six seven eight nine)); +my @teen = qw(ten eleven twelve thirteen fourteen + fifteen sixteen seventeen eighteen nineteen); +my @ten = ('','',qw(twenty thirty forty fifty sixty seventy eighty ninety)); +my @mill = map { (my $val=$_) =~ s/_/illion/; " $val" } + ('',qw(thousand m_ b_ tr_ quadr_ quint_ sext_ sept_ oct_ non_ dec_)); + + +sub mill { my $ind = $_[0]||0; + die "Number out of range\n" if $ind > $#mill; + return $ind<@mill ? $mill[$ind] : ' ???illion'; } + +sub unit { return $unit[$_[0]]. mill($_[1]); } + +sub ten +{ + return $ten[$_[0]] . ($_[0]&&$_[1]?'-':'') . $unit[$_[1]] . mill($_[2]) + if $_[0] ne '1'; + return $teen[$_[1]]. $mill[$_[2]||0]; +} + +sub hund +{ + return unit($_[0]) . " hundred" . ($_[1] || $_[2] ? " $_[4] " : '') + . ten($_[1],$_[2]) . mill($_[3]) . ', ' if $_[0]; + return ten($_[1],$_[2]) . mill($_[3]) . ', ' if $_[1] || $_[2]; + return ''; +} + + +sub enword +{ + my ($num,$group,$zero,$one,$comma,$and) = @_; + + if ($group==1) + { + $num =~ s/(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")."$comma " /eg; + } + elsif ($group==2) + { + $num =~ s/(\d)(\d)/ ($1 ? ten($1,$2) : $2 ? " $zero " . unit($2) : " $zero $zero") . "$comma " /eg; + $num =~ s/(\d)/ ($1 ? unit($1) :" $zero")."$comma " /e; + } + elsif ($group==3) + { + $num =~ s/(\d)(\d)(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")." ".($2 ? ten($2,$3) : $3 ? " $zero " . unit($3) : " $zero $zero") . "$comma " /eg; + $num =~ s/(\d)(\d)/ ($1 ? ten($1,$2) : $2 ? " $zero " . unit($2) : " $zero $zero") . "$comma " /e; + $num =~ s/(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")."$comma " /e; + } + elsif ($num+0==0) { + $num = $zero; + } + elsif ($num+0==1) { + $num = $one; + } + else { + $num =~ s/\A\s*0+//; + my $mill = 0; + 1 while $num =~ s/(\d)(\d)(\d)(?=\D*\Z)/ hund($1,$2,$3,$mill++,$and) /e; + $num =~ s/(\d)(\d)(?=\D*\Z)/ ten($1,$2,$mill)."$comma " /e; + $num =~ s/(\d)(?=\D*\Z)/ unit($1,$mill) . "$comma "/e; + } + return $num; +} + +sub NUMWORDS +{ + my $num = shift; + my %arg = ( %default_args, @_ ); + my $group = $arg{group}; + + die "Bad chunking option: $group\n" unless $group =~ /\A[0-3]\Z/; + my $sign = ($num =~ /\A\s*\+/) ? "plus" + : ($num =~ /\A\s*\-/) ? "minus" + : ''; + + my ($zero, $one) = @arg{'zero','one'}; + my $comma = $arg{comma}; + my $and = $arg{'and'}; + + my $ord = $num =~ s/(st|nd|rd|th)\Z//; + my @chunks = ($arg{decimal}) + ? $group ? split(/\./, $num) : split(/\./, $num, 2) + : ($num); + + my $first = 1; + + if ($chunks[0] eq '') { $first=0; shift @chunks; } + + foreach ( @chunks ) + { + s/\D//g; + $_ = '0' unless $_; + + if (!$group && !$first) { $_ = enword($_,1,$zero,$one,$comma,$and) } + else { $_ = enword($_,$group,$zero,$one,$comma,$and) } + + s/, \Z//; + s/\s+,/,/g; + s/, (\S+)\s+\Z/ $and $1/ if !$group and $first; + s/\s+/ /g; + s/(\A\s|\s\Z)//g; + $first = '' if $first; + } + + my @numchunks = (); + if ($first =~ /0/) + { + unshift @chunks, ''; + } + else + { + @numchunks = split /\Q$comma /, $chunks[0]; + } + + $numchunks[-1] =~ s/($ordinal_suff)\Z/$ordinal{$1}/ + if $ord and @numchunks; + + foreach (@chunks[1..$#chunks]) + { + push @numchunks, $arg{decimal}; + push @numchunks, split /\Q$comma /; + } + + if (wantarray) + { + unshift @numchunks, $sign if $sign; + return @numchunks + } + elsif ($group) + { + return ($sign?"$sign ":'') . join ", ", @numchunks; + } + else + { + $num = ($sign?"$sign ":'') . shift @numchunks; + $first = ($num !~ /$arg{decimal}\Z/); + foreach ( @numchunks ) + { + if (/\A$arg{decimal}\Z/) + { + $num .= " $_"; + $first = 0; + } + elsif ($first) + { + $num .= "$comma $_"; + } + else + { + $num .= " $_"; + } + } + return $num; + } +} + +1; + +__END__ + +=head1 NAME + +Lingua::EN::Inflect - Convert singular to plural. Select "a" or "an". + +=head1 VERSION + +This document describes version 1.86 of Lingua::EN::Inflect, +released October 20, 2000. + +=head1 SYNOPSIS + + use Lingua::EN::Inflect qw ( PL PL_N PL_V PL_ADJ NO NUM + PL_eq PL_N_eq PL_V_eq PL_ADJ_eq + A AN + PART_PRES + ORD NUMWORDS + inflect classical + def_noun def_verb def_adj def_a def_an ); + + + # UNCONDITIONALLY FORM THE PLURAL + + print "The plural of ", $word, " is ", PL($word), "\n"; + + + # CONDITIONALLY FORM THE PLURAL + + print "I saw $cat_count ", PL("cat",$cat_count), "\n"; + + + # FORM PLURALS FOR SPECIFIC PARTS OF SPEECH + + print PL_N("I",$N1), PL_V("saw",$N1), + PL_ADJ("my",$N2), PL_N("saw",$N2), "\n"; + + + # DEAL WITH "0/1/N" -> "no/1/N" TRANSLATION: + + print "There ", PL_V("was",$errors), NO(" error",$errors), "\n"; + + + # USE DEFAULT COUNTS: + + print NUM($N1,""), PL("I"), PL_V(" saw"), NUM($N2), PL_N(" saw"); + print "There ", NUM($errors,''), PL_V("was"), NO(" error"), "\n"; + + + # COMPARE TWO WORDS "NUMBER-INSENSITIVELY": + + print "same\n" if PL_eq($word1, $word2); + print "same noun\n" if PL_eq_N($word1, $word2); + print "same verb\n" if PL_eq_V($word1, $word2); + print "same adj.\n" if PL_eq_ADJ($word1, $word2); + + + # ADD CORRECT "a" OR "an" FOR A GIVEN WORD: + + print "Did you want ", A($thing), " or ", AN($idea), "\n"; + + + # CONVERT NUMERALS INTO ORDINALS (i.e. 1->1st, 2->2nd, 3->3rd, etc.) + + print "It was", ORD($position), " from the left\n"; + + # CONVERT NUMERALS TO WORDS (i.e. 1->"one", 101->"one hundred and one", etc.) + # IN A SCALAR CONTEXT: GET BACK A SINGLE STRING... + + $words = NUMWORDS(1234); # "one thousand, two hundred and thirty-four" + $words = NUMWORDS(ORD(1234)); # "one thousand, two hundred and thirty-fourth" + + + # IN A LIST CONTEXT: GET BACK A LIST OF STRINGSi, ONE FOR EACH "CHUNK"... + + @words = NUMWORDS(1234); # ("one thousand","two hundred and thirty-four") + + + # OPTIONAL PARAMETERS CHANGE TRANSLATION: + + $words = NUMWORDS(12345, group=>1); + # "one, two, three, four, five" + + $words = NUMWORDS(12345, group=>2); + # "twelve, thirty-four, five" + + $words = NUMWORDS(12345, group=>3); + # "one twenty-three, forty-five" + + $words = NUMWORDS(1234, 'and'=>''); + # "one thousand, two hundred thirty-four" + + $words = NUMWORDS(1234, 'and'=>', plus'); + # "one thousand, two hundred, plus thirty-four" + + $words = NUMWORDS(555_1202, group=>1, zero=>'oh'); + # "five, five, five, one, two, oh, two" + + $words = NUMWORDS(555_1202, group=>1, one=>'unity'); + # "five, five, five, unity, two, oh, two" + + $words = NUMWORDS(123.456, group=>1, decimal=>'mark'); + # "one two three mark four five six" + + + # REQUIRE "CLASSICAL" PLURALS (EG: "focus"->"foci", "cherub"->"cherubim") + + classical; # USE ALL CLASSICAL PLURALS + + classical 1; # USE ALL CLASSICAL PLURALS + classical 0; # USE ALL MODERN PLURALS (DEFAULT) + + classical 'zero'; # "no error" INSTEAD OF "no errors" + classical zero=>1; # "no error" INSTEAD OF "no errors" + classical zero=>0; # "no errors" INSTEAD OF "no error" + + classical 'herd'; # "2 buffalo" INSTEAD OF "2 buffalos" + classical herd=>1; # "2 buffalo" INSTEAD OF "2 buffalos" + classical herd=>0; # "2 buffalos" INSTEAD OF "2 buffalo" + + classical 'persons'; # "2 chairpersons" INSTEAD OF "2 chairpeople" + classical persons=>1; # "2 chairpersons" INSTEAD OF "2 chairpeople" + classical persons=>0; # "2 chairpeople" INSTEAD OF "2 chairpersons" + + classical 'ancient'; # "2 formulae" INSTEAD OF "2 formulas" + classical ancient=>1; # "2 formulae" INSTEAD OF "2 formulas" + classical ancient=>0; # "2 formulas" INSTEAD OF "2 formulae" + + + + # INTERPOLATE "PL()", "PL_N()", "PL_V()", "PL_ADJ()", A()", "AN()" + # "NUM()" AND "ORD()" WITHIN STRINGS: + + print inflect("The plural of $word is PL($word)\n"); + print inflect("I saw $cat_count PL("cat",$cat_count)\n"); + print inflect("PL(I,$N1) PL_V(saw,$N1) PL(a,$N2) PL_N(saw,$N2)"); + print inflect("NUM($N1,)PL(I) PL_V(saw) NUM($N2,)PL(a) PL_N(saw)"); + print inflect("I saw NUM($cat_count) PL("cat")\nNUM()"); + print inflect("There PL_V(was,$errors) NO(error,$errors)\n"); + print inflect("There NUM($errors,) PL_V(was) NO(error)\n"; + print inflect("Did you want A($thing) or AN($idea)\n"); + print inflect("It was ORD($position) from the left\n"); + + + # ADD USER-DEFINED INFLECTIONS (OVERRIDING INBUILT RULES): + + def_noun "VAX" => "VAXen"; # SINGULAR => PLURAL + + def_verb "will" => "shall", # 1ST PERSON SINGULAR => PLURAL + "will" => "will", # 2ND PERSON SINGULAR => PLURAL + "will" => "will", # 3RD PERSON SINGULAR => PLURAL + + def_adj "hir" => "their", # SINGULAR => PLURAL + + def_a "h" # "AY HALWAYS SEZ 'HAITCH'!" + + def_an "horrendous.*" # "AN HORRENDOUS AFFECTATION" + + +=head1 DESCRIPTION + +The exportable subroutines of Lingua::EN::Inflect provide plural +inflections, "a"/"an" selection for English words, and manipulation +of numbers as words + +Plural forms of all nouns, most verbs, and some adjectives are +provided. Where appropriate, "classical" variants (for example: "brother" -> +"brethren", "dogma" -> "dogmata", etc.) are also provided. + +Pronunciation-based "a"/"an" selection is provided for all English +words, and most initialisms. + +It is also possible to inflect numerals (1,2,3) to ordinals (1st, 2nd, 3rd) +and to english words ("one", "two", "three). + +In generating these inflections, Lingua::EN::Inflect follows the Oxford +English Dictionary and the guidelines in Fowler's Modern English +Usage, preferring the former where the two disagree. + +The module is built around standard British spelling, but is designed +to cope with common American variants as well. Slang, jargon, and +other English dialects are I explicitly catered for. + +Where two or more inflected forms exist for a single word (typically a +"classical" form and a "modern" form), Lingua::EN::Inflect prefers the +more common form (typically the "modern" one), unless "classical" +processing has been specified +(see L<"MODERN VS CLASSICAL INFLECTIONS">). + +=head1 FORMING PLURALS + +=head2 Inflecting Plurals + +All of the C plural inflection subroutines take the word to be +inflected as their first argument and return the corresponding inflection. +Note that all such subroutines expect the I form of the word. The +results of passing a plural form are undefined (and unlikely to be correct). + +The C subroutines also take an optional second argument, +which indicates the grammatical "number" of the word (or of another word +with which the word being inflected must agree). If the "number" argument is +supplied and is not C<1> (or C<"one"> or C<"a">, or some other adjective that +implies the singular), the plural form of the word is returned. If the +"number" argument I indicate singularity, the (uninflected) word +itself is returned. If the number argument is omitted, the plural form +is returned unconditionally. + +The various subroutines are: + +=over 8 + +=item C + +The exportable subroutine C takes a I English noun or +pronoun and returns its plural. Pronouns in the nominative ("I" -> +"we") and accusative ("me" -> "us") cases are handled, as are +possessive pronouns ("mine" -> "ours"). + + +=item C + +The exportable subroutine C takes the I form of a +conjugated verb (that is, one which is already in the correct "person" +and "mood") and returns the corresponding plural conjugation. + + +=item C + +The exportable subroutine C takes the I form of +certain types of adjectives and returns the corresponding plural form. +Adjectives that are correctly handled include: "numerical" adjectives +("a" -> "some"), demonstrative adjectives ("this" -> "these", "that" -> +"those"), and possessives ("my" -> "our", "cat's" -> "cats'", "child's" +-> "childrens'", etc.) + + +=item C + +The exportable subroutine C takes a I English noun, +pronoun, verb, or adjective and returns its plural form. Where a word +has more than one inflection depending on its part of speech (for +example, the noun "thought" inflects to "thoughts", the verb "thought" +to "thought"), the (singular) noun sense is preferred to the (singular) +verb sense. + +Hence C will return "knives" ("knife" having been treated +as a singular noun), whereas C will return "knife" +("knifes" having been treated as a 3rd person singular verb). + +The inherent ambiguity of such cases suggests that, +where the part of speech is known, C, C, and +C should be used in preference to C. + +=back + +Note that all these subroutines ignore any whitespace surrounding the +word being inflected, but preserve that whitespace when the result is +returned. For example, C> returns S<" cats ">. + + +=head2 Numbered plurals + +The C subroutines return only the inflected word, not the count that +was used to inflect it. Thus, in order to produce "I saw 3 ducks", it +is necessary to use: + + print "I saw $N ", PL_N($animal,$N), "\n"; + +Since the usual purpose of producing a plural is to make it agree with +a preceding count, Lingua::EN::Inflect provides an exportable subroutine +(C) which, given a word and a(n optional) count, returns the +count followed by the correctly inflected word. Hence the previous +example can be rewritten: + + print "I saw ", NO($animal,$N), "\n"; + +In addition, if the count is zero (or some other term which implies +zero, such as C<"zero">, C<"nil">, etc.) the count is replaced by the +word "no". Hence, if C<$N> had the value zero, the previous example +would print the somewhat more elegant: + + I saw no animals + +rather than: + + I saw 0 animals + +Note that the name of the subroutine is a pun: the subroutine +returns either a number (a I) or a C<"no">, in front of the +inflected word. + + +=head2 Reducing the number of counts required + +In some contexts, the need to supply an explicit count to the various +C subroutines makes for tiresome repetition. For example: + + print PL_ADJ("This",$errors), PL_N(" error",$errors), + PL_V(" was",$errors), " fatal.\n"; + +Lingua::EN::Inflect therefore provides an exportable subroutine +(C) which may be used to set a persistent "default number" +value. If such a value is set, it is subsequently used whenever an +optional second "number" argument is omitted. The default value thus set +can subsequently be removed by calling C with no arguments. +Hence we could rewrite the previous example: + + NUM($errors); + print PL_ADJ("This"), PL_N(" error"), PL_V(" was"), "fatal.\n"; + NUM(); + +Normally, C returns its first argument, so that it may also +be "inlined" in contexts like: + + print NUM($errors), PL_N(" error"), PL_V(" was"), " detected.\n" + print PL_ADJ("This"), PL_N(" error"), PL_V(" was"), "fatal.\n" + if $severity > 1; + +However, in certain contexts (see L<"INTERPOLATING INFLECTIONS IN STRINGS">) +it is preferable that C return an empty string. Hence C +provides an optional second argument. If that argument is supplied (that is, if +it is defined) and evaluates to false, C returns an empty string +instead of its first argument. For example: + + print NUM($errors,0), NO("error"), PL_V(" was"), " detected.\n"; + print PL_ADJ("This"), PL_N(" error"), PL_V(" was"), "fatal.\n" + if $severity > 1; + + + +=head2 Number-insensitive equality + +Lingua::EN::Inflect also provides a solution to the problem +of comparing words of differing plurality through the exportable subroutines +C, C, C, and C. +Each of these subroutines takes two strings, and compares them +using the corresponding plural-inflection subroutine (C, C, +C, and C respectively). + +The comparison returns true if: + +=over 8 + +=item * + +the strings are C-equal, or + +=item * + +one string is C-equal to a plural form of the other, or + +=item * + +the strings are two different plural forms of the one word. + +=back + +Hence all of the following return true: + + PL_eq("index","index") # RETURNS "eq" + PL_eq("index","indexes") # RETURNS "s:p" + PL_eq("index","indices") # RETURNS "s:p" + PL_eq("indexes","index") # RETURNS "p:s" + PL_eq("indices","index") # RETURNS "p:s" + PL_eq("indices","indexes") # RETURNS "p:p" + PL_eq("indexes","indices") # RETURNS "p:p" + PL_eq("indices","indices") # RETURNS "eq" + +As indicated by the comments in the previous example, the actual value +returned by the various C subroutines encodes which of the +three equality rules succeeded: "eq" is returned if the strings were +identical, "s:p" if the strings were singular and plural respectively, +"p:s" for plural and singular, and "p:p" for two distinct plurals. +Inequality is indicated by returning an empty string. + +It should be noted that two distinct singular words which happen to take +the same plural form are I considered equal, nor are cases where +one (singular) word's plural is the other (plural) word's singular. +Hence all of the following return false: + + PL_eq("base","basis") # ALTHOUGH BOTH -> "bases" + PL_eq("syrinx","syringe") # ALTHOUGH BOTH -> "syringes" + PL_eq("she","he") # ALTHOUGH BOTH -> "they" + + PL_eq("opus","operas") # ALTHOUGH "opus" -> "opera" -> "operas" + PL_eq("taxi","taxes") # ALTHOUGH "taxi" -> "taxis" -> "taxes" + +Note too that, although the comparison is "number-insensitive" it is I +case-insensitive (that is, C returns false. To obtain +both number and case insensitivity, prefix both arguments with C +(that is, C returns true). + + +=head1 OTHER VERB FORMS + +=head2 Present participles + +C also provides the C subroutine, +which can take a 3rd person singular verb and +correctly inflect it to its present participle: + + PART_PRES("runs") # "running" + PART_PRES("loves") # "loving" + PART_PRES("eats") # "eating" + PART_PRES("bats") # "batting" + PART_PRES("spies") # "spying" + + +=head1 PROVIDING INDEFINITE ARTICLES + +=head2 Selecting indefinite articles + +Lingua::EN::Inflect provides two exportable subroutines (C and +C) which will correctly prepend the appropriate indefinite +article to a word, depending on its pronunciation. For example: + + A("cat") # -> "a cat" + AN("cat") # -> "a cat" + A("euphemism") # -> "a euphemism" + A("Euler number") # -> "an Euler number" + A("hour") # -> "an hour" + A("houri") # -> "a houri" + +The two subroutines are I in function and may be used +interchangeably. The only reason that two versions are provided is to +enhance the readability of code such as: + + print "That is ", AN($errortype), " error\n; + print "That is ", A($fataltype), " fatal error\n; + +Note that in both cases the actual article provided depends I on +the pronunciation of the first argument, I on the name of the +subroutine. + +C and C will ignore any indefinite article that already +exists at the start of the string. Thus: + + @half_arked = ( + "a elephant", + "a giraffe", + "an ewe", + "a orangutan", + ); + + print A($_), "\n" for @half_arked; + + # prints: + # an elephant + # a giraffe + # a ewe + # an orangutan + + +C and C both take an optional second argument. As with the +C subroutines, this second argument is a "number" specifier. If +its value is C<1> (or some other value implying singularity), C and +C insert "a" or "an" as appropriate. If the number specifier +implies plurality, (C and C insert the actual second argument instead. +For example: + + A("cat",1) # -> "a cat" + A("cat",2) # -> "2 cat" + A("cat","one") # -> "one cat" + A("cat","no") # -> "no cat" + +Note that, as implied by the previous examples, C and +C both assume that their job is merely to provide the correct +qualifier for a word (that is: "a", "an", or the specified count). +In other words, they assume that the word they are given has +already been correctly inflected for plurality. Hence, if C<$N> +has the value 2, then: + + print A("cat",$N); + +prints "2 cat", instead of "2 cats". The correct approach is to use: + + print A(PL("cat",$N),$N); + +or, better still: + + print NO("cat",$N); + +Note too that, like the various C subroutines, whenever C +and C are called with only one argument they are subject to the +effects of any preceding call to C. Hence, another possible +solution is: + + NUM($N); + print A(PL("cat")); + + +=head2 Indefinite articles and initialisms + +"Initialisms" (sometimes inaccurately called "acronyms") are terms which +have been formed from the initial letters of words in a phrase (for +example, "NATO", "NBL", "S.O.S.", "SCUBA", etc.) + +Such terms present a particular challenge when selecting between "a" +and "an", since they are sometimes pronounced as if they were a single +word ("nay-tow", "sku-ba") and sometimes as a series of letter names +("en-eff-ell", "ess-oh-ess"). + +C and C cope with this dichotomy using a series of inbuilt +rules, which may be summarized as: + +=over 8 + +=item 1. + +If the word starts with a single letter, followed by a period or dash +(for example, "R.I.P.", "C.O.D.", "e-mail", "X-ray", "T-square"), then +choose the appropriate article for the I of the first letter +("an R.I.P.", "a C.O.D.", "an e-mail", "an X-ray", "a T-square"). + +=item 2. + +If the first two letters of the word are capitals, +consonants, and do not appear at the start of any known English word, +(for example, "LCD", "XML", "YWCA"), then once again choose "a" or +"an" depending on the I of the first letter ("an LCD", "an +XML", "a YWCA"). + +=item 3. + +Otherwise, assume the string is a capitalized word or a +pronounceable initialism (for example, "LED", "OPEC", "FAQ", "UNESCO"), and +therefore takes "a" or "an" according to the (apparent) pronunciation of +the entire word ("a LED", "an OPEC", "a FAQ", "a UNESCO"). + +=back + +Note that rules 1 and 3 together imply that the presence or absence of +punctuation may change the selection of indefinite article for a +particular initialism (for example, "a FAQ" but "an F.A.Q."). + + +=head2 Indefinite articles and "soft H's" + +Words beginning in the letter 'H' present another type of difficulty +when selecting a suitable indefinite article. In a few such words +(for example, "hour", "honour", "heir") the 'H' is not voiced at +all, and so such words inflect with "an". The remaining cases +("voiced H's") may be divided into two categories: +"hard H's" (such as "hangman", "holograph", "hat", etc.) and +"soft H's" (such as "hysterical", "horrendous", "holy", etc.) + +Hard H's always take "a" as their indefinite article, and soft +H's normally do so as well. But I English speakers prefer +"an" for soft H's (although the practice is now generally considered an +affectation, rather than a legitimate grammatical alternative). + +At present, the C and C subroutines ignore soft H's and use +"a" for any voiced 'H'. The author would, however, welcome feedback on +this decision (envisaging a possible future "soft H" mode). + + +=head1 INFLECTING ORDINALS + +Occasionally it is useful to present an integer value as an ordinal +rather than as a numeral. For example: + + Enter password (1st attempt): ******** + Enter password (2nd attempt): ********* + Enter password (3rd attempt): ********* + No 4th attempt. Access denied. + +To this end, Lingua::EN::Inflect provides the C subroutine. + takes a single argument and forms its ordinal equivalent. +If the argument isn't a numerical integer, it just adds "-th". + + +=head1 CONVERTING NUMBERS TO WORDS + +The exportable subroutine C takes a number (cardinal or ordinal) +and returns an English representation of that number. In a scalar context +a string is returned. Hence: + + use Lingua::EN::Inflect qw( NUMWORDS ); + + $words = NUMWORDS(1234567); + +puts the string: + + "one million, two hundred and thirty-four thousand, five hundred and sixty-seven" + +into $words. + +In a list context each comma-separated chunk is returned as a separate element. +Hence: + + @words = NUMWORDS(1234567); + +puts the list: + + ("one million", + "two hundred and thirty-four thousand", + "five hundred and sixty-seven") + +into @words. + +Non-digits (apart from an optional leading plus or minus sign, +any decimal points, and ordinal suffixes -- see below) are silently +ignored, so the following all produce identical results: + + NUMWORDS(5551202); + NUMWORDS(5_551_202); + NUMWORDS("5,551,202"); + NUMWORDS("555-1202"); + +That last case is a little awkward since it's almost certainly a phone number, +and "five million, five hundred and fifty-one thousand, two hundred and two" +probably isn't what's wanted. + +To overcome this, C takes an optional named argument, 'group', +which changes how numbers are translated. The argument must be a +positive integer less than four, which indicated how the digits of the +number are to be grouped. If the argument is C<1>, then each digit is +translated separately. If the argument is C<2>, pairs of digits +(starting from the I) are grouped together. If the argument is +C<3>, triples of numbers (again, from the I) are grouped. Hence: + + NUMWORDS("555-1202", group=>1) + +returns C<"five, five, five, one, two, zero, two">, whilst: + + NUMWORDS("555-1202", group=>2) + +returns C<"fifty-five, fifty-one, twenty, two">, and: + + NUMWORDS("555-1202", group=>3) + +returns C<"five fifty-five, one twenty, two">. + +Phone numbers are often written in words as +C<"five..five..five..one..two..zero..two">, which is also easy to +achieve: + + join '..', NUMWORDS("555-1202", group=>1) + +C also handles decimal fractions. Hence: + + NUMWORDS("1.2345") + +returns C<"one point two three four five"> in a scalar context +and C<("one","point","two","three","four","five")>) in an array context. +Exponent form (C<"1.234e56">) is not yet handled. + +Multiple decimal points are only translated in one of the "grouping" modes. +Hence: + + NUMWORDS(101.202.303) + +returns C<"one hundred and one point two zero two three zero three">, +whereas: + + NUMWORDS(101.202.303, group=>1) + +returns C<"one zero one point two zero two point three zero three">. + +The digit C<'0'> is unusual in that in may be translated to English as "zero", +"oh", or "nought". To cater for this diversity, C may be passed +a named argument, 'zero', which may be set to +the desired translation of C<'0'>. For example: + + print join "..", NUMWORDS("555-1202", group=>3, zero=>'oh') + +prints C<"five..five..five..one..two..oh..two">. +By default, zero is rendered as "zero". + +Likewise, the digit C<'1'> may be rendered as "one" or "a/an" (or very +occasionally other variants), depending on the context. So there is a +C<'one'> argument as well: + + print NUMWORDS($_, one=>'a solitary', zero=>'no more'), + PL(" bottle of beer on the wall\n", $_) + for (3,2,1,0); + + # prints: + # three bottles of beer on the wall + # two bottles of beer on the wall + # a solitary bottle of beer on the wall + # no more bottles of beer on the wall + +Care is needed if the word "a/an" is to be used as a C<'one'> value. +Unless the next word is known in advance, it's almost always necessary +to use the C function as well: + + print A( NUMWORDS(1, one=>'a') . " $_\n") + for qw(cat aardvark ewe hour); + + # prints: + # a cat + # an aardvark + # a ewe + # an hour + +Another major regional variation in number translation is the use of +"and" in certain contexts. The named argument 'and' +allows the programmer to specify how "and" should be handled. Hence: + + print scalar NUMWORDS("765", 'and'=>'') + +prints "seven hundred sixty-five", instead of "seven hundred and sixty-five". +By default, the "and" is included. + +The translation of the decimal point is also subject to variation +(with "point", "dot", and "decimal" being the favorites). +The named argument 'decimal' allows the +programmer to how the decimal point should be rendered. Hence: + + print scalar NUMWORDS("666.124.64.101", group=>3, decimal=>'dot') + +prints "six sixty-six, dot, one twenty-four, dot, sixty-four, dot, one zero one" +By default, the decimal point is rendered as "point". + +C also handles the ordinal forms of numbers. So: + + print scalar NUMWORDS('1st'); + print scalar NUMWORDS('3rd'); + print scalar NUMWORDS('202nd'); + print scalar NUMWORDS('1000000th'); + +print: + + first + third + two hundred and twenty-second + one millionth + +Two common idioms in this regard are: + + print scalar NUMWORDS(ORD($number)); + +and: + + print scalar ORD(NUMWORDS($number)); + +These are identical in effect, except when $number contains a decimal: + + $number = 99.09; + print scalar NUMWORDS(ORD($number)); # ninety-ninth point zero nine + print scalar ORD(NUMWORDS($number)); # ninety-nine point zero ninth + +Use whichever you feel is most appropriate. + + +=head1 INTERPOLATING INFLECTIONS IN STRINGS + +By far the commonest use of the inflection subroutines is to +produce message strings for various purposes. For example: + + print NUM($errors), PL_N(" error"), PL_V(" was"), " detected.\n"; + print PL_ADJ("This"), PL_N(" error"), PL_V(" was"), "fatal.\n" + if $severity > 1; + +Unfortunately the need to separate each subroutine call detracts +significantly from the readability of the resulting code. To ameliorate +this problem, Lingua::EN::Inflect provides an exportable string-interpolating +subroutine (C), which recognizes calls to the various inflection +subroutines within a string and interpolates them appropriately. + +Using C the previous example could be rewritten: + + print inflect "NUM($errors) PL_N(error) PL_V(was) detected.\n"; + print inflect "PL_ADJ(This) PL_N(error) PL_V(was) fatal.\n" + if $severity > 1; + +Note that C also correctly handles calls to the C subroutine +(whether interpolated or antecedent). The C subroutine has +a related extra feature, in that it I cancels any "default +number" value before it returns its interpolated string. This means that +calls to C which are embedded in an C-interpolated +string do not "escape" and interfere with subsequent inflections. + + +=head1 MODERN VS CLASSICAL INFLECTIONS + +Certain words, mainly of Latin or Ancient Greek origin, can form +plurals either using the standard English "-s" suffix, or with +their original Latin or Greek inflections. For example: + + PL("stigma") # -> "stigmas" or "stigmata" + PL("torus") # -> "toruses" or "tori" + PL("index") # -> "indexes" or "indices" + PL("millennium") # -> "millenniums" or "millennia" + PL("ganglion") # -> "ganglions" or "ganglia" + PL("octopus") # -> "octopuses" or "octopodes" + + +Lingua::EN::Inflect caters to such words by providing an +"alternate state" of inflection known as "classical mode". +By default, words are inflected using their contemporary English +plurals, but if classical mode is invoked, the more traditional +plural forms are returned instead. + +The exportable subroutine C controls this feature. +If C is called with no arguments, it unconditionally +invokes classical mode. If it is called with a single argument, it +turns all classical inflects on or off (depending on whether the argument is +true or false). If called with two or more arguments, those arguments +specify which aspects of classical behaviour are to be used. + +Thus: + + classical; # SWITCH ON CLASSICAL MODE + print PL("formula"); # -> "formulae" + + classical 0; # SWITCH OFF CLASSICAL MODE + print PL("formula"); # -> "formulas" + + classical $cmode; # CLASSICAL MODE IFF $cmode + print PL("formula"); # -> "formulae" (IF $cmode) + # -> "formulas" (OTHERWISE) + + classical herd=>1; # SWITCH ON CLASSICAL MODE FOR "HERD" NOUNS + print PL("wilderbeest"); # -> "wilderbeest" + + classical names=>1; # SWITCH ON CLASSICAL MODE FOR NAMES + print PL("sally"); # -> "sallies" + print PL("Sally"); # -> "Sallys" + +Note however that C has no effect on the inflection of words which +are now fully assimilated. Hence: + + PL("forum") # ALWAYS -> "forums" + PL("criterion") # ALWAYS -> "criteria" + +LEI assumes that a capitalized word is a person's name. So it forms the +plural according to the rules for names (which is that you don't +inflect, you just add -s or -es). You can choose to turn that behaviour +off (it's on by the default, even when the module isn't in classical +mode) by calling C< classical(names=>0) >; + +=head1 USER-DEFINED INFLECTIONS + +=head2 Adding plurals at run-time + +Lingua::EN::Inflect provides five exportable subroutines which allow +the programmer to override the module's behaviour for specific cases: + +=over 8 + +=item C + +The C subroutine takes a pair of string arguments: the singular and +plural forms of the noun being specified. The singular form +specifies a pattern to be interpolated (as C). +Any noun matching this pattern is then replaced by the string in the +second argument. The second argument specifies a string which is +interpolated after the match succeeds, and is then used as the plural +form. For example: + + def_noun 'cow' => 'kine'; + def_noun '(.+i)o' => '$1i'; + def_noun 'spam(mer)?' => '\\$\\%\\@#\\$\\@#!!'; + +Note that both arguments should usually be specified in single quotes, +so that they are not interpolated when they are specified, but later (when +words are compared to them). As indicated by the last example, care +also needs to be taken with certain characters in the second argument, +to ensure that they are not unintentionally interpolated during comparison. + +The second argument string may also specify a second variant of the plural +form, to be used when "classical" plurals have been requested. The beginning +of the second variant is marked by a '|' character: + + def_noun 'cow' => 'cows|kine'; + def_noun '(.+i)o' => '$1os|$1i'; + def_noun 'spam(mer)?' => '\\$\\%\\@#\\$\\@#!!|varmints'; + +If no classical variant is given, the specified plural form is used in +both normal and "classical" modes. + +If the second argument is C instead of a string, then the +current user definition for the first argument is removed, and the +standard plural inflection(s) restored. + +Note that in all cases, later plural definitions for a particular +singular form replace earlier definitions of the same form. For example: + + # FIRST, HIDE THE MODERN FORM.... + def_noun 'aviatrix' => 'aviatrices'; + + # LATER, HIDE THE CLASSICAL FORM... + def_noun 'aviatrix' => 'aviatrixes'; + + # FINALLY, RESTORE THE DEFAULT BEHAVIOUR... + def_noun 'aviatrix' => undef; + + +Special care is also required when defining general patterns and +associated specific exceptions: put the more specific cases I +the general pattern. For example: + + def_noun '(.+)us' => '$1i'; # EVERY "-us" TO "-i" + def_noun 'bus' => 'buses'; # EXCEPT FOR "bus" + +This "try-most-recently-defined-first" approach to matching +user-defined words is also used by C, C and C. + + +=item C + +The C subroutine takes three pairs of string arguments (that is, six +arguments in total), specifying the singular and plural forms of the three +"persons" of verb. As with C, the singular forms are specifications of +run-time-interpolated patterns, whilst the plural forms are specifications of +(up to two) run-time-interpolated strings: + + def_verb 'am' => 'are', + 'are' => 'are|art", + 'is' => 'are'; + + def_verb 'have' => 'have', + 'have' => 'have", + 'ha(s|th)' => 'have'; + +Note that as with C, modern/classical variants of plurals +may be separately specified, subsequent definitions replace previous +ones, and C'ed plural forms revert to the standard behaviour. + + +=item C + +The C subroutine takes a pair of string arguments, which specify +the singular and plural forms of the adjective being defined. +As with C and C, the singular forms are specifications of +run-time-interpolated patterns, whilst the plural forms are specifications of +(up to two) run-time-interpolated strings: + + def_adj 'this' => 'these', + def_adj 'red' => 'red|gules', + +As previously, modern/classical variants of plurals +may be separately specified, subsequent definitions replace previous +ones, and C'ed plural forms revert to the standard behaviour. + + +=item C and C + +The C and C subroutines each take a single argument, which +specifies a pattern. If a word passed to C or C matches this +pattern, it will be prefixed (unconditionally) with the corresponding indefinite +article. For example: + + def_a 'error'; + def_a 'in.+'; + + def_an 'mistake'; + def_an 'error'; + +As with the other C subroutines, such redefinitions are sequential +in effect so that, after the above example, "error" will be inflected with "an". + +=back + +=head2 The F<$HOME/.inflectrc> file + +When it is imported, Lingua::EN::Inflect executes (as Perl code) +the contents of any file named F<.inflectrc> which it finds in the +in the directory where F is installed, +or in the current home directory (C<$ENV{HOME}>), or in both. +Note that the code is executed within the Lingua::EN::Inflect +namespace. + +Hence the user or the local Perl guru can make appropriate calls to +C, C, etc. in one of these F<.inflectrc> files, to +permanently and universally modify the behaviour of the module. For example + + > cat /usr/local/lib/perl5/Text/Inflect/.inflectrc + + def_noun "UNIX" => "UN*X|UNICES"; + + def_verb "teco" => "teco", # LITERALLY: "to edit with TECO" + "teco" => "teco", + "tecos" => "teco"; + + def_a "Euler.*"; # "Yewler" TURNS IN HIS GRAVE + + +Note that calls to the C subroutines from within a program +will take precedence over the contents of the home directory +F<.inflectrc> file, which in turn takes precedence over the system-wide +F<.inflectrc> file. + + +=head1 DIAGNOSTICS + +On loading, if the Perl code in a F<.inflectrc> file is invalid +(syntactically or otherwise), an appropriate fatal error is issued. +A common problem is not ending the file with something that +evaluates to true (as the five C subroutines do). + +Using the five C subroutines directly in a program may also +result in fatal diagnostics, if a (singular) pattern or an interpolated +(plural) string is somehow invalid. + +Specific diagnostics related to user-defined inflections are: + +=over 8 + +=item C<"Bad user-defined singular pattern:\n\t %s"> + +The singular form of a user-defined noun or verb +(as defined by a call to C, C, C, +C or C) is not a valid Perl regular expression. The +actual Perl error message is also given. + +=item C<"Bad user-defined plural string: '%s'"> + +The plural form(s) of a user-defined noun or verb +(as defined by a call to C, C or C) +is not a valid Perl interpolated string (usually because it +interpolates some undefined variable). + +=item C<"Bad .inflectrc file (%s):\n %s"> + +Some other problem occurred in loading the named local +or global F<.inflectrc> file. The Perl error message (including +the line number) is also given. + +=back + +There are I diagnosable run-time error conditions for the actual +inflection subroutines, except C and hence no run-time +diagnostics. If the inflection subroutines are unable to form a plural +via a user-definition or an inbuilt rule, they just "guess" the +commonest English inflection: adding "-s" for nouns, removing "-s" for +verbs, and no inflection for adjectives. + +C can C with the following messages: + +=over 8 + +=item C<"Bad grouping option: %s"> + +The optional argument to C wasn't 1, 2 or 3. + +=item C<"Number out of range"> + +C was passed a number larger than +999,999,999,999,999,999,999,999,999,999,999,999 (that is: nine hundred +and ninety-nine decillion, nine hundred and ninety-nine nonillion, nine +hundred and ninety-nine octillion, nine hundred and ninety-nine +septillion, nine hundred and ninety-nine sextillion, nine hundred and +ninety-nine quintillion, nine hundred and ninety-nine quadrillion, nine +hundred and ninety-nine trillion, nine hundred and ninety-nine billion, +nine hundred and ninety-nine million, nine hundred and ninety-nine +thousand, nine hundred and ninety-nine :-) + +The problem is that C doesn't know any +words for number components bigger than "decillion". + + +=head1 OTHER ISSUES + +=head2 2nd Person precedence + +If a verb has identical 1st and 2nd person singular forms, but +different 1st and 2nd person plural forms, then when its plural is +constructed, the 2nd person plural form is always preferred. + +The author is not currently aware of any such verbs in English, but is +not quite arrogant enough to assume I that none exist. + + +=head2 Nominative precedence + +The singular pronoun "it" presents a special problem because its plural form +can vary, depending on its "case". For example: + + It ate my homework -> They ate my homework + It ate it -> They ate them + I fed my homework to it -> I fed my homework to them + +As a consequence of this ambiguity, C or C have been implemented +so that they always return the I plural (that is, "they"). + +However, when asked for the plural of an unambiguously I +"it" (namely, C, C, C, +etc.), both subroutines will correctly return the accusative plural +("to them", "from them", "with them", etc.) + + +=head2 The plurality of zero + +The rules governing the choice between: + + There were no errors. + +and + + There was no error. + +are complex and often depend more on I rather than I. +Hence it is infeasible to specify such rules algorithmically. + +Therefore, Lingua::EN::Text contents itself with the following compromise: If +the governing number is zero, inflections always return the plural form +unless the appropriate "classical" inflection is in effect, in which case the +singular form is always returned. + +Thus, the sequence: + + NUM(0); + print inflect "There PL(was) NO(choice)"; + +produces "There were no choices", whereas: + + classical 'zero'; # or: classical(zero=>1); + NUM(0); + print inflect "There PL(was) NO(choice)"; + +it will print "There was no choice". + + +=head2 Homographs with heterogeneous plurals + +Another context in which intent (and not content) sometimes determines +plurality is where two distinct meanings of a word require different +plurals. For example: + + Three basses were stolen from the band's equipment trailer. + Three bass were stolen from the band's aquarium. + + I put the mice next to the cheese. + I put the mouses next to the computers. + + Several thoughts about leaving crossed my mind. + Several thought about leaving across my lawn. + +Lingua::EN::Inflect handles such words in two ways: + +=over 8 + +=item * + +If both meanings of the word are the I part of speech (for +example, "bass" is a noun in both sentences above), then one meaning +is chosen as the "usual" meaning, and only that meaning's plural is +ever returned by any of the inflection subroutines. + +=item * + +If each meaning of the word is a different part of speech (for +example, "thought" is both a noun and a verb), then the noun's +plural is returned by C and C and the verb's plural is +returned only by C. + +=back + +Such contexts are, fortunately, uncommon (particularly +"same-part-of-speech" examples). An informal study of nearly 600 +"difficult plurals" indicates that C can be relied upon to "get +it right" about 98% of the time (although, of course, ichthyophilic +guitarists or cyber-behaviouralists may experience higher rates of +confusion). + +If the choice of a particular "usual inflection" is considered +inappropriate, it can always be reversed with a preliminary call +to the corresponding C subroutine. + +=head1 NOTE + +I'm not taking any further correspondence on: + +=over + +=item "octopi". + +Despite the populist pandering of certain New World dictionaries, the +plural is "octopuses" or (for the pendantic classicist) "octopodes". The +suffix "-pus" is Greek, not Latin, so the plural is "-podes", not "pi". + + +=item "virus". + +Had no plural in Latin (possibly because it was a mass noun). +The only plural is the Anglicized "viruses". + +=back + +=head1 AUTHORS + +Damian Conway (damian@conway.org) +Matthew Persico (ORD inflection) + + +=head1 BUGS AND IRRITATIONS + +The endless inconsistencies of English. + +(I report words for which the correct plural or +indefinite article is not formed, so that the reliability +of Lingua::EN::Inflect can be improved.) + + + +=head1 COPYRIGHT + + Copyright (c) 1997-2000, Damian Conway. All Rights Reserved. + This module is free software. It may be used, redistributed + and/or modified under the same terms as Perl itself. diff --git a/moose-class/exercises/t/lib/MooseClass/Tests.pm b/moose-class/exercises/t/lib/MooseClass/Tests.pm new file mode 100644 index 0000000..8bc112d --- /dev/null +++ b/moose-class/exercises/t/lib/MooseClass/Tests.pm @@ -0,0 +1,153 @@ +package MooseClass::Tests; + +use strict; +use warnings; + +use Lingua::EN::Inflect qw( PL_N ); +use Test::More 'no_plan'; + +sub tests01 { + local $Test::Builder::Level = $Test::Builder::Level + 1; + + has_meta('Person'); + + check_isa( 'Person', ['Moose::Object'] ); + + count_attrs( 'Person', 2 ); + + has_rw_attr( 'Person', $_ ) for qw( first_name last_name ); + + has_method( 'Person', 'full_name' ); + + no_droppings('Person'); + is_immutable('Person'); + + person01(); + + has_meta('Employee'); + + check_isa( 'Employee', [ 'Person', 'Moose::Object' ] ); + + count_attrs( 'Employee', 3 ); + + has_rw_attr( 'Employee', $_ ) for qw( position salary ); + has_ro_attr( 'Employee', 'ssn' ); + + has_overridden_method( 'Employee', 'full_name' ); + + employee01(); +} + +sub has_meta { + my $class = shift; + + ok( $class->can('meta'), "$class has a meta() method" ) + or BAIL_OUT("Cannot run tests against a class without a meta!"); +} + +sub check_isa { + my $class = shift; + my $parents = shift; + + my @isa = $class->meta->linearized_isa; + shift @isa; # returns $class as the first entry + + my $count = scalar @{$parents}; + my $noun = PL_N( 'parent', $count ); + + is( scalar @isa, $count, "$class has $count $noun" ); + + for ( my $i = 0; $i < @{$parents}; $i++ ) { + is( $isa[$i], $parents->[$i], "parent[$i] is $parents->[$i]" ); + } +} + +sub count_attrs { + my $class = shift; + my $count = shift; + + my $noun = PL_N( 'attribute', $count ); + is( scalar $class->meta->get_attribute_list, $count, + "$class defines $count $noun" ); +} + +sub has_rw_attr { + my $class = shift; + my $name = shift; + + ok( $class->meta->has_attribute($name), "$class has attribute - $name" ); + + my $attr = $class->meta->get_attribute($name); + + is( $attr->get_read_method, $name, + "$name attribute has a reader accessor - $name()" ); + is( $attr->get_write_method, $name, + "$name attribute has a writer accessor - $name()" ); +} + +sub has_ro_attr { + my $class = shift; + my $name = shift; + + ok( $class->meta->has_attribute($name), "$class has attribute - $name" ); + + my $attr = $class->meta->get_attribute($name); + + is( $attr->get_read_method, $name, + "$name attribute has a reader accessor - $name()" ); + is( $attr->get_write_method, undef, + "$name attribute does not have a writer" ); +} + +sub has_method { + my $class = shift; + my $name = shift; + + ok( $class->meta->has_method($name), "$class has a $name method" ); +} + +sub has_overridden_method { + my $class = shift; + my $name = shift; + + ok( $class->meta->has_method($name), "$class has a $name method" ); + + my $meth = $class->meta->get_method($name); + isa_ok( $meth, 'Moose::Meta::Method::Overridden' ); +} + +sub no_droppings { + my $class = shift; + + ok( !$class->can('has'), "no Moose droppings in $class" ); +} + +sub is_immutable { + my $class = shift; + + ok( $class->meta->is_immutable, "$class has been made immutable" ); +} + +sub person01 { + my $person = Person->new( + first_name => 'Bilbo', + last_name => 'Baggins', + ); + + is( $person->full_name, 'Bilbo Baggins', + 'full_name() is correctly implemented' ); +} + +sub employee01 { + my $employee = Employee->new( + first_name => 'Amanda', + last_name => 'Palmer', + position => 'Singer', + ); + + is( $employee->full_name, 'Amanda Palmer (Singer)', + 'full_name() is properly overriden in Employee' ); +} + + +1; diff --git a/moose-class/exercises/tarballs/Algorithm-C3-0.08.tar.gz b/moose-class/exercises/tarballs/Algorithm-C3-0.08.tar.gz new file mode 100644 index 0000000..4529dc7 Binary files /dev/null and b/moose-class/exercises/tarballs/Algorithm-C3-0.08.tar.gz differ diff --git a/moose-class/exercises/tarballs/Class-C3-0.21.tar.gz b/moose-class/exercises/tarballs/Class-C3-0.21.tar.gz new file mode 100644 index 0000000..d44d41f Binary files /dev/null and b/moose-class/exercises/tarballs/Class-C3-0.21.tar.gz differ diff --git a/moose-class/exercises/tarballs/Class-C3-XS-0.11.tar.gz b/moose-class/exercises/tarballs/Class-C3-XS-0.11.tar.gz new file mode 100644 index 0000000..01adf48 Binary files /dev/null and b/moose-class/exercises/tarballs/Class-C3-XS-0.11.tar.gz differ diff --git a/moose-class/exercises/tarballs/Class-MOP-0.85.tar.gz b/moose-class/exercises/tarballs/Class-MOP-0.85.tar.gz new file mode 100644 index 0000000..fb93993 Binary files /dev/null and b/moose-class/exercises/tarballs/Class-MOP-0.85.tar.gz differ diff --git a/moose-class/exercises/tarballs/Data-OptList-0.104.tar.gz b/moose-class/exercises/tarballs/Data-OptList-0.104.tar.gz new file mode 100644 index 0000000..c507e79 Binary files /dev/null and b/moose-class/exercises/tarballs/Data-OptList-0.104.tar.gz differ diff --git a/moose-class/exercises/tarballs/Devel-GlobalDestruction-0.02.tar.gz b/moose-class/exercises/tarballs/Devel-GlobalDestruction-0.02.tar.gz new file mode 100644 index 0000000..dce3a18 Binary files /dev/null and b/moose-class/exercises/tarballs/Devel-GlobalDestruction-0.02.tar.gz differ diff --git a/moose-class/exercises/tarballs/List-MoreUtils-0.22.tar.gz b/moose-class/exercises/tarballs/List-MoreUtils-0.22.tar.gz new file mode 100644 index 0000000..3daa285 Binary files /dev/null and b/moose-class/exercises/tarballs/List-MoreUtils-0.22.tar.gz differ diff --git a/moose-class/exercises/tarballs/MRO-Compat-0.11.tar.gz b/moose-class/exercises/tarballs/MRO-Compat-0.11.tar.gz new file mode 100644 index 0000000..7952561 Binary files /dev/null and b/moose-class/exercises/tarballs/MRO-Compat-0.11.tar.gz differ diff --git a/moose-class/exercises/tarballs/Moose-0.80.tar.gz b/moose-class/exercises/tarballs/Moose-0.80.tar.gz new file mode 100644 index 0000000..2d7fd31 Binary files /dev/null and b/moose-class/exercises/tarballs/Moose-0.80.tar.gz differ diff --git a/moose-class/exercises/tarballs/Params-Util-1.00.tar.gz b/moose-class/exercises/tarballs/Params-Util-1.00.tar.gz new file mode 100644 index 0000000..d8c90c0 Binary files /dev/null and b/moose-class/exercises/tarballs/Params-Util-1.00.tar.gz differ diff --git a/moose-class/exercises/tarballs/Scalar-List-Utils-1.21.tar.gz b/moose-class/exercises/tarballs/Scalar-List-Utils-1.21.tar.gz new file mode 100644 index 0000000..1fc8642 Binary files /dev/null and b/moose-class/exercises/tarballs/Scalar-List-Utils-1.21.tar.gz differ diff --git a/moose-class/exercises/tarballs/Scope-Guard-0.03.tar.gz b/moose-class/exercises/tarballs/Scope-Guard-0.03.tar.gz new file mode 100644 index 0000000..f9f96fd Binary files /dev/null and b/moose-class/exercises/tarballs/Scope-Guard-0.03.tar.gz differ diff --git a/moose-class/exercises/tarballs/Sub-Exporter-0.982.tar.gz b/moose-class/exercises/tarballs/Sub-Exporter-0.982.tar.gz new file mode 100644 index 0000000..056050a Binary files /dev/null and b/moose-class/exercises/tarballs/Sub-Exporter-0.982.tar.gz differ diff --git a/moose-class/exercises/tarballs/Sub-Install-0.925.tar.gz b/moose-class/exercises/tarballs/Sub-Install-0.925.tar.gz new file mode 100644 index 0000000..f7abc26 Binary files /dev/null and b/moose-class/exercises/tarballs/Sub-Install-0.925.tar.gz differ diff --git a/moose-class/exercises/tarballs/Sub-Name-0.04.tar.gz b/moose-class/exercises/tarballs/Sub-Name-0.04.tar.gz new file mode 100644 index 0000000..34c7d7e Binary files /dev/null and b/moose-class/exercises/tarballs/Sub-Name-0.04.tar.gz differ diff --git a/moose-class/slides/index.html b/moose-class/slides/index.html index dcae330..10d0339 100644 --- a/moose-class/slides/index.html +++ b/moose-class/slides/index.html @@ -1059,5 +1059,16 @@ use Moose; +
+

Classes - Exercises

+ +
$ cd exercises
+$ prove -lv t/00-prereq.t
+
+Missing anything? Install it. (see tarballs/)
+
+# prove -lv t/01-classes.t
+# 
+