-
Notifications
You must be signed in to change notification settings - Fork 0
/
jade.lua
571 lines (494 loc) · 15.1 KB
/
jade.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
--[[
# comments come after numsign
multiline comments technically exist because all must end with a semicolon
a semicolon ends the current statement and also resets the stack
NOTE: newlines are replaced with spaces and tabs are removed ;
c set; # sets the next variable to be stored as c then resets the stack;
2 5 +; # inserts 2 to the stack, inserts 5 to the stack, then adds them (and sets if we defined a set point);
# we can get c by just typing c
strings can be created and wrapped 3 ways: <>, ", and '
however, <> should ONLY be used for function operations.
keep in mind that this language works from smallest -> largest in terms of importance.
for example, when defining a function: args name <operations> funk
args are of least importance, then the name for the operation,
then the actual operations of the function, and finally "funk" used to define it.
to define a variable: 5 cat sdef; # value, the identifying name,
then the keyword to define it. ;
]]
-- utils
local first, last = 1, 2 -- while it says first, it's really just the one before the last
local function ToTable( str )
local tbl = {}
for i = 1, string.len( str ) do
tbl[i] = string.sub( str, i, i )
end
return tbl
end
local function tCopy() end
tCopy = function( t, lookup_table )
local copy = {}
for k, v in pairs( t ) do
copy[ k ] = type( v ) == "table" and tCopy(v) or v
end
return copy
end
local function tobool( val )
if val == "false" then return false end
if val == "true" then return true end
return nil
end
-- now let's get into this shit
-- define globals, woo.
local global = {}
setmetatable( global, {
__index = function( self, key )
return _G[ key ]
end,
})
-- we might be calling this inside a keyword, so let's re-define it later
local function ExecuteJade() end
-- same
local function kw() end
-- all keywords get the following passed: stack, info, data
-- note that a list of the stack data is stack.data
local keywords = {
-- prepares a variable to be defined with another function or operation
[ "set" ] = function( stack, info )
info.set = stack( last )
end,
-- wipes the variable done by set
[ "reset" ] = function()
info.set = false
end,
-- wipes the stack
[ "wipe" ] = function( stack )
stack.data = {}
end,
-- deletes a variable
[ "del" ] = function( stack, info, data )
data[ stack(last) ] = nil
end,
-- deletes all variables found from stack
[ "dels" ] = function( stack, info, data )
for k, v in pairs( stack.data ) do
data[ v ] = nil
end
end,
-- defines a variable and also performs /set/ on the variable
[ "def" ] = function( stack, info, data )
data[ stack(last) ] = stack(first)
info.set = stack(first)
end,
-- defines a variable yet does not perform /set/ on the variable
[ "sdef" ] = function( stack, info, data )
data[ stack(last) ] = stack(first)
end,
-- wipes all but the last item in the stack
[ "stage" ] = function( stack )
local keep = stack(last)
stack.data = {}
return keep
end,
-- swaps the first and the last parts in the stack
[ "swap" ] = function( stack, info, data )
local fir, sec = stack(first), stack(last)
stack.data[ #stack.data - 1 ] = sec
stack.data[ #stack.data ] = fir
end,
-- basic math!
[ "+" ] = function( stack, info, data )
return type(stack(first)) == "string" and stack(first) .. stack(last) or
stack(first) + stack(last)
end,
[ "-" ] = function( stack, info, data )
return stack(first) - stack(last)
end,
[ "*" ] = function( stack, info, data )
return stack(first) * stack(last)
end,
[ "/" ] = function( stack, info, data )
return stack(first) / stack(last)
end,
-- logic
[ "==" ] = function( stack, info, data )
return stack(first) == stack(last)
end,
[ "!=" ] = function( stack, info, data )
return stack(first) ~= stack(last)
end,
[ "conclude" ] = function( stack, info, data )
for k, v in pairs( stack.data ) do
if not v then return false end
end
kw( "wipe", stack, info, data )
return true
end,
-- param <if> <if not> ifelse;
[ "ifelse" ] = function( stack, info, data )
local args = tCopy( stack.data )
local ifnot = table.remove( args, #args )
local If = table.remove( args, #args )
local param = table.remove( args, #args )
ExecuteJade( param and If.cmd or ifnot.cmd, {}, data )
end,
[ "or" ] = function( stack, info, data )
return stack(first) or stack(last)
end,
-- loop functionality: key value table <operations> loop;
[ "loop" ] = function( stack, info, data )
local args = tCopy( stack.data )
local operation = table.remove( args, #args )
local array = table.remove( args, #args )
local val = #args > 0 and table.remove( args, #args ) or nil
local key = #args > 0 and table.remove( args, #args ) or nil
if type(operation) ~= "table" or not operation.funk then
error( "invalid operation given to loop (<operation> expected, got " .. type(operation) .. ")" )
return
end
if type(array) ~= "table" then
error( "bad array given to loop (table expected, got " .. type(array) .. ")" )
return
end
for k, v in pairs( array ) do
ExecuteJade( operation.cmd, { [ key or "__loopkey" ] = k, [ val or "__loopkey" ] = v }, data )
end
end,
-- arrays
[ "array:new" ] = function( stack, info, data )
return {}
end,
[ "array:insert" ] = function( stack, info, data )
if type(stack(last)) ~= "table" then
error( "last type not array (table expected, got " .. type(stack(last)) .. ", " .. tostring(stack(last)) .. ")" )
return
end
table.insert( stack(last), stack(first) )
end,
[ "array:remove" ] = function( stack, info, data )
if type(stack(last)) ~= "table" then
error( "last type not array (table expected, got " .. type(stack(last)) .. ")" )
return
end
table.remove( stack(last), stack(first) )
end,
[ "array:set" ] = function( stack, info, data )
if type(stack(last)) ~= "table" then
error( "last type not array (table expected, got " .. type(stack(last)) .. ", " .. tostring(stack(last)) .. ")" )
return
end
local args = tCopy( stack.data )
local tab = table.remove( args, #args )
local key = table.remove( args, #args )
local value = table.remove( args, #args )
stack(last)[key] = value
end,
[ "array:delete" ] = function( stack, info, data )
if type(stack(last)) ~= "table" then
error( "last type not array (table expected, got " .. type(stack(last)) .. ")" )
return
end
stack(last)[stack(first)] = nil
end,
[ "array:concat" ] = function( stack, info, data )
if type(stack(last)) ~= "table" then
error( "last type not array (table expected, got " .. type(stack(last)) .. ")" )
return
end
return table.concat( stack(last), stack(first) )
end,
-- calls a function
[ "ring" ] = function( stack, info, data )
local args = {}
for k, v in pairs( stack.data ) do
if k == #stack.data then
if type(v) ~= "function" then
error( "did not receive function to call (expected function, got " .. type(v) .. ")" )
return
else
return v( table.unpack( args ) )
end
else
args[ #args + 1 ] = v
end
end
end,
-- wipes before calling
[ "ringx" ] = function( stack, info, data )
local args = {}
for k, v in pairs( stack.data ) do
if k == #stack.data then
if type(v) ~= "function" then
error( "did not receive function to call (expected function, got " .. type(v) .. ")" )
return
else
kw( "wipe", stack, info, data )
return v( table.unpack( args ) )
end
else
args[ #args + 1 ] = v
end
end
end,
-- calls a function under a condition
[ "dial" ] = function( stack, info, data )
local args = tCopy( stack.data )
local func = table.remove( args, #args )
local condition = table.remove( args, #args )
if not condition then return end
print( "condition is " .. type(condition) .. ":" .. tostring(condition) )
table.insert( args, func )
kw( "ring", {data=args}, info, data )
end,
-- wipes before dialing
[ "dialx" ] = function( stack, info, data )
local args = tCopy( stack.data )
local func = table.remove( args, #args )
local condition = table.remove( args, #args )
if not condition then return end
table.insert( args, func )
kw( "ring", {data=args}, info, data )
end,
-- defines a function
-- should be used like: arg1 arg2 arg3 arg4 <function operations> funk;
[ "funk" ] = function( stack, info, data )
local args = tCopy( stack.data )
local funk = table.remove( args, #args )
if type( funk ) ~= "table" or not funk.funk then
funk = "'bad funk def' stage print;"
else funk = funk.cmd end
local name = table.remove( args, #args )
data[ name ] = function( ... )
local t = {}
local fetched = {...}
for k, v in pairs( args ) do
t[ v ] = fetched[ k ] or nil
end
ExecuteJade( funk, t, data )
end
end,
-- prints text with a newline
[ "print" ] = function( stack, _, data )
print( table.unpack({tostring(stack(first)), #stack.data > 1 and tostring(stack(last)) or nil}) )
end,
-- completely stops execution of the program
-- note that it does not terminate here, this is merely called on termination
-- also, this elevates the termination to the entire execution.
-- for a local termination (restricted inside the relative <operation>), see cancel
[ "terminate" ] = function( stack, info, data )
--print( "goodbye!" )
end,
-- local termination
[ "cancel" ] = function( stack, info, data )
--print( "goodbye for now!" )
end,
-- don't do this :(
[ "crazy" ] = function( stack )
local condom = load( stack(last) )
if condom then
condom()
end
end,
}
kw = function( name, stack, info, data )
return keywords[ name ]( stack, info, data )
end
local quotes = {["\""] = "\"", ["'"] = "'", ["<"] = ">"}
local function ParseJade( str )
str = string.gsub( string.gsub( str, "\n", " " ), "\t", "" )
local Statements = {}
local Split = ToTable( str )
local log = {} -- stores all of our commands before our ending /;/
local current = ""
local inside_string = false -- toggled by a double quote
local quote_recursion = 0 -- for quotes with differing opening/closing characters inside itself
local inside_comment = false -- once enabled, doesn't disable until the line ends or a /;/ ennds it.
for count, char in pairs( Split ) do
if inside_comment then
if char ~= ";" and char ~= "\n" then
goto lazycontinue
elseif #log > 0 or current ~= "" then
if current ~= "" then log[ #log + 1 ] = { raw = not inside_string, cmd = string.lower( current ) } end
Statements[ #Statements + 1 ] = log
log = {}
current = ""
goto lazycontinue
end
end
-- continue parsing for this statement if we're inside a string or the statement isn't attempting to be ended.
if (char ~= ";" and char ~= "\n") or inside_string then
if not inside_string and char == "#" then
inside_comment = true
goto lazycontinue
else
if inside_string then
if char == inside_string then
quote_recursion = quote_recursion + 1
current = current .. char
elseif char == quotes[ inside_string ] then
quote_recursion = quote_recursion - 1
if quote_recursion <= 0 then
inside_string = false
log[ #log + 1 ] = { raw = false, cmd = current, funk = char == ">" }
current = ""
goto lazycontinue
else
current = current .. char
end
else
current = current .. char
end
elseif char == " " then
if current ~= "" then
log[ #log + 1 ] = { raw = true, cmd = string.lower( current ) }
current = ""
end
elseif quotes[ char ] then
inside_string = char
quote_recursion = 1
else
current = current .. char
end
end
-- reset our stuff if we've logged anything so far
elseif #log > 0 or current ~= "" then
if current ~= "" then log[ #log + 1 ] = { raw = true, cmd = string.lower( current ) } end
Statements[ #Statements + 1 ] = log
log = {}
current = ""
end
::lazycontinue::
end
if #log > 0 or current ~= "" then
if current ~= "" then log[ #log + 1 ] = { raw = true, cmd = string.lower( current ) } end
Statements[ #Statements + 1 ] = log
log = {}
current = ""
end
return Statements
end
local function evaluate( cmd, data )
local t = {}
local last = 1
while true do
local start, stop = string.find( cmd, ".", last, true )
if not start or not stop then
if last ~= 1 then
t[ #t + 1 ] = string.sub( cmd, last, #cmd )
end
break
end
t[ #t + 1 ] = string.sub( cmd, last, stop - 1 )
last = stop + 1
end
if #t > 0 then
local function recur( look )
local last = look
for k, v in pairs( t ) do
if not last then return false end
last = last[ v ] or false
end
return last
end
return recur( data ) or cmd
else
return data[ cmd ] or cmd
end
end
local function funkify( cmd )
local t = { funk = true, cmd = cmd }
setmetatable( t, {
__tostring = function( self )
return self.cmd
end,
})
return t
end
ExecuteJade = function( str, temp, love )
temp = temp or {} -- used for func rings
local data = love or { TERMINATE = false } -- where we store our variables and etc - func rings might pass us diff data to reference
local face = {} -- used instead of temp/data/global directly.
setmetatable( face, {
__index = function( self, key )
return temp[ key ] or data[ key ] or global[ key ]
end,
__newindex = function( self, key, val )
rawset( data, key, val )
end,
})
local stack = { data = {} } -- reset each statement iteration; our current stack
setmetatable( stack, {
__index = function( self, key )
return rawget( self.data, key )
end,
__newindex = function( self, key, val )
rawset( self.data, key, val )
end,
__call = function( self, val, insert )
if insert then
--if #self.data > 1 then
-- table.remove( self.data, 1 )
--end
table.insert( self.data, val )
return true
end
if val == first then
return self.data[ #self.data - 1 > 0 and #self.data - 1 or 1 ]
elseif val == last then
return self.data[ #self.data ]
end
return "uh no???"
end
})
local info = {
set = false, -- used by the keyword "set" to store variables.
}
local var = nil -- disposable
local terminate_me = false
local Statements = ParseJade( str )
for count1, Commands in pairs( Statements ) do
stack.data = {} -- our current stack
for count2, cmd in pairs( Commands ) do
if cmd.raw and keywords[ cmd.cmd ] then
local var = {keywords[ cmd.cmd ]( stack, info, face )}
if #var > 0 then
if info.set then
face[ info.set ] = var[1]
else
for a, b in pairs( var ) do
stack( b, true )
end
end
end
if cmd.cmd == "terminate" then
terminate_me = true
data.TERMINATE = true
break
elseif cmd.cmd == "cancel" then
terminate_me = true
break
end
else
if tobool( cmd.cmd ) ~= nil then
var = tobool( cmd.cmd )
else
var = cmd.funk and funkify(cmd.cmd) or
not cmd.raw and cmd.cmd or tonumber( cmd.cmd ) or
evaluate( cmd.cmd, face )
end
stack( var, true )
end
end
if terminate_me then break end
if data.TERMINATE then break end
end
end
-- local jade = require( "jade" )
-- jade.load( "myscript.jade" )
-- or,
-- jade.execute( '"loaded my fancy jade" print;')
return { execute = ExecuteJade, parse = ParseJade, load = function( str )
local exec = io.read( str )
if exec and exec ~= "" then
ExecuteJade( exec )
end
end }