From 2d0db01b8651648f59cbf55ac1e9b7176886456d Mon Sep 17 00:00:00 2001 From: Alex Pecsi Date: Fri, 19 Aug 2016 14:04:47 +0100 Subject: [PATCH] Updated AST and VM --- Gemfile | 3 +- Gemfile.lock | 31 +++++++++--------- packages/ar/ar_mode.rb | 58 +++++++++++++++++++++++----------- packages/ar/ar_ns.rb | 2 +- packages/ar/ar_vm.rb | 58 ++++++++++++++++++++++++++++++++++ packages/ar/spec/ar_vm_spec.rb | 51 ++++++++++++++++++++++++++++++ 6 files changed, 168 insertions(+), 35 deletions(-) create mode 100644 packages/ar/ar_vm.rb create mode 100644 packages/ar/spec/ar_vm_spec.rb diff --git a/Gemfile b/Gemfile index 050b1a0..23918a6 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,8 @@ source 'https://rubygems.org' end gem 'simplecov' -gem 'activerecord', '~> 4.2.6' +gem 'activerecord', '~> 5.0' +#gem 'activerecord', '~> 4.2.6' gem 'activerecord-jdbc-adapter', '~> 1.3', platform: :jruby gem 'activerecord-jdbcpostgresql-adapter', platform: :jruby gem 'pg', platform: :mri diff --git a/Gemfile.lock b/Gemfile.lock index 783a8f2..922f39d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -44,26 +44,24 @@ GIT GEM remote: https://rubygems.org/ specs: - activemodel (4.2.7) - activesupport (= 4.2.7) - builder (~> 3.1) - activerecord (4.2.7) - activemodel (= 4.2.7) - activesupport (= 4.2.7) - arel (~> 6.0) + activemodel (5.0.0.1) + activesupport (= 5.0.0.1) + activerecord (5.0.0.1) + activemodel (= 5.0.0.1) + activesupport (= 5.0.0.1) + arel (~> 7.0) activerecord-jdbc-adapter (1.3.20) activerecord (>= 2.2) activerecord-jdbcpostgresql-adapter (1.3.20) activerecord-jdbc-adapter (~> 1.3.20) jdbc-postgres (>= 9.1) - activesupport (4.2.7) + activesupport (5.0.0.1) + concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - arel (6.0.3) - builder (3.2.2) + arel (7.1.1) + ast (2.3.0) celluloid (0.17.3) celluloid-essentials celluloid-extras @@ -82,6 +80,8 @@ GEM celluloid-supervision (0.20.6) timers (>= 4.1.1) coderay (1.1.1) + concurrent-ruby (1.0.2) + concurrent-ruby (1.0.2-java) diff-lcs (1.2.5) docile (1.1.5) ffi (1.9.14-java) @@ -92,8 +92,8 @@ GEM jdbc-postgres (9.4.1206) jruby-jars (9.1.2.0) jruby-rack (1.1.20) - json (1.8.3) - json (1.8.3-java) + json (2.0.2) + json (2.0.2-java) method_source (0.8.2) minitest (5.9.0) net-ssh (3.2.0) @@ -137,9 +137,10 @@ PLATFORMS ruby DEPENDENCIES - activerecord (~> 4.2.6) + activerecord (~> 5.0) activerecord-jdbc-adapter (~> 1.3) activerecord-jdbcpostgresql-adapter + ast celluloid highline jdbc-postgres diff --git a/packages/ar/ar_mode.rb b/packages/ar/ar_mode.rb index 90f7ec0..7a348a0 100644 --- a/packages/ar/ar_mode.rb +++ b/packages/ar/ar_mode.rb @@ -2,6 +2,8 @@ require 'console/table' require 'ast' +require_relative 'ar_vm' + include AST::Sexp class ActiveRecordMode < BaseMode @@ -49,19 +51,34 @@ def use_namespace(out, ar, namespace_id) end def dynamic_command(input) - tokens = ARQLLexer.new.tokenize(input)[:tokens] - ast, remaining_tokens = ARQLCommandParser.new.parse(tokens) - begin + tokens = ARQLLexer.new.tokenize(input)[:tokens] + ast, remaining_tokens = ARQLCommandParser.new.parse(tokens) + command_list = ARQLProcessor.new.process ast + vm = ARQLVM.new @ns - action = -> { - command_list.reduce(@ns) { |acc, msg| acc.send msg[0], *(msg[1]), &msg[2] } + # the action lambda is dependency-injected + action = -> (out) { + command_list.each { |instr| vm.send instr[0], *(instr[1]), &instr[2] } + process_object(vm.pop, out) } Command.new(:dynamic, "", "", { dynamic: true }, &action) - rescue e - + rescue => e + puts "ERROR" + end + end + + def process_object(result, out) + case result.type + when :select_result + pt = PrinTable.new + + head = result.children.first[0] + body = result.children.first[1] + + out.puts pt.print(head, body, :db) end end @@ -85,9 +102,11 @@ def initialize end def on_select_expr(node) - selexpr = process_all(node).flatten(1) + process_all(node).flatten(1) << [ :apply, [], -> (model, data, verbosity) { + header = model.new.filter_fields(verbosity) - selexpr + s(:select_result, [ header, data ]) + } ] end def on_model_expr(node) @@ -97,8 +116,7 @@ def on_model_expr(node) def on_model_ids(node) process_all(node).map { |n| [ - [ :lookup, [ n.to_s.to_sym ] ], - [ :[], [ :class_name ] ] + [ :select_model, [ n.to_s.to_sym ] ] ] }.flatten(1) end @@ -109,10 +127,13 @@ def on_model_id(node) def on_model_clause(node) if node.children.empty? - [[ :all, [] ]] + [ + [ :dup, [] ], + [ :apply, [], -> (model) { model.all } ] + ] else node.children.first.map { |key, value| - [ :where, [ { key.to_sym => value } ] ] + [ :apply, [], -> (model) { model.where(key.to_sym => value) } ] } end end @@ -122,20 +143,21 @@ def on_partition_expr(node) result = [] if part[:limit] >= 0 - result << [ :limit, [ part[:limit] ] ] + result << [ :apply, [], -> (model) { model.limit part[:limit] } ] end if part[:offset] > 0 - result << [ :offset, [ part[:offset] ] ] + result << [ :apply, [], -> (model) { model.offset part[:offset] } ] end result end def on_verbosity(node) - [[ :map, [], lambda { |e| - e.flatten_fields(node.children.first) - } ]] + [ + [ :apply, [], -> (model) { model.map { |e| e.flatten_fields(node.children.first) } } ], + [ :push, [ node.children.first ] ] + ] end end diff --git a/packages/ar/ar_ns.rb b/packages/ar/ar_ns.rb index 036d7d2..c1a8b93 100644 --- a/packages/ar/ar_ns.rb +++ b/packages/ar/ar_ns.rb @@ -69,7 +69,7 @@ def filter_fields(verbosity) end end - def flatten_fields(verbosity, include_headers = false) + def flatten_fields(verbosity) filter_fields(verbosity).map { |attr| resolve_attribute(attr) } end diff --git a/packages/ar/ar_vm.rb b/packages/ar/ar_vm.rb new file mode 100644 index 0000000..f323af5 --- /dev/null +++ b/packages/ar/ar_vm.rb @@ -0,0 +1,58 @@ + +class ARQLVM + + def initialize(ns = nil) + @ns = ns + @stack = [] + @current_model = nil + end + + def select_model(model_name) + @stack << @ns.lookup(model_name)[:class_name] + end + + def filter(&filter_expr) + top_elem = @stack.pop + + if top_elem.nil? + raise "Can not apply filter to an empty stack" + elsif top_elem.is_a? Enumerable + @stack << top_elem.find_all(&filter_expr) + #elsif top_elem.is_a? Class and top_elem.ancestors.member? ActiveRecordBaseProxy + #@stack << top_elem.all.find_all(&filter_expr) + else + raise "Can not apply filter to #{top_elem.class}" + end + end + + def push(*elem) + @stack.push *elem + end + + def pop(n = nil) + if n.nil? + @stack.pop + else + @stack.pop(n) + end + end + + def dup + push @stack.last unless @stack.last.nil? + end + + def apply(&function) + n = function.arity + items = pop(n) + push(function.call(*items)) + end + + def empty? + @stack.empty? + end + + def dump_stack! + puts "Stack size: #{@stack.length}" + @stack.each { |item| puts " #{item.class} @ #{item.to_s[0..64]}" } + end +end diff --git a/packages/ar/spec/ar_vm_spec.rb b/packages/ar/spec/ar_vm_spec.rb new file mode 100644 index 0000000..0773b9d --- /dev/null +++ b/packages/ar/spec/ar_vm_spec.rb @@ -0,0 +1,51 @@ +require 'ar/ar' + +RSpec.describe ARQLVM do + + context '#initialize' do + it 'should create a new, blank VM' do + vm = ARQLVM.new + end + end + + context '#push' do + it 'should push a new item onto the top of the stack' do + vm = ARQLVM.new + + vm.push :test + expect(vm.pop).to eq(:test) + end + end + + context '#apply' do + it 'should apply the given lambda to the top n elements of the stack' do + vm = ARQLVM.new + + vm.push 1 + vm.push 3 + vm.apply { |a, b| a + b } + expect(vm.pop).to eq(4) + expect(vm.empty?).to be_truthy + end + + it 'should push every element back after evaluation' do + vm = ARQLVM.new + + vm.push 1 + vm.push 3 + vm.push -4 + vm.apply { |a| [ 2, 5 ] } + expect(vm.pop(4)).to eq([1, 3, [ 2, 5 ] ]) + expect(vm.empty?).to be_truthy + end + + it 'should work well with arrays' do + vm = ARQLVM.new + + vm.push 1 + vm.push [ 3, 4, 5 ] + vm.apply { |a| a.map { |b| b + 1 } } + expect(vm.pop(2)).to eq([1, [4, 5, 6]]) + end + end +end