|
1 module(..., package.seeall); |
|
2 |
|
3 require "luarocks.require" |
|
4 require "lpeg" |
|
5 |
|
6 -- Add case-insensitive string matching to Lpeg |
|
7 function lpeg.Pi(s) |
|
8 local patt = lpeg.P(true); |
|
9 for c in s:gmatch(".") do |
|
10 patt = patt * (lpeg.P(c:lower()) + lpeg.P(c:upper())); |
|
11 end |
|
12 return patt; |
|
13 end |
|
14 |
|
15 function lpeg.one_of(list) |
|
16 local patt = lpeg.P(false); |
|
17 for _, match in ipairs(list) do |
|
18 patt = patt + lpeg.Pi(match); |
|
19 end |
|
20 return patt; |
|
21 end |
|
22 |
|
23 local ordinal = lpeg.P{ lpeg.C(lpeg.R("09")^-2) * (lpeg.Pi("st") + lpeg.Pi("nd") + lpeg.Pi("rd") + lpeg.Pi("th")) + 1 * lpeg.V(1) }; |
|
24 local number = lpeg.R "09"^1 |
|
25 |
|
26 local day_name = lpeg.one_of {'monday', 'tuesday', 'wednesday', |
|
27 'thursday', 'friday', 'saturday', 'sunday'} |
|
28 |
|
29 local month_name = lpeg.one_of {'january', 'february', 'march', 'april', 'may', 'june', |
|
30 'july', 'august', 'september', 'october', 'november', 'december' } |
|
31 |
|
32 local year = (lpeg.R("09")^4)^-4 |
|
33 |
|
34 local unit_of_time = lpeg.one_of { 'second', 'minute', 'hour', 'day', 'week', 'month', 'year' } |
|
35 |
|
36 local time_of_day = lpeg.one_of { 'morning', 'noon', 'afternoon', 'evening', 'night', 'midnight' } |
|
37 local time_of_days = { morning = 09, noon = 12, afternoon = 13, evening = 17, night = 21, midnight = 00 } |
|
38 |
|
39 local quantity; |
|
40 local quantities = { |
|
41 ["a"] = 1; |
|
42 ["an"] = 1; |
|
43 |
|
44 ["a couple of"] = 2; |
|
45 |
|
46 ["a few"] = 3; |
|
47 ["several"] = 3; |
|
48 }; |
|
49 |
|
50 -- Create 'quantity' to match any of the quantities we know |
|
51 do |
|
52 local quantity_list = {}; |
|
53 for k in pairs(quantities) do |
|
54 quantity_list[#quantity_list+1] = k; |
|
55 end |
|
56 table.sort(quantity_list, function (a,b) return #a>#b; end); |
|
57 quantity = number + lpeg.one_of(quantity_list); |
|
58 end |
|
59 |
|
60 seconds_in_a = { second = 1 } |
|
61 seconds_in_a.minute = seconds_in_a.second * 60; |
|
62 seconds_in_a.hour = seconds_in_a.minute * 60; |
|
63 seconds_in_a.day = seconds_in_a.hour * 24; |
|
64 seconds_in_a.week = seconds_in_a.day * 7; |
|
65 seconds_in_a.month = seconds_in_a.week * 4; |
|
66 seconds_in_a.year = seconds_in_a.day * 365; |
|
67 |
|
68 local function get_time_part(time, part) |
|
69 return os.date("*t", time)[part]; |
|
70 end |
|
71 |
|
72 local function adjust_time(time, part, value) |
|
73 local split_time = os.date("*t", time); |
|
74 |
|
75 split_time[part] = value; |
|
76 |
|
77 return os.time(split_time); |
|
78 end |
|
79 |
|
80 function when(str, relative_to) |
|
81 local time = relative_to or os.time(); |
|
82 local P = lpeg.P; |
|
83 |
|
84 local patterns = |
|
85 { |
|
86 { P"tomorrow" / |
|
87 function () |
|
88 time = time + seconds_in_a.day; |
|
89 end }; |
|
90 { P"next week" / |
|
91 function () |
|
92 time = time + seconds_in_a.week; |
|
93 end }; |
|
94 { P"next year" / |
|
95 function () |
|
96 time = adjust_time(time, "year", get_time_part(time, "year") + 1); |
|
97 end }; |
|
98 { P"on "^0 * day_name / |
|
99 function (day_name) |
|
100 time = find_nearest_day_by_name(time, day_name); |
|
101 end }; |
|
102 { P"in "^0 * month_name / |
|
103 function (month_name) |
|
104 time = find_nearest_month_by_name(time, month_name); |
|
105 end }; |
|
106 { P"in "^0 * ( quantity * P" " * unit_of_time ) * (P"s"^-1) / |
|
107 function (number_and_unit) |
|
108 local number, unit = number_and_unit:gsub("^in ", ""):match("^(.+)%s+(.-)s?$"); |
|
109 |
|
110 number = quantities[number] or tonumber(number); |
|
111 |
|
112 time = time + seconds_in_a[unit] * number; |
|
113 end }; |
|
114 { (P"this " + P"in the " + P"at ")^0 * time_of_day / |
|
115 function (time_of_day) |
|
116 time_of_day = time_of_day:match("%S+$"); |
|
117 time = adjust_time(time, "hour", time_of_days[time_of_day]); |
|
118 if time_of_day == "noon" or time_of_day == "midnight" then |
|
119 time = adjust_time(time, "min", 00); |
|
120 else |
|
121 time = adjust_time(time, "min", 30); |
|
122 end |
|
123 end }; |
|
124 } |
|
125 |
|
126 local ret, pos; |
|
127 for _, pattern in pairs(patterns) do |
|
128 ret = lpeg.match(lpeg.P{ pattern[1] + 1 * lpeg.V(1) }, str); |
|
129 if ret then |
|
130 pos = ret; |
|
131 --print("Matches ".._.." until "..ret); |
|
132 end |
|
133 end |
|
134 |
|
135 return time, pos; |
|
136 end |
|
137 |