Module: Subaltern

Defined in:
lib/subaltern.rb,
lib/subaltern/errors.rb,
lib/subaltern/kernel.rb,
lib/subaltern/context.rb,
lib/subaltern/version.rb,
lib/subaltern/evaluator.rb

Defined Under Namespace

Classes: AccessError, BlacklistedMethodError, Block, Command, Context, Function, LoopFunction, NonWhitelistedClassError, NonWhitelistedMethodError, Return, SubalternError, UndefinedVariableError

Constant Summary collapse

VERSION =
'1.0.0'
BLACKLIST =

– whitelisting, and some blacklisting, out of pure distrust ++

%w[
extend instance_eval instance_exec method methods include
private_methods public_methods protected_methods singleton_methods
send __send__
taint tainted? untaint
instance_variables instance_variable_get instance_variable_set
instance_variable_defined?
free frozen?
]
WHITELIST =

TODO :

eventually generate those whitelists by doing

class.public_instance_methods - BLACKLIST

though it would accept any new methods added by the ‘user’. Well, since the ‘user’ can’t overwrite methods…

{
  Array => %w[
& * + - << <=> == === =~ [] []=
all? any? assoc at choice class clear clone collect collect! combination
compact compact! concat count cycle delete delete_at delete_if detect display
drop drop_while dup each each_cons each_index each_slice each_with_index
empty? entries enum_cons enum_for enum_slice enum_with_index eql? equal?
fetch fill find find_all find_index first flatten flatten!
grep group_by hash id include? index indexes indices inject insert inspect
instance_of? is_a? join kind_of? last length map map! max max_by member?
min min_by minmax minmax_by nil? nitems none? object_id one? pack partition
permutation pop product push rassoc reduce reject reject! replace respond_to?
reverse reverse! reverse_each rindex select shift shuffle shuffle! size
slice slice! sort sort! sort_by take take_while tap to_a to_ary to_enum to_s
transpose type uniq uniq!  unshift untaint values_at zip |
  ],
  Fixnum => %w[
% & * ** + +@ - -@ / < << <= <=> == === =~ > >= >> [] ^ __id__
abs between? ceil chr class clone coerce display div divmod downto dup enum_for
eql? equal? even? fdiv floor hash id id2name inspect
instance_of? integer? is_a? kind_of? modulo next nil? nonzero? object_id odd?
ord prec prec_f prec_i pred quo remainder respond_to? round size step succ tap
times to_a to_enum to_f to_i to_int to_s to_sym truncate type upto zero? | ~
  ],
  Hash => %w[
== === =~ [] []= __id__
all? any? class clear clone collect count cycle default default= default_proc
delete delete_if detect display drop drop_while dup each each_cons each_key
each_pair each_slice each_value each_with_index empty? entries enum_cons
enum_for enum_slice enum_with_index eql? equal? fetch find find_all find_index
first grep group_by has_key? has_value? hash id include? index indexes indices
inject inspect instance_of? invert is_a? key? keys kind_of? length map max
max_by member? merge merge! min min_by minmax minmax_by nil? none? object_id
one? partition reduce rehash reject reject! replace respond_to? reverse_each
select shift size sort sort_by store take take_while tap to_a to_enum to_hash
to_s type update value? values values_at zip
  ],
  MatchData => %[
[]
  ],
  NilClass => %[
& == === =~ ^ __id__ class clone display dup enum_for eql? equal? hash
id inspect instance_of? is_a? kind_of? nil? object_id respond_to?
tap to_a to_enum to_f to_i to_s type |
  ],
  Regexp => %w[
match
  ],
  String => %w[
% * + < << <= <=> == === =~ > >= [] []= __id__
all? any? between? bytes bytesize capitalize capitalize! casecmp center chars
chomp chomp! chop chop! class clone collect concat count crypt cycle delete
delete! detect display downcase downcase! drop drop_while dump dup
each each_byte each_char each_cons each_line each_slice each_with_index empty?
end_with? entries enum_cons enum_for enum_slice enum_with_index eql? equal?
find find_all find_index first grep group_by gsub gsub! hash hex id include?
index inject insert inspect instance_of? intern is_a? kind_of? length lines
ljust lstrip lstrip! map match max max_by member? min min_by minmax minmax_by
next next! nil? none? object_id oct one? partition reduce reject replace
respond_to? reverse reverse! reverse_each rindex rjust rpartition rstrip
rstrip! scan select size slice slice! sort sort_by split squeeze squeeze!
start_with? strip strip! sub sub! succ succ! sum swapcase swapcase! take
take_while tap to_a to_enum to_f to_i to_s to_str to_sym tr tr! tr_s tr_s!
type unpack upcase upcase! upto zip
  ],
  Class => %w[
===
  ],
  TrueClass => %w[
==
  ],
  FalseClass => %w[
==
  ],
}
WHITELISTED_CONSTANTS =
WHITELIST.keys.collect { |k| k.name.to_sym } +
[ :FalseClass, :TrueClass ]

