1- -- Copyright 2025 Stepan Klokocka. See LICENSE.
1+ -- Copyright 2005 Stepan Klokocka. See LICENSE.
22-- Crontab LPeg lexer.
33
44local lexer = require (' lexer' )
5- local token , word_match = lexer .token , lexer .word_match
6- local P , R , S , V = lpeg .P , lpeg .R , lpeg .S , lpeg .V
5+ local P , S , R = lpeg .P , lpeg .S , lpeg .R
76
8- local lex = lexer .new (' crontab ' )
7+ local lex = lexer .new (... )
98
10- -- Whitespace.
11- lex :add_rule (' whitespace' , token (lexer .WHITESPACE , lexer .space ^ 1 ))
9+ -- Define basic patterns
10+ local space = S (' \t ' )
11+ local newline = P (' \r\n ' ) + P (' \n ' ) + P (' \r ' )
12+ local digit = R (' 09' )
13+ local alpha = R (' az' , ' AZ' )
14+ local alnum = alpha + digit
1215
13- -- Comments.
14- lex :add_rule (' comment ' , token (lexer .COMMENT , ' # ' * lexer . nonnewline ^ 0 ))
16+ -- Whitespace
17+ lex :add_rule (' whitespace ' , lexer . token (lexer .WHITESPACE , space ^ 1 ))
1518
16- -- Numbers.
17- local digit = R (' 09' )
18- local number = digit ^ 1
19- lex :add_rule (' number' , token (lexer .NUMBER , number ))
19+ -- Comments
20+ lex :add_rule (' comment' , lexer .token (lexer .COMMENT , P (' #' ) * (P (1 ) - newline )^ 0 ))
2021
21- -- Cron operators.
22- local operators = S (' *,-/?' )
23- lex :add_rule (' operator' , token (lexer .OPERATOR , operators ))
22+ -- Numbers (field-specific)
23+ lex :add_rule (' minute' , lexer .token (' minute' , digit ^ 1 ))
24+ lex :add_rule (' hour' , lexer .token (' hour' , digit ^ 1 ))
25+ lex :add_rule (' dom' , lexer .token (' dom' , digit ^ 1 )) -- day of month
26+ lex :add_rule (' month_num' , lexer .token (' month_num' , digit ^ 1 ))
27+ lex :add_rule (' dow_num' , lexer .token (' dow_num' , digit ^ 1 )) -- day of week
28+ lex :add_rule (' number' , lexer .token (lexer .NUMBER , digit ^ 1 ))
2429
25- -- Special time strings.
26- local special_times = word_match {
30+ -- Special time strings
31+ local special_times = lexer . word_match {
2732 ' @yearly' , ' @annually' , ' @monthly' , ' @weekly' , ' @daily' , ' @midnight' , ' @hourly' , ' @reboot'
2833}
29- lex :add_rule (' special_time' , token (' special_time ' , special_times ))
34+ lex :add_rule (' special_time' , lexer . token (lexer . KEYWORD , special_times ))
3035
31- -- Month names (can be used in month field).
32- local month_names = word_match {
36+ -- Month names
37+ local month_names = lexer . word_match {
3338 ' jan' , ' feb' , ' mar' , ' apr' , ' may' , ' jun' ,
34- ' jul' , ' aug' , ' sep' , ' oct' , ' nov' , ' dec' ,
35- ' january' , ' february' , ' march' , ' april' , ' may' , ' june' ,
36- ' july' , ' august' , ' september' , ' october' , ' november' , ' december'
39+ ' jul' , ' aug' , ' sep' , ' oct' , ' nov' , ' dec'
3740}
41+ lex :add_rule (' month_name' , lexer .token (lexer .KEYWORD , month_names ))
3842
39- -- Day names (can be used in day-of-week field).
40- local day_names = word_match {
41- ' sun' , ' mon' , ' tue' , ' wed' , ' thu' , ' fri' , ' sat' ,
42- ' sunday' , ' monday' , ' tuesday' , ' wednesday' , ' thursday' , ' friday' , ' saturday'
43+ -- Day names
44+ local day_names = lexer .word_match {
45+ ' sun' , ' mon' , ' tue' , ' wed' , ' thu' , ' fri' , ' sat'
4346}
47+ lex :add_rule (' day_name' , lexer .token (lexer .KEYWORD , day_names ))
4448
45- -- Month and day names.
46- lex :add_rule (' month_name' , token (' month_name' , month_names ))
47- lex :add_rule (' day_name' , token (' day_name' , day_names ))
48-
49- -- Environment variables (VAR=value).
50- local env_var = lexer .alpha * (lexer .alnum + ' _' )^ 0 * ' =' * lexer .nonnewline ^ 0
51- lex :add_rule (' environment' , token (' environment' , env_var ))
52-
53- -- Strings (quoted).
54- local sq_str = lexer .range (" '" , true )
55- local dq_str = lexer .range (' "' , true )
56- lex :add_rule (' string' , token (lexer .STRING , sq_str + dq_str ))
57-
58- -- Commands (everything after the 5 time fields).
59- -- This is a complex rule that matches after whitespace-separated fields.
60- local field = (number + operators + month_names + day_names + lexer .alpha ^ 1 )
61- local time_spec = special_times + (field * (lexer .space ^ 1 * field )^ 4 )
62- local command_start = time_spec * lexer .space ^ 1
63- local command = lexer .nonnewline ^ 1
64-
65- -- Add a rule for the entire cron line structure.
66- local cron_line = P {
67- ' line' ,
68- line = V (' special_line' ) + V (' env_line' ) + V (' cron_entry_line' ) + V (' comment_line' ),
69-
70- special_line = token (' special_time' , special_times ) *
71- lexer .space ^ 1 *
72- token (' command' , lexer .nonnewline ^ 0 ),
73-
74- env_line = token (' environment' , lexer .alpha * (lexer .alnum + ' _' )^ 0 ) *
75- token (lexer .OPERATOR , ' =' ) *
76- token (' env_value' , lexer .nonnewline ^ 0 ),
77-
78- comment_line = token (lexer .COMMENT , ' #' * lexer .nonnewline ^ 0 ),
79-
80- cron_entry_line = V (' minute_field' ) * lexer .space ^ 1 *
81- V (' hour_field' ) * lexer .space ^ 1 *
82- V (' dom_field' ) * lexer .space ^ 1 *
83- V (' month_field' ) * lexer .space ^ 1 *
84- V (' dow_field' ) * lexer .space ^ 1 *
85- token (' command' , lexer .nonnewline ^ 0 ),
86-
87- minute_field = token (' minute' , V (' field_content' )),
88- hour_field = token (' hour' , V (' field_content' )),
89- dom_field = token (' dom' , V (' field_content' )),
90- month_field = token (' month' , V (' field_content' ) + month_names ),
91- dow_field = token (' dow' , V (' field_content' ) + day_names ),
92-
93- field_content = (number + operators + ' ?' )^ 1
94- }
49+ -- Cron operators
50+ lex :add_rule (' operator' , lexer .token (lexer .OPERATOR , S (' *,-/?' )))
9551
96- -- Simplified approach - use individual rules and rely on order.
97- -- Identifiers (for unrecognized words that might be part of commands).
98- lex :add_rule (' identifier' , token (lexer .IDENTIFIER , lexer .alpha * (lexer .alnum + ' _' )^ 0 ))
99-
100- -- Everything else (commands, paths, etc.).
101- lex :add_rule (' default' , token (lexer .DEFAULT , lexer .any ))
102-
103- -- Define token styles.
104- local styles = {
105- special_time = lexer .STYLE_KEYWORD ,
106- environment = lexer .STYLE_VARIABLE ,
107- env_value = lexer .STYLE_STRING ,
108- month_name = lexer .STYLE_KEYWORD .. ' ,bold' ,
109- day_name = lexer .STYLE_KEYWORD .. ' ,bold' ,
110- command = lexer .STYLE_FUNCTION ,
111- minute = lexer .STYLE_NUMBER .. ' ,fore:#FF6B35' , -- Orange for minute
112- hour = lexer .STYLE_NUMBER .. ' ,fore:#4ECDC4' , -- Teal for hour
113- dom = lexer .STYLE_NUMBER .. ' ,fore:#45B7D1' , -- Blue for day of month
114- month = lexer .STYLE_NUMBER .. ' ,fore:#96CEB4' , -- Green for month
115- dow = lexer .STYLE_NUMBER .. ' ,fore:#FFEAA7' -- Yellow for day of week
116- }
52+ -- Environment variables
53+ lex :add_rule (' environment' , lexer .token (' environment' , alpha * (alnum + P (' _' ))^ 0 * P (' =' ) * (P (1 ) - newline )^ 0 ))
54+
55+ -- Commands (everything after time fields on a line)
56+ lex :add_rule (' command' , lexer .token (' command' , alpha * (P (1 ) - space - newline )^ 0 ))
57+
58+ -- Strings
59+ local sq_str = P (" '" ) * (P (1 ) - P (" '" ))^ 0 * P (" '" )
60+ local dq_str = P (' "' ) * (P (1 ) - P (' "' ))^ 0 * P (' "' )
61+ lex :add_rule (' string' , lexer .token (lexer .STRING , sq_str + dq_str ))
62+
63+ -- Identifiers
64+ lex :add_rule (' identifier' , lexer .token (lexer .IDENTIFIER , alpha * (alnum + S (' _.-' ))^ 0 ))
65+
66+ -- Default
67+ lex :add_rule (' default' , lexer .token (lexer .DEFAULT , P (1 )))
68+
69+ -- Add custom styles for cron fields
70+ lex :add_style (' minute' , lexer .styles .number )
71+ lex :add_style (' hour' , lexer .styles .number )
72+ lex :add_style (' dom' , lexer .styles .number )
73+ lex :add_style (' month_num' , lexer .styles .number )
74+ lex :add_style (' dow_num' , lexer .styles .number )
75+ lex :add_style (' environment' , lexer .styles .variable )
76+ lex :add_style (' command' , lexer .styles [' function' ])
11777
118- -- Apply styles.
119- for token_name , style in pairs (styles ) do
120- lex :add_style (token_name , style )
121- end
122-
123- -- Folding (minimal - just for readability).
124- -- Fold on blank lines to separate cron entries.
125- lex :add_fold_point (lexer .WHITESPACE , lexer .newline ^ 2 , ' ' )
126-
127- -- Word lists for autocompletion (commented out as they cause errors in some Scintillua versions).
128- -- If your Scintillua version supports word lists, uncomment these:
129- -- lex:set_word_list(lexer.KEYWORD, {
130- -- '@yearly', '@annually', '@monthly', '@weekly', '@daily', '@midnight', '@hourly', '@reboot'
131- -- })
132- -- lex:set_word_list('month', {
133- -- 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'
134- -- })
135- -- lex:set_word_list('day', {
136- -- 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
137- -- })
78+ -- Add folding points (fold comment blocks)
79+ lex :add_fold_point (lexer .COMMENT , ' #' , newline )
13880
13981return lex
0 commit comments