Permalink
Browse files

Tests pass

  • Loading branch information...
txus committed Feb 13, 2014
1 parent 997aaab commit 628455805fc34dc374276a3de9703f97c2ae2c3d
View
@@ -1,143 +1,76 @@
#!/usr/bin/env rbx
-#
-# vim: filetype=ruby
-$:.unshift File.expand_path('../../lib', __FILE__)
+file = __FILE__
+file = File.readlink(file) while File.symlink? file
+$LOAD_PATH.unshift(File.expand_path('../../lib', file))
-require 'readline'
require 'lambra'
-class Lambra::Script
- HISTORY = File.expand_path "~/.lambra_history"
+evals = []
+settings = Hash.new { |h,k| h[k] = false }
+options = Rubinius::Options.new "Usage: #$0 [options] [script]", 20
- attr_accessor :prompt
+options.on( "-A", "Print the AST" ) { settings[:ast] = true }
+options.on( "-B", "Print the bytecode" ) { settings[:bc] = true }
- def initialize
- @evals = []
- @script = nil
- @history = []
- end
-
- def options(argv=ARGV)
- options = Rubinius::Options.new "Usage: lambra [options] [script]", 20
-
- options.on "-", "Read and evaluate code from STDIN" do
- @evals << STDIN.read
- end
+options.on "-e", "CODE", "Execute CODE" do |e|
+ evals << ['(eval)', e]
+end
- options.on "-c", "FILE", "Check the syntax of FILE" do |f|
- begin
- begin
- Lambra::Parser.parse_file f
- rescue => e
- e.render
- exit 1
- end
-
- puts "Syntax OK"
- exit 0
- end
- end
+options.on "-v", "--version", "Show version" do
+ puts "Lambra #{Lambra::VERSION}"
+ exit 0
+end
- options.on "-e", "CODE", "Execute CODE" do |e|
- @evals << e
- end
+options.on "-h", "--help", "Display this help" do
+ puts options
+ exit 0
+end
- options.on "-v", "--version", "Display the version" do
- puts Lambra::COMMAND_VERSION
- end
+info = proc do |cond, name, &block|
+ next unless settings[cond]
+ puts '', " #{name} ".center(80, "=")
+ block[]
+ puts "-" * 80, ''
+end
- options.on "-h", "--help", "Display this help" do
- puts options
- exit 0
+display = proc do |file, code, ev=true, binding=nil|
+ begin
+ if settings[:ast]
+ ast = Lambra::Parser.parse_file(file)
+ info.call(:ast, 'AST') { Rubinius::AST::AsciiGrapher.new(ast).print }
end
- options.doc ""
-
- rest = options.parse(argv)
- @script ||= rest.first
- end
-
- def evals
- return if @evals.empty?
-
- Lambra::CodeLoader.evaluate @evals.join("\n")
- end
-
- def script
- return unless @script
-
- if File.exists? @script
- Lambra::CodeLoader.execute_file @script
+ info.call(:bc, "Bytecode") { puts Lambra::Compiler.compile_string(code, file).decode }
+ if ev
+ puts "=> %p" % Lambra::Compiler.eval(code, file, binding)
else
- STDERR.puts "Unable to find '#{@script}' to run"
- exit 1
+ Lambra::Compiler.eval(code, file, binding)
end
- end
-
- def load_history
- return unless File.exists? HISTORY
- File.open HISTORY, "r" do |f|
- f.each { |l| Readline::HISTORY << l }
- end
- end
-
- def save_history
- File.open HISTORY, "w" do |f|
- @history.last(100).map { |s| f.puts s }
- end
- end
-
- def record_history(str)
- @history << str
- end
-
- def start_prompt
- @prompt = "> "
+ rescue Exception => e
+ e.render
end
+end
- def continue_prompt
- @prompt = ". "
- end
+options.parse(ARGV).each do |file|
+ evals << [file, File.read(file), false]
+end
- def repl
- return if @script
- load_history
- start_prompt
-
- env = Object.new
-
- begin
- snippet = ""
- while str = Readline.readline(prompt)
- break if str.nil? and snippet.empty?
- next if str.empty?
-
- snippet << str
- begin
- value = Lambra::CodeLoader.evaluate snippet, env
- p value
-
- record_history snippet
- snippet = ""
- start_prompt
- rescue => e
- snippet = ""
- STDERR.puts "Error: #{e}"
- end
- end
- ensure
- save_history
+if evals.empty?
+ if $stdin.tty?
+ require 'readline'
+ binding = Object.send(:binding)
+ loop do
+ code = Readline.readline "lm> "
+ exit 0 unless code and code != "exit"
+ display['(repl)', code, true, binding]
end
- end
-
- def main
- options
- evals
- script
- repl
+ else
+ evals << ['(stdin)', STDIN.read]
end
end
-Lambra::Script.new.main
+evals.each(&display)
+
+
View
@@ -3,6 +3,6 @@
require 'lambra/version'
require 'lambra/syntax'
require 'lambra/parser'
+require 'lambra/compiler'
require 'lambra/bytecode_compiler'
require 'lambra/process'
-require 'lambra/library'
View
@@ -58,7 +58,7 @@ module GlobalScope
:/ => PrimitiveFunction.new { |a, b| a / b },
:* => PrimitiveFunction.new { |a, b| a * b },
:send => PrimitiveFunction.new { |pid, *args| Process[pid].push(*args) },
- :sleep => PrimitiveFunction.new { |seconds| sleep(seconds) }
+ :sleep => PrimitiveFunction.new { |seconds| sleep(seconds); nil }
})
class Keyword
@@ -39,6 +39,10 @@ def compile(ast, variables=nil, debugging=false)
finalize
end
+ def visit_EvalExpression(o)
+ o.body.accept(self)
+ end
+
def visit_List(o)
set_line(o)
return g.push_nil if o.elements.count.zero?
View
@@ -0,0 +1,137 @@
+require_relative 'bootstrap'
+
+module Lambra
+ class CompileError < RBX::CompileError
+ end
+
+ class Compiler < RBX::Compiler
+ Stages = Hash.new { |h,k| RBX::Compiler::Stages[k] }
+
+ def initialize(from, to)
+ @start = Stages[from].new self, to
+ end
+
+ def self.eval(code, *args)
+ file, line, binding, instance = '(eval)', 1, Object.send(:binding), Object.new
+ args.each do |arg|
+ case arg
+ when String then file = arg
+ when Integer then line = arg
+ when Binding then binding = arg
+ else
+ instance = arg
+ end
+ end
+
+ cm = compile_eval(code, binding.variables, file, line)
+ cm.scope = Rubinius::ConstantScope.new(Object)
+ cm.name = :__lambra__
+ script = Rubinius::CompiledMethod::Script.new(cm, file, true)
+ be = Rubinius::BlockEnvironment.new
+
+ script.eval_source = code
+ cm.scope.script = script
+
+ be.under_context(binding.variables, cm)
+ be.call_on_instance(instance)
+ end
+
+ def self.compile_eval(string, variable_scope, file="(eval)", line=1)
+ if ec = @eval_cache
+ layout = variable_scope.local_layout
+ if code = ec.retrieve([string, layout, line])
+ return code
+ end
+ end
+
+ compiler = new :eval, :compiled_code
+
+ parser = compiler.parser
+ parser.root RBX::AST::EvalExpression
+ parser.default_transforms
+ parser.input string, file, line
+
+ compiler.generator.variable_scope = variable_scope
+
+ code = compiler.run
+
+ code.add_metadata :for_eval, true
+
+ if ec and parser.should_cache?
+ ec.set([string.dup, layout, line], code)
+ end
+
+ return code
+ end
+
+ class Generator < RBX::Compiler::Generator
+ Stages[:bytecode] = self
+ next_stage RBX::Compiler::Encoder
+
+ def initialize(*)
+ super
+ end
+
+ def run
+ @output = @processor.new
+ @input.variable_scope = @variable_scope
+ b = Lambra::BytecodeCompiler.new(@output)
+ b.compile(@input)
+ # @input.bytecode @output
+ @output.close
+ run_next
+ end
+ end
+
+ class Parser < RBX::Compiler::Parser
+ def initialize(*)
+ super
+ ensure
+ @processor = Lambra::Parser
+ end
+
+ def create
+ @parser = @processor.new
+ end
+ end
+
+ class FileParser < Parser
+ Stages[:file] = self
+ next_stage Generator
+
+ def input(file, line=1)
+ @file = file
+ @line = line
+ end
+
+ def parse
+ create.parse_file(@file)
+ end
+ end
+
+ # source string -> AST
+ class StringParser < Parser
+ Stages[:string] = self
+ next_stage Generator
+
+ def input(string, name="(eval)", line=1)
+ @input = string
+ @file = name
+ @line = line
+ end
+
+ def parse
+ create.parse_string(@input)
+ end
+ end
+
+ class EvalParser < StringParser
+ Stages[:eval] = self
+ next_stage Generator
+
+ def should_cache?
+ @output.should_cache?
+ end
+ end
+ end
+end
View
@@ -1 +0,0 @@
-require 'lambra/library/code_loader'
Oops, something went wrong.

0 comments on commit 6284558

Please sign in to comment.