-
-
Save fcoury/120782 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=begin | |
endless.rb is a pre-processor for ruby which allows you to use python-ish | |
indentation to delimit scopes, instead of having to type 'end' every time. | |
Basically, this makes the end keyword optional. If you leave off the | |
end, the preprocessor inserts an end for you at the next line indented | |
at or below the level of indentation of the line which started the scope. | |
End is optional, so you can still write things like this: | |
begin | |
do_something | |
end until done? | |
(However, you'd better make damn sure you get the end indented to the | |
right level!) | |
This script uses RubyLexer to extract a stream of tokens and modify | |
it, then turn those tokens back into ruby code. Since RubyLexer is a | |
complete stand-alone lexer, this should be a very thorough solution, | |
free of insoluable little problems due to the script's inability to | |
follow where comments and strings start and stop. (That said, I'm sure | |
there will be some problems with it, as it's pretty raw code.) | |
As different programs have a variety of interpretations as to the | |
width of a tab character, tabs for indentation are absolutely | |
forbidden by endless.rb. | |
There is a similar script, pyruby.rb, or pyrb.rb floating around which | |
examines a source file line by line and assumes lines ending in a colon | |
are the start of a block. Since pyrb.rb does not tokenize the input, | |
pyruby.rb can be fooled by a colon in a string or comment: | |
p "a: | |
b" | |
p "a" #: | |
It's basically impossible to get this kind of thing right without actually | |
tokenizing the input. Also, unlike python (and pyruby.rb), endless.rb needs | |
no extra colon to start an indented block. (I don't like python's colons | |
much.) | |
Code written without ends must be pulled into the interpreter via special | |
versions of the #load or #eval builtin methods. Eventually, there should | |
be a version of #require as well, but for now you must get along with #load. | |
The special versions are contained in the Endless module, so to load up | |
source code without ends in it, use | |
Endless.load 'filename' | |
instead of | |
require 'filename' | |
or | |
load 'filename' | |
For examples, see the end of the file. | |
=end | |
require 'rubygems' | |
require 'rubylexer' | |
require 'tempfile' | |
class EndlessRubyLexer<RubyLexer | |
def initialize *args | |
@old_indent_levels=[] | |
super | |
end | |
def old_indent_level | |
@old_indent_levels.last | |
end | |
DONT_END=/^( *)(end|when|else|elsif|rescue|ensure)(?!#{LETTER_DIGIT}|[?!])/o | |
def start_of_line_directives | |
super | |
if @file.check /^( *)(.)/ #(?!end(?!#{LETTER_DIGIT}|[?!]).)(.)/m | |
lm=@file.last_match | |
@indent_level=lm[1].size if lm[1] | |
if /\A(?![ ])[\s\v]\Z/===lm[2] | |
@indent_level=:invalid | |
end | |
if !(@parsestack.last.respond_to? :in_body and !@parsestack.last.in_body) and | |
(!@moretokens[-2] or NewlineToken===@moretokens[-2]) | |
#auto-terminate previous same or more indented want-end contexts | |
pos=input_position | |
while WantsEndContext===@parsestack.last and | |
@parsestack.last.indent_level > @indent_level | |
insert_implicit_end pos | |
end | |
while WantsEndContext===@parsestack.last and | |
@parsestack.last.indent_level == @indent_level | |
insert_implicit_end pos | |
end unless @file.check DONT_END | |
end | |
end | |
end | |
def insert_implicit_end pos | |
#emit implicit end token | |
@moretokens.push WsToken.new(' ',pos), | |
KeywordToken.new('end',pos), | |
KeywordToken.new(';',pos) | |
@parsestack.pop | |
end | |
def indent_level | |
if Integer===@indent_level | |
@indent_level | |
else | |
raise "invalid indentation: must use only spaces" | |
end | |
end | |
def endoffile_detected(str='') | |
result=super | |
pos=input_position | |
while WantsEndContext===@parsestack.last | |
insert_implicit_end pos | |
end | |
@moretokens.push result | |
return @moretokens.shift | |
end | |
def keyword_for(str,offset,result) | |
result=super | |
@parsestack[-2].indent_level=indent_level | |
return result | |
end | |
def keyword_def(str,offset,result) | |
fail unless @indent_level | |
@old_indent_levels.push @indent_level | |
result=super | |
@old_indent_levels.pop | |
return result | |
end | |
eval %w[module class begin case].map{|w| | |
" | |
def keyword_#{w}(str,offset,result) | |
result=super | |
@parsestack.last.indent_level=indent_level | |
return result | |
end | |
" | |
}.join | |
eval %w[while until if unless].map{|w| | |
" | |
def keyword_#{w}(str,offset,result) | |
result=super | |
if @parsestack[-2].respond_to? :indent_level= | |
@parsestack[-2].indent_level||=indent_level | |
end | |
return result | |
end | |
" | |
}.join | |
def keyword_do(str,offset,result) | |
return super if ExpectDoOrNlContext===@parsestack.last | |
result=super | |
ctx=@parsestack[-1] | |
ctx=@parsestack[-2] if BlockParamListLhsContext===ctx | |
ctx.indent_level=indent_level | |
return result | |
end | |
end | |
class RubyLexer::NestedContexts::WantsEndContext | |
attr_accessor :indent_level | |
end | |
class RubyLexer::NestedContexts::DefContext | |
alias endful_see see | |
def see(lxr,msg) | |
if :semi==msg | |
fail unless lxr.parsestack.last.equal? self | |
@indent_level||=lxr.old_indent_level | |
end | |
endful_see lxr, msg | |
end | |
end | |
module Endless | |
VERSIOn="0.0.2" | |
class<<self | |
def require(name) | |
raise NotImplementedError | |
huh load | |
end | |
def load(filename,wrap=false) | |
[''].concat($:).each{|pre| | |
pre+="/" unless %r{(\A|/)\Z}===pre | |
if File.exist? finally=pre+filename | |
code=File.open(finally){|fd| fd.read } | |
f=Tempfile.new filename | |
begin | |
preprocess code,filename,f | |
f.rewind | |
return ::Kernel::load(f.path, wrap) | |
ensure f.close | |
end | |
end | |
} | |
raise LoadError, "no such file to load -- "+filename | |
end | |
def eval(code,file="(eval)",line=1,binding=nil) | |
#binding should default to Binding.of_caller, not nil...... | |
eval(preprocess(code,file),file,line,binding) | |
end | |
def preprocess(code,filename,output='') | |
lexer=EndlessRubyLexer.new(filename,code) | |
printer=RubyLexer::KeepWsTokenPrinter.new | |
begin | |
tok=lexer.get1token | |
output << printer.aprint(tok) | |
end until RubyLexer::EoiToken===tok | |
return output | |
end | |
def main | |
fn=ARGV.first | |
if fn=='-e' | |
fn=ARGV[1..-1].join ' ' | |
f=fn | |
elsif fn=='test' | |
f=DATA | |
else | |
f=open fn if fn | |
end | |
preprocess f||$stdin.read, fn||'-', $stdout | |
end | |
end | |
end | |
Endless.main if __FILE__==$0 | |
__END__ | |
#test data follows | |
begin | |
a | |
b | |
begin | |
a | |
b | |
begin \ | |
a | |
b | |
begin | |
a | |
b | |
begin | |
a | |
a2 | |
rescue | |
b | |
begin | |
a | |
a2 | |
ensure | |
b | |
begin | |
a | |
a2 | |
else | |
b | |
begin | |
a | |
a2 | |
rescue | |
b | |
else | |
d | |
ensure | |
c | |
x | |
case ARGV[0] | |
when /^\d*$/ | |
p "That's an integer!" | |
when /^[A-Za-z]*$/ | |
p "That's a word!" | |
else | |
p "That's something that's not an integer or a word..." | |
end | |
case ARGV[0] | |
when /^\d*$/ | |
p "That's an integer!" | |
when /^[A-Za-z]*$/ | |
p "That's a word!" | |
else | |
p "That's something that's not an integer or a word..." | |
case | |
ARGV[0] | |
when /^\d*$/ | |
p "That's an integer!" | |
when /^[A-Za-z]*$/ | |
p "That's a word!" | |
else | |
p "That's something that's not an integer or a word..." | |
case ARGV[0] | |
when /^\d*$/ | |
p "That's an integer!" | |
when /^[A-Za-z]*$/ | |
p "That's a word!" | |
else | |
p "That's something that's not an integer or a word..." | |
case ARGV[0] | |
when /^\d*$/ | |
p "That's an integer!" | |
when /^[A-Za-z]*$/ | |
p "That's a word!" | |
else | |
p "That's something that's not an integer or a word..." | |
end | |
case ARGV[0] | |
when /^\d*$/ | |
p "That's an integer!" | |
when /^[A-Za-z]*$/ | |
p "That's a word!" | |
else | |
p "That's something that's not an integer or a word..." | |
end if ARGV[0] | |
result = case ARGV[0] | |
when /^\d*$/ | |
p "That's an integer!" | |
when /^[A-Za-z]*$/ | |
p "That's a word!" | |
else | |
p "That's something that's not an integer or a word..." | |
end if ARGV[0] | |
result = case ARGV[0] | |
when /^\d*$/ | |
p "That's an integer!" | |
when /^[A-Za-z]*$/ | |
p "That's a word!" | |
else | |
p "That's something that's not an integer or a word..." | |
end if ARGV[0] | |
class A | |
class B | |
class C | |
module D | |
foo | |
end | |
class A | |
class B | |
class C | |
module D | |
foo | |
end | |
class A < | |
Ay; foo | |
bar | |
class B \ | |
; foo | |
bar | |
module C include | |
Cy; foo | |
bar | |
module C \ | |
; foo | |
bar | |
def a(b) | |
c | |
d | |
def a#(b) | |
c | |
d | |
def a | |
c | |
d | |
def a() | |
c | |
d | |
def a b | |
c | |
d | |
def a b | |
c | |
end | |
def a b | |
end | |
def a b | |
end | |
def keo() | |
retun sup | |
def keo() | |
retun sup | |
end | |
def goo | |
xxx | |
def goo | |
xxx | |
def fooo x, | |
y, | |
z | |
some code | |
really_important_code! | |
fantastically_critical_code! | |
more code | |
def foo big, | |
list, | |
of, | |
args | |
some.stuff | |
some.stuff | |
some.stuff | |
something.more.important! | |
look.at.me! | |
more.stuff=boring | |
#i don't know why this works, but it does | |
baz. | |
foo(bar, :a=>1, :b=>2, | |
:c=>3, :d=>4) do | |
do_stuff! | |
xxx | |
#i don't know why this works, but it does | |
foo(bar, :a=>1, :b=>2, | |
:c=>3, :d=>4) do | |
do_stuff! | |
xxx | |
def foo(bar, a=1, b=2, | |
c=3, d=4) | |
do_stuff! | |
xxx | |
n = 2 * -[1,2,3,4,5].inject(0) do |a, b| | |
c = a * 2 | |
a * c + b | |
result = 8 * -if n > 0 | |
-n += 10000 | |
else | |
n + 500000 | |
puts result | |
for i in 1..10 do | |
p i | |
pp i | |
for i in 1..10 | |
p i | |
pp i | |
#for i | |
# in 1..10 do | |
# p i | |
# pp i | |
#for i | |
# in 1..10 | |
# p i | |
# pp i | |
for | |
i in 1..10 do | |
p i | |
pp i | |
for | |
i in 1..10 | |
p i | |
pp i | |
for i in | |
1..10 do | |
p i | |
pp i | |
for i in | |
1..10 | |
p i | |
pp i | |
for i in 1..10 do \ | |
p i | |
pp i | |
if a | |
b | |
else | |
c | |
class RubyLexer | |
class WantsEndContext | |
def initialize(a,b) | |
end | |
end | |
end | |
class RubyLexer | |
WantsEndContext.new 1,2 | |
class WantsEndContext | |
attr_accessor :indent_level | |
end | |
WantsEndContext.new | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment