1 | #!/usr/bin/env bash
|
---|
2 |
|
---|
3 | ARGV=$@
|
---|
4 |
|
---|
5 | __tick_error() {
|
---|
6 | echo "TICKTICK PARSING ERROR: "$1
|
---|
7 | }
|
---|
8 |
|
---|
9 | # This is from https://github.com/dominictarr/JSON.sh
|
---|
10 | # See LICENSE for more info. {{{
|
---|
11 | __tick_json_tokenize() {
|
---|
12 | local ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
|
---|
13 | local CHAR='[^[:cntrl:]"\\]'
|
---|
14 | local STRING="\"$CHAR*($ESCAPE$CHAR*)*\""
|
---|
15 | local VARIABLE="\\\$[A-Za-z0-9_]*"
|
---|
16 | local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?'
|
---|
17 | local KEYWORD='null|false|true'
|
---|
18 | local SPACE='[[:space:]]+'
|
---|
19 | egrep -ao "$STRING|$VARIABLE|$NUMBER|$KEYWORD|$SPACE|." --color=never | egrep -v "^$SPACE$" # eat whitespace
|
---|
20 | }
|
---|
21 |
|
---|
22 | __tick_json_parse_array() {
|
---|
23 | local index=0
|
---|
24 | local ary=''
|
---|
25 |
|
---|
26 | read -r Token
|
---|
27 |
|
---|
28 | case "$Token" in
|
---|
29 | ']') ;;
|
---|
30 | *)
|
---|
31 | while :
|
---|
32 | do
|
---|
33 | __tick_json_parse_value "$1" "`printf "%012d" $index`"
|
---|
34 |
|
---|
35 | (( index++ ))
|
---|
36 | ary+="$Value"
|
---|
37 |
|
---|
38 | read -r Token
|
---|
39 | case "$Token" in
|
---|
40 | ']') break ;;
|
---|
41 | ',') ary+=_ ;;
|
---|
42 | *)
|
---|
43 | __tick_error "Array syntax malformed"
|
---|
44 | break ;;
|
---|
45 | esac
|
---|
46 | read -r Token
|
---|
47 | done
|
---|
48 | ;;
|
---|
49 | esac
|
---|
50 | }
|
---|
51 |
|
---|
52 | __tick_json_parse_object() {
|
---|
53 | local key
|
---|
54 | local obj=''
|
---|
55 | read -r Token
|
---|
56 |
|
---|
57 | case "$Token" in
|
---|
58 | '}') ;;
|
---|
59 | *)
|
---|
60 | while :
|
---|
61 | do
|
---|
62 | # The key, it should be valid
|
---|
63 | case "$Token" in
|
---|
64 | '"'*'"'|\$[A-Za-z0-9_]*) key=$Token ;;
|
---|
65 | # If we get here then we aren't on a valid key
|
---|
66 | *)
|
---|
67 | __tick_error "Object without a Key"
|
---|
68 | break
|
---|
69 | ;;
|
---|
70 | esac
|
---|
71 |
|
---|
72 | # A colon
|
---|
73 | read -r Token
|
---|
74 |
|
---|
75 | # The value
|
---|
76 | read -r Token
|
---|
77 | __tick_json_parse_value "$1" "$key"
|
---|
78 | obj+="$key:$Value"
|
---|
79 |
|
---|
80 | read -r Token
|
---|
81 | case "$Token" in
|
---|
82 | '}') break ;;
|
---|
83 | ',') obj+=_ ;;
|
---|
84 | esac
|
---|
85 | read -r Token
|
---|
86 | done
|
---|
87 | ;;
|
---|
88 | esac
|
---|
89 | }
|
---|
90 |
|
---|
91 | __tick_json_parse_value() {
|
---|
92 | local jpath="${1:+$1_}$2"
|
---|
93 | local prej=${jpath//\"/}
|
---|
94 |
|
---|
95 | [ "$prej" ] && prej="_$prej"
|
---|
96 | [ "$prej" ] && prej=${prej/-/__hyphen__}
|
---|
97 |
|
---|
98 | case "$Token" in
|
---|
99 | '{') __tick_json_parse_object "$jpath" ;;
|
---|
100 | '[') __tick_json_parse_array "$jpath" ;;
|
---|
101 |
|
---|
102 | *)
|
---|
103 | Value=$Token
|
---|
104 | Path="$Prefix$prej"
|
---|
105 | Path=${Path/#_/}
|
---|
106 | echo __tick_data_${Path// /}=$Value
|
---|
107 | ;;
|
---|
108 | esac
|
---|
109 | }
|
---|
110 |
|
---|
111 | __tick_json_parse() {
|
---|
112 | read -r Token
|
---|
113 | __tick_json_parse_value
|
---|
114 | read -r Token
|
---|
115 | }
|
---|
116 | # }}} End of code from github
|
---|
117 |
|
---|
118 | # Since the JSON parser is just json parser, and we have a runtime
|
---|
119 | # and assignments built on to this, along with javascript like referencing
|
---|
120 | # there is a two-pass system, just because it was easier to code.
|
---|
121 | #
|
---|
122 | # This one separates out the valid JSON from the runtime library support
|
---|
123 | # and little fo' language that this is coded in.
|
---|
124 | __tick_fun_tokenize_expression() {
|
---|
125 | CHAR='[0-9]*[A-Za-z_$\\][0-9]*'
|
---|
126 | FUNCTION="(push|pop|shift|items|delete|length)[[:space:]]*\("
|
---|
127 | NUMBER='[0-9]*'
|
---|
128 | STRING="$CHAR*($CHAR*)*"
|
---|
129 | PAREN="[()]"
|
---|
130 | QUOTE="[\"\']"
|
---|
131 | SPACE='[[:space:]]+'
|
---|
132 | egrep -ao "$FUNCTION|$STRING|$QUOTE|$PAREN|$NUMBER|$SPACE|." --color=never |\
|
---|
133 | sed "s/^/S/g;s/$/E/g" # Make sure spaces are respected
|
---|
134 | }
|
---|
135 |
|
---|
136 | __tick_fun_parse_expression() {
|
---|
137 | while read -r token; do
|
---|
138 | token=${token/#S/}
|
---|
139 | token=${token/%E/}
|
---|
140 |
|
---|
141 | if [ $done ]; then
|
---|
142 | suffix+="$token"
|
---|
143 | else
|
---|
144 | case "$token" in
|
---|
145 | #
|
---|
146 | # The ( makes sure that you can do key.push = 1, not that you would, but
|
---|
147 | # avoiding having reserved words lowers the barrier to entry. Try doing
|
---|
148 | # say function debugger() {} in javascript and then run it in firefox. That's
|
---|
149 | # a fun one.
|
---|
150 | #
|
---|
151 | # So, it's probably better to be as lenient as possible when dealing with
|
---|
152 | # syntax like this.
|
---|
153 | #
|
---|
154 | 'push('|'pop('|'shift('|'items('|'delete('|'length(') function=$token ;;
|
---|
155 | ')')
|
---|
156 | function=${function/%(/}
|
---|
157 |
|
---|
158 | #
|
---|
159 | # Since bash only returns integers, we have to have a significant hack in order
|
---|
160 | # to return a string and then do something to the object. Basically, everything
|
---|
161 | # gets slammed inline.
|
---|
162 | #
|
---|
163 | # Q: Why don't you just reserve a global and then have the subfunction assign to it?
|
---|
164 | #
|
---|
165 | # A: Because the assignment has to happen prior to the function running. There's a number
|
---|
166 | # of syntax tricks where you can basically emulate "pointers", but then the coder would
|
---|
167 | # have to know about this "pointer" idea and then deal with their variables a different
|
---|
168 | # way.
|
---|
169 | #
|
---|
170 | # ---------
|
---|
171 | #
|
---|
172 | # Q: Why don't you just do stuff in a sub-shell and then make sure you emit things in
|
---|
173 | # something like a ( ) or a ` ` block?
|
---|
174 | #
|
---|
175 | # A: Because environments get copied into the subshell and then you'd be modifying the
|
---|
176 | # copy, not the original data. After the subshell ended, the original environment
|
---|
177 | # would stay, unmodified.
|
---|
178 | #
|
---|
179 | # ---------
|
---|
180 | #
|
---|
181 | # Q: Why don't you use the file system and do some magic with subthreads or something?
|
---|
182 | #
|
---|
183 | # A: Really? This should have side-effects? In programming there is something called
|
---|
184 | # the principle of least astonishment. In a way, the implementation below somewhat
|
---|
185 | # breaks that principle. However, using a file system or doing something really
|
---|
186 | # funky like that, would violate that principle far more.
|
---|
187 | #
|
---|
188 | # ---------
|
---|
189 | #
|
---|
190 | # But really, I sincerely hate the current solution. If you have a better idea, please
|
---|
191 | # please, open a dialog with me.
|
---|
192 | #
|
---|
193 | case $function in
|
---|
194 | items) echo '${!__tick_data_'"$Prefix"'*}' ;;
|
---|
195 | delete) echo 'unset __tick_data_'${Prefix/%_/} ;;
|
---|
196 | pop) echo '"$( __tick_runtime_last ${!__tick_data_'"$Prefix"'*} )"; __tick_runtime_pop ${!__tick_data_'"$Prefix"'*}' ;;
|
---|
197 | shift) echo '`__tick_runtime_first ${!__tick_data_'"$Prefix"'*}`; __tick_runtime_shift ${!__tick_data_'"$Prefix"'*}' ;;
|
---|
198 | length) echo '`__tick_runtime_length ${!__tick_data_'"$Prefix"'*}`' ;;
|
---|
199 | *) echo "__tick_runtime_$function \"$arguments\" __tick_data_$Prefix "'${!__tick_data_'"$Prefix"'*}'
|
---|
200 | esac
|
---|
201 | unset function
|
---|
202 |
|
---|
203 | return
|
---|
204 | ;;
|
---|
205 |
|
---|
206 | [0-9]*[A-Za-z]*[0-9]*) [ -n "$function" ] && arguments+="$token" || Prefix+="$token" ;;
|
---|
207 |
|
---|
208 | [0-9]*) Prefix+=`printf "%012d" $token` ;;
|
---|
209 | '['|.) Prefix+=_ ;;
|
---|
210 | '"'|"'"|']') ;;
|
---|
211 | =) done=1 ;;
|
---|
212 | # Only respect a space if its in the args.
|
---|
213 | ' ') [ -n "$function" ] && arguments+="$token" ;;
|
---|
214 | *) [ -n "$function" ] && arguments+="$token" || Prefix+="$token" ;;
|
---|
215 | esac
|
---|
216 | fi
|
---|
217 | done
|
---|
218 |
|
---|
219 | if [ "$suffix" ]; then
|
---|
220 | echo "$suffix" | __tick_json_tokenize | __tick_json_parse
|
---|
221 | else
|
---|
222 | Prefix=${Prefix/-/__hyphen__}
|
---|
223 | echo '${__tick_data_'$Prefix'}'
|
---|
224 | fi
|
---|
225 | }
|
---|
226 |
|
---|
227 | __tick_fun_parse_tickcount_reset() {
|
---|
228 | # If the tick count is 1 then the backtick we encountered was a
|
---|
229 | # shell code escape. These ticks need to be preserved for the script.
|
---|
230 | if (( ticks == 1 )); then
|
---|
231 | code+='`'
|
---|
232 | fi
|
---|
233 |
|
---|
234 | # This resets the backtick counter so that `some shell code` doesn't
|
---|
235 | # trip up the tokenizer
|
---|
236 | ticks=0
|
---|
237 | }
|
---|
238 |
|
---|
239 | # The purpose of this function is to separate out the Bash code from the
|
---|
240 | # special "tick tick" code. We do this by hijacking the IFS and reading
|
---|
241 | # in a single character at a time
|
---|
242 | __tick_fun_parse() {
|
---|
243 | IFS=
|
---|
244 |
|
---|
245 | # code oscillates between being bash or tick tick blocks.
|
---|
246 | code=''
|
---|
247 |
|
---|
248 | # By using -n, we are given that a newline will be an empty token. We
|
---|
249 | # can certainly test for that.
|
---|
250 | while read -r -n 1 token; do
|
---|
251 | case "$token" in
|
---|
252 | '`')
|
---|
253 |
|
---|
254 | # To make sure that we find two sequential backticks, we reset the counter
|
---|
255 | # if it's not a backtick.
|
---|
256 | if (( ++ticks == 2 )); then
|
---|
257 |
|
---|
258 | # Whether we are in the stanza or not, is controlled by a different
|
---|
259 | # variable
|
---|
260 | if (( tickFlag == 1 )); then
|
---|
261 | tickFlag=0
|
---|
262 | [ "$code" ] && echo -n "`echo $code | __tick_fun_tokenize_expression | __tick_fun_parse_expression`"
|
---|
263 | else
|
---|
264 | tickFlag=1
|
---|
265 | echo -n "$code"
|
---|
266 | fi
|
---|
267 |
|
---|
268 | # If we have gotten this deep, then we are toggling between backtick
|
---|
269 | # and bash. So se should unset the code.
|
---|
270 | unset code
|
---|
271 | fi
|
---|
272 | ;;
|
---|
273 |
|
---|
274 | '')
|
---|
275 | __tick_fun_parse_tickcount_reset
|
---|
276 |
|
---|
277 | # this is a newline. If we are in ticktick, then we want to consume
|
---|
278 | # them for the parser later on. If we are in bash, then we want to
|
---|
279 | # preserve them. We do this by emitting our buffer and then clearing
|
---|
280 | # it
|
---|
281 | if (( tickFlag == 0 )); then
|
---|
282 | echo "$code"
|
---|
283 | unset code
|
---|
284 | fi
|
---|
285 |
|
---|
286 | ;;
|
---|
287 |
|
---|
288 | *)
|
---|
289 | __tick_fun_parse_tickcount_reset
|
---|
290 |
|
---|
291 | # This is a buffer of the current code, either bash or backtick
|
---|
292 | code+="$token"
|
---|
293 | ;;
|
---|
294 | esac
|
---|
295 | done
|
---|
296 | }
|
---|
297 |
|
---|
298 | __tick_fun_tokenize() {
|
---|
299 | # This makes sure that when we rerun the code that we are
|
---|
300 | # interpreting, we don't try to interpret it again.
|
---|
301 | export __tick_var_tokenized=1
|
---|
302 |
|
---|
303 | # Using bash's caller function, which is for debugging, we
|
---|
304 | # can find out the name of the program that called us. We
|
---|
305 | # then cat the calling program and push it through our parser
|
---|
306 | local code=$(cat `caller 1 | cut -d ' ' -f 3` | __tick_fun_parse)
|
---|
307 |
|
---|
308 | # Before the execution we search to see if we emitted any parsing errors
|
---|
309 | hasError=`echo "$code" | grep "TICKTICK PARSING ERROR" | wc -l`
|
---|
310 |
|
---|
311 | if [ $__tick_var_debug ]; then
|
---|
312 | printf "%s\n" "$code"
|
---|
313 | exit 0
|
---|
314 | fi
|
---|
315 |
|
---|
316 | # If there are no errors, then we go ahead
|
---|
317 | if (( hasError == 0 )); then
|
---|
318 | # Take the output and then execute it
|
---|
319 |
|
---|
320 | bash -c "$code" -- $ARGV
|
---|
321 | else
|
---|
322 | echo "Parsing Error Detected, see below"
|
---|
323 |
|
---|
324 | # printf observes the new lines
|
---|
325 | printf "%s\n" "$code"
|
---|
326 | echo "Parsing stopped here."
|
---|
327 | fi
|
---|
328 |
|
---|
329 | exit
|
---|
330 | }
|
---|
331 |
|
---|
332 | ## Runtime {
|
---|
333 | __tick_runtime_length() { echo $#; }
|
---|
334 | __tick_runtime_first() { echo ${!1}; }
|
---|
335 | __tick_runtime_last() { eval 'echo $'${!#}; }
|
---|
336 | __tick_runtime_pop() { eval unset ${!#}; }
|
---|
337 |
|
---|
338 | __tick_runtime_shift() {
|
---|
339 | local left=
|
---|
340 | local right=
|
---|
341 |
|
---|
342 | for (( i = 1; i <= $# + 1; i++ )) ; do
|
---|
343 | if [ "$left" ]; then
|
---|
344 | eval "$left=\$$right"
|
---|
345 | fi
|
---|
346 | left=$right
|
---|
347 | right=${!i}
|
---|
348 | done
|
---|
349 | eval unset $left
|
---|
350 | }
|
---|
351 | __tick_runtime_push() {
|
---|
352 | local value="${1/\'/\\\'}"
|
---|
353 | local base=$2
|
---|
354 | local lastarg=${!#}
|
---|
355 |
|
---|
356 | let nextval=${lastarg/$base/}+1
|
---|
357 | nextval=`printf "%012d" $nextval`
|
---|
358 |
|
---|
359 | eval $base$nextval=\'$value\'
|
---|
360 | }
|
---|
361 |
|
---|
362 | tickParse() {
|
---|
363 | eval `echo "$1" | __tick_json_tokenize | __tick_json_parse | tr '\n' ';'`
|
---|
364 | }
|
---|
365 | ## } End of Runtime
|
---|
366 |
|
---|
367 |
|
---|
368 | [ $__tick_var_tokenized ] || __tick_fun_tokenize
|
---|