Class Method Summary collapse

Class Method Details

.bounce(target, method) ⇒ Object

Will raise an exception if calling that method on this target is not whitelisted (or is blacklisted).



272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/subaltern/evaluator.rb', line 272

def self.bounce(target, method)

  if BLACKLIST.include?(method.to_s)
    raise BlacklistedMethodError.new(target.class)
  end
  unless WHITELIST.keys.include?(target.class)
    raise NonWhitelistedClassError.new(target.class)
  end
  unless WHITELIST[target.class].include?(method.to_s)
    raise NonWhitelistedMethodError.new(target.class, method)
  end
end

.eval(source, vars = {}) ⇒ Object



34
35
36
37
# File 'lib/subaltern.rb', line 34

def self.eval(source, vars={})

  Context.new(nil, vars).eval(source)
end

.eval_and(context, tree) ⇒ Object



514
515
516
517
518
519
520
521
522
523
524
# File 'lib/subaltern/evaluator.rb', line 514

def self.eval_and(context, tree)

  tree[1..-1].inject(nil) { |_, t|

    current = eval_tree(context, t)

    return current unless current

    current
  }
end

.eval_array(context, tree) ⇒ Object



340
341
342
343
# File 'lib/subaltern/evaluator.rb', line 340

def self.eval_array(context, tree)

  tree[1..-1].collect { |t| eval_tree(context, t) }
end

.eval_block(context, tree) ⇒ Object



386
387
388
389
# File 'lib/subaltern/evaluator.rb', line 386

def self.eval_block(context, tree)

  tree[1..-1].collect { |t| eval_tree(context, t) }.last
end

.eval_break(context, tree) ⇒ Object

Raises:



485
486
487
488
489
# File 'lib/subaltern/evaluator.rb', line 485

def self.eval_break(context, tree)

  raise(
    Command.new('break', tree[1..-1].collect { |t| eval_tree(context, t) }))
end

.eval_call(context, tree) ⇒ Object



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
# File 'lib/subaltern/evaluator.rb', line 358

def self.eval_call(context, tree)

  if tree[1] == nil
    # variable lookup or function application...

    value = eval_lvar(context, tree[1, 2])

    return value.call(context, tree) if value.is_a?(Subaltern::Function)
    return value
  end

  target = eval_tree(context, tree[1])
  method = tree[2].to_s
  args = tree[3][1..-1]

  if is_javascripty_hash_lookup?(target, method, args)
    return target[method]
  end

  args = args.collect { |t| eval_tree(context, t) }

  return target.call(context, args) if target.is_a?(Subaltern::Block)

  bounce(target, method)

  target.send(method, *args)
end

.eval_case(context, tree) ⇒ Object



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/subaltern/evaluator.rb', line 531

def self.eval_case(context, tree)

  value = eval_tree(context, tree[1])
  whens = tree[2..-1].select { |t| t[0] == :when }
  the_else = (tree[2..-1] - whens).first

  whens.each do |w|

    eval_tree(context, w[1]).each do |v|

      return eval_tree(context, w[2]) if v === value
    end
  end

  the_else ? eval_tree(context, the_else) : nil
end

.eval_colon2(context, tree) ⇒ Object

Raises:



406
407
408
409
# File 'lib/subaltern/evaluator.rb', line 406

def self.eval_colon2(context, tree)

  raise AccessError.new("no access to constants (#{tree[1]})")
end

.eval_const(context, tree) ⇒ Object

Raises:



411
412
413
414
415
416
417
418
# File 'lib/subaltern/evaluator.rb', line 411

def self.eval_const(context, tree)

  if WHITELISTED_CONSTANTS.include?(tree[1])
    return Kernel.const_get(tree[1].to_s)
  end

  raise AccessError.new("no access to constants (#{tree[1]})")
end

.eval_defn(context, tree) ⇒ Object



420
421
422
423
424
425
426
# File 'lib/subaltern/evaluator.rb', line 420

def self.eval_defn(context, tree)

  name = tree[1].to_s
  context[name] = Function.new(tree)

  nil
end

.eval_dstr(context, tree) ⇒ Object



318
319
320
321
322
323
# File 'lib/subaltern/evaluator.rb', line 318

def self.eval_dstr(context, tree)

  tree[1..-1].collect { |t|
    t.is_a?(String) ? t : eval_tree(context, t)
  }.join
end

.eval_dxstr(context, tree) ⇒ Object

Raises:



335
336
337
338
# File 'lib/subaltern/evaluator.rb', line 335

def self.eval_dxstr(context, tree)

  raise AccessError.new("no backquoting allowed (#{tree[1]})")
end

.eval_evstr(context, tree) ⇒ Object



325
326
327
328
# File 'lib/subaltern/evaluator.rb', line 325

def self.eval_evstr(context, tree)

  eval_tree(context, tree[1])
end

.eval_for(context, tree) ⇒ Object



584
585
586
587
588
589
590
# File 'lib/subaltern/evaluator.rb', line 584

def self.eval_for(context, tree)

  values = eval_tree(context, tree[1])
  block = Block.new(tree[2..-1])

  values.each { |v| block.call(context, [ v ], false) }
end

.eval_gvar(context, tree) ⇒ Object

Raises:



401
402
403
404
# File 'lib/subaltern/evaluator.rb', line 401

def self.eval_gvar(context, tree)

  raise AccessError.new("no global variables (#{tree[1]})")
end

.eval_hash(context, tree) ⇒ Object



345
346
347
348
# File 'lib/subaltern/evaluator.rb', line 345

def self.eval_hash(context, tree)

  Hash[*eval_array(context, tree)]
end

.eval_if(context, tree) ⇒ Object



497
498
499
500
501
502
503
504
# File 'lib/subaltern/evaluator.rb', line 497

def self.eval_if(context, tree)

  if eval_tree(context, tree[1])
    eval_tree(context, tree[2])
  elsif tree[3]
    eval_tree(context, tree[3])
  end
end

.eval_iter(context, tree) ⇒ Object



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
# File 'lib/subaltern/evaluator.rb', line 438

def self.eval_iter(context, tree)

  if tree[1][1] == nil
    #
    # function with a block

    con = Context.new(context, '__block' => Block.new(tree[2..-1]))

    eval_call(con, tree[1])

  else
    #
    # method with a block

    target = eval_tree(context, tree[1][1])
    method = tree[1][2]

    bounce(target, method)

    method_args = tree[1][3][1..-1].collect { |t| eval_tree(context, t) }
    block = Block.new(tree[2..-1])

    command = nil

    result = target.send(method, *method_args) { |*args|
      begin
        block.call(context, args)
      rescue Command => c
        case c.name
          when 'break' then break c
          when 'next' then next c
          else raise c
        end
      end
    }

    Command.narrow(result)
  end
end

.eval_lasgn(context, tree) ⇒ Object



391
392
393
394
# File 'lib/subaltern/evaluator.rb', line 391

def self.eval_lasgn(context, tree)

  context[tree[1].to_s] = eval_tree(context, tree[2])
end

.eval_lit(context, tree) ⇒ Object



308
309
310
311
# File 'lib/subaltern/evaluator.rb', line 308

def self.eval_lit(context, tree)

  tree[1]
end

.eval_lvar(context, tree) ⇒ Object



396
397
398
399
# File 'lib/subaltern/evaluator.rb', line 396

def self.eval_lvar(context, tree)

  context[tree[1].to_s]
end

.eval_next(context, tree) ⇒ Object

Raises:



491
492
493
494
495
# File 'lib/subaltern/evaluator.rb', line 491

def self.eval_next(context, tree)

  raise(
    Command.new('next', tree[1..-1].collect { |t| eval_tree(context, t) }))
end

.eval_not(context, tree) ⇒ Object



526
527
528
529
# File 'lib/subaltern/evaluator.rb', line 526

def self.eval_not(context, tree)

  not eval_tree(context, tree[1])
end

.eval_or(context, tree) ⇒ Object



506
507
508
509
510
511
512
# File 'lib/subaltern/evaluator.rb', line 506

def self.eval_or(context, tree)

  tree[1..-1].each { |t|
    result = eval_tree(context, t)
    return result if result
  }
end

.eval_rescue(context, tree) ⇒ Object



592
593
594
595
# File 'lib/subaltern/evaluator.rb', line 592

def self.eval_rescue(context, tree)

  Subaltern.eval_tree(context, tree[2][2])
end

.eval_return(context, tree) ⇒ Object

Raises:



433
434
435
436
# File 'lib/subaltern/evaluator.rb', line 433

def self.eval_return(context, tree)

  raise(Return.new(eval_tree(context, tree[1])))
end

.eval_scope(context, tree) ⇒ Object



428
429
430
431
# File 'lib/subaltern/evaluator.rb', line 428

def self.eval_scope(context, tree)

  eval_tree(context, tree[1])
end

.eval_str(context, tree) ⇒ Object



313
314
315
316
# File 'lib/subaltern/evaluator.rb', line 313

def self.eval_str(context, tree)

  tree[1]
end

.eval_tree(context, tree) ⇒ Object

The entry point.



291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/subaltern/evaluator.rb', line 291

def self.eval_tree(context, tree)

  send("eval_#{tree.first}", context, tree)

rescue NoMethodError => nme
  puts '-' * 80
  p nme
  p tree
  puts nme.backtrace.join("\n")
  puts '-' * 80
  raise nme
end

.eval_until(context, tree) ⇒ Object



566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
# File 'lib/subaltern/evaluator.rb', line 566

def self.eval_until(context, tree)

  result = until eval_tree(context, tree[1])

    begin
      eval_tree(context, tree[2])
    rescue Command => c
      case c.name
        when 'break' then break *c.args
        when 'next' then next *c.args
        else raise c
      end
    end
  end

  Command.narrow(result)
end

.eval_while(context, tree) ⇒ Object



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
# File 'lib/subaltern/evaluator.rb', line 548

def self.eval_while(context, tree)

  result = while eval_tree(context, tree[1])

    begin
      eval_tree(context, tree[2])
    rescue Command => c
      case c.name
        when 'break' then break c
        when 'next' then next c
        else raise c
      end
    end
  end

  Command.narrow(result)
end

.eval_xstr(context, tree) ⇒ Object

Raises:



330
331
332
333
# File 'lib/subaltern/evaluator.rb', line 330

def self.eval_xstr(context, tree)

  raise AccessError.new("no backquoting allowed (#{tree[1]})")
end

.eval_yield(context, tree) ⇒ Object



478
479
480
481
482
483
# File 'lib/subaltern/evaluator.rb', line 478

def self.eval_yield(context, tree)

  args = tree[1..-1].collect { |t| eval_tree(context, t) }

  context['__block'].call(context, args)
end

.is_javascripty_hash_lookup?(target, method, args) ⇒ Boolean

Returns:

  • (Boolean)


350
351
352
353
354
355
356
# File 'lib/subaltern/evaluator.rb', line 350

def self.is_javascripty_hash_lookup?(target, method, args)

  target.is_a?(Hash) &&
  args.empty? &&
  ( ! WHITELIST[Hash].include?(method)) &&
  target.has_key?(method)
end

.kernelObject



26
27
28
29
30
31
# File 'lib/subaltern/kernel.rb', line 26

def self.kernel

  {
    'loop' => LoopFunction.new
  }
end