#!/usr/bin/env ruby
#
# Mplex: Extended Metaprogramming Library
#
# Copyright (c) 2009 FURUHASHI Sadayuki
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

class Mplex
	def initialize(src, fname = "(mplex)")
		@script = self.class.compile(src)
		b = nil; Object.new.instance_eval { b = binding }
		@proc   = eval("Proc.new{#{@script}}", b, fname)
	end
	attr_reader :script

	def result(context = nil, output = "")
		self.class.with_context(context, output) {|ctx|
			ctx.instance_eval(&@proc)
		}
	end

	def self.result(src, context = nil, fname = "(mplex)", output = "")
		with_context(context, output) {|ctx|
			ctx.instance_eval(compile(src), fname)
		}
	end

	def self.script(src)
		compile(src)
	end

	private
	def self.with_context(context, output, &block)
		context ||= Object.new
		context.instance_variable_set(:@_mplexout, output)
		block.call(context)
		context.instance_eval { remove_instance_variable(:@_mplexout) }
	end

	def self.compile(src)
		# MPLEX_COMPILE_BEGIN
		o = ""
		k = false
		src.each_line {|t|
			(k = false; o << "\n"; next) if k && t == "__END__\n"
			(o << t; next) if k
			(k = true;  o << "\n"; next) if t == "__BEGIN__\n"

			c, l = t.split(/^[ \t]*%/,2)
			(o << l; next) if l

			c, a, b = t.split(/[ \t]*\%\|([a-z0-9\,\*\&\(\)]*)\|/,2)
			t = "[%:#{b.strip} do |#{a}|%]#{c+"\n"}[%:end%]" if b

			c, q = t.split(/[ \t]*\%\>/,2)
			t = "[%:(%]#{c+"\n"}[%:)#{q.strip}%]" if q

			t.split(/\[\%(\:?.*?)\%\]/m).each_with_index {|m,i|
				(o << "#{m[1..-1]};"; next) if m[0] == ?: && i % 2 == 1
				(o << "@_mplexout.concat((#{m}).to_s);"; next) if i % 2 == 1
				o << "@_mplexout.concat(#{m.dump});" unless m.empty?
			}
			o << "\n"
		}
		o << "@_mplexout"
		# MPLEX_COMPILE_END
	end
end

#!/usr/bin/env ruby
#
# Mplex: Extended Metaprogramming Library
#
# Copyright (c) 2009 FURUHASHI Sadayuki
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

require 'optparse'

op = OptionParser.new
op.banner += " input..."

finputs = "-"
foutput = "-"
fctx    = nil
libs    = []
script  = false

op.on("-c file",    "context ruby script") {|v| fctx    = v   }
op.on("-o file",    "output file")         {|v| foutput = v   }
op.on("-r library", "load library")        {|v| libs   << v   }
op.on("-x",         "print ruby script")   {|v| script = true }

(class<<self;self;end).module_eval {
	define_method(:usage) {
		puts op.to_s
		puts $! unless $!.to_s.empty?
		exit 1
	}
}

begin
	op.parse!(ARGV)

	usage if ARGV.empty?

	finputs = ARGV

	raise "multiple stdin" if (finputs + [fctx]).count("-") >= 2

rescue
	usage
end

def fread(path)
	path == "-" ? $stdin.read : File.read(path)
end

def fname(path)
	path == "-" ? "(stdin)" : path
end

libs.each {|lib| require lib }

inputs = finputs.map {|f| [fread(f), fname(f)] }

result = ""
if script
	inputs.each {|src, name|
		result.concat Mplex.script(src)
	}

else
	if fctx
		b = nil; Object.instance_eval { b = binding }
		ctx = eval("lambda{#{fread(fctx)}}.call", b, fname(fctx))
	end
	ctx ||= Object.new

	inputs.each {|src, name|
		Mplex.result(src, ctx, name, result)
	}
end

if foutput == "-"
	$stdout.write(result)
else
	File.open(foutput, "w") {|f|
		f.write result
	}
end

