Skip to content

Commit

Permalink
Improvements to the REPL (ifancy) backwards compatibility has been pr…
Browse files Browse the repository at this point in the history
…eserved. A better API for loading plugins will come shortly to allow for easier adding of user plugins. If coderay is present, syntax hilighting will be used on returned values. Features have been implemented, documentation to come.
  • Loading branch information
swarley committed Oct 5, 2012
1 parent a7ed481 commit 57e0bf0
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 33 deletions.
144 changes: 112 additions & 32 deletions bin/ifancy
Original file line number Diff line number Diff line change
@@ -1,25 +1,77 @@
#!/usr/bin/env fancy
# -*- fancy -*-

class Fancy Interactive {
@@info = <[ ]>

def self at: key {
return @@info[key]
}

def self set: key to: value {
return @@info at: key put: value
}
}

require("rb-readline/readline")
try {
require("coderay")
require(File.expand_path(File.dirname(__FILE__)) ++ "/../ruby_lib/interactive/hilight")
Fancy Interactive set: 'coderay to: true
} catch LoadError {
Fancy Interactive set: 'coderay to: false
}

["Welcome to the (still very simple) Fancy REPL",
"Fancy #{Fancy VERSION}"] println

HISTORY_FILE = File expand_path("~/.fancy_history")
HISTORY = []

PROMPT = "ifancy[%d]> "

class Prompt {

def initialize: @str {
@line = 0
}

def inc_line { @line = (@line + 1) }
def aug_line: num { @line = (@line + num) }
def to_s { return sprintf(@str, @line) }
}

class Feature {
@@hooks = <[ ]>

def initialize: @name will: @desc by: @block {
@@hooks at: @name put: self
}

def run: string { @block call: string }

def about { @name ++ ": " ++ @desc }

def Feature match: mod str: string {
if: (@@hooks[mod]) then: {
@@hooks[mod] run: string
return true
}
return false
}

def Feature get: mod {
if: (@@hooks[mod]) then: {
return @@hooks[mod]
} else: { return false }
}
}

ARGV rest each: |file| {
"LOADING: " ++ file println
require: file
}

# handle SIGINT
trap("INT") {
"Quitting." println
save_history
System exit
}

Console newline;

def double_or_empty?: line {
Expand Down Expand Up @@ -82,40 +134,68 @@ Readline completion: |str| {
}
}

def read_line {
def read_line: prompt {
try {
return Readline readline(">> ", true)
return Readline readline(prompt to_s, true)
} catch ArgumentError {
return ""
}
}

load_history
at_exit() { save_history }

bnd = binding()

prompt = Prompt new: PROMPT

Feature new: "exit" will: "Exit the shell" by: |x| { System exit }
Feature new: "quit" will: "Exit the shell" by: |x| { System exit }
Feature new: "?" will: "Give information on a command" by: |x| {
f = Feature get: $ x words [1]
if: f then: {
if: (f is_a?: Feature) then: {
f about . println
} else: {
"Sorry, #{x words [1]} is not a feature that is loaded}" println
}
} else: {
"Please supply the name of a feature as an argument" println
}
}

try {
bnd = binding()

while: { read_line } do: |line| {
if: (line =~ /^\s*(exit|quit)\s*$/) then: {
break
}
Feature new: "#{" will: "Create multiline capabilities" by: |x| {
line = read_line: "-> "
line_s = ""
until: { line =~ /^\#}$/ == 0} do: {
line_s = line_s ++ line ++ "\n"
line = read_line: "-> "
}
try {
Fancy eval: line_s binding: bnd . inspect println
prompt aug_line: (( line_s split: "\n") size)
} catch Exception => e {
e message() println
}
}

HISTORY << line
if: (double_or_empty?: line) then: {
Readline::HISTORY pop()
HISTORY pop()
}
loop: {
line = read_line: prompt
{ next } if: (Feature match: (line words [0]) str: line)

unless: (line =~ /^\s*$/) do: {
try {
Fancy eval: line binding: bnd . inspect println
} catch Exception => e {
e message() println
}
}
HISTORY << line
if: (double_or_empty?: line) then: {
Readline::HISTORY pop()
HISTORY pop()
}
try {
if: (Fancy Interactive at: 'coderay) then: {
"=> #{CodeRay.scan((Fancy eval: line binding: bnd . inspect), 'fancy).term()}" println
} else: {
"=> #{Fancy eval: line binding: bnd . inspect}" println
}
prompt inc_line
} catch Exception => e {
e message() println
}
save_history
} catch Interrupt => e {
save_history
System exit
}
2 changes: 1 addition & 1 deletion fancy.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Gem::Specification.new do |s|
["ruby_lib/fancy", "ruby_lib/ifancy", "ruby_lib/fdoc", "ruby_lib/fyi", "ruby_lib/fspec"] +
Dir.glob("lib/**/*.fy") + Dir.glob("lib/parser/ext/**/*") +
Dir.glob("tests/**/*.fy") + ["tools/fancy-mode.el"] + ["bin/fancy", "bin/fdoc", "bin/fyi", "bin/ifancy", "bin/fspec"] +
Dir.glob("examples/**/*.fy") + Dir.glob("doc/**/*")
Dir.glob("examples/**/*.fy") + Dir.glob("doc/**/*") + Dir.glob("ruby_lib/interactive/*")

files = files.reject{ |f| f =~ /\.(fyc|rbc|o|log|plist)/ }.reject{ |f| f =~ /conftest\.dSYM/ }
files += Dir.glob("boot/**/*").reject{ |f| f =~ /conftest\.dSYM/ }.reject{ |f| f =~ /\.(fyc|rbc|o|log|plist)/ }
Expand Down
57 changes: 57 additions & 0 deletions ruby_lib/interactive/coderay.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# This is basically a big hacked version of CodeRay::Scanners::Ruby,
# since the language semantics are the same in many areas, the patterns
# are where it will differ the most if i am correct.

module CodeRay
module Scanners

class Fancy < Scanner

register_for :fancy
file_exstension 'fy'

autoload :Patterns, File.expand_path(File.dirname(__FILE__)) + "/coderay/patterns"
autoload :StringState, File.expand_path(File.dirname(__FILE__)) + "/coderay/string_state"

def interpreted_string_state
StringState.new :string, true, ?"
end

protected

def setup
@state = :initial
end

def scan_tokens encoder, options
state, heredocs = options[:state] || @state
heredocs = heredocs.dup if heredocs.is_a? Array

if state && state.instance_of? StringState
encoder.begin_group state.type
end

last_state = nil

method_call_expected = false
value_expected = true

inline_block_stack = nil
inline_block_curly_depth = 0

if heredocs
state = heredocs.shift
encoder.begin_group state.type
heredocs = nil if heredocs.empty?
end

patters = Patterns

unicode = string.respond_to? :encoding && string.encoding.name == 'UTF-8'

until eos?

if state.instance_of? ::Symbol

if match = scan(/[ \t\f\v]+/

130 changes: 130 additions & 0 deletions ruby_lib/interactive/hilight.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# encoding: utf-8
module CodeRay
module Scanners

# Fancy scanner by swarley.
class Fancy < Scanner

register_for :fancy
file_extension 'fy'

SPECIAL_FORMS = %w[
def throw try catch class
] # :nodoc:

CORE_FORMS = %w[
+ - > < == != >= <= % ** * = && || =~
] # :nodoc:

PREDEFINED_CONSTANTS = %w[
true false nil
] # :nodoc:

IDENT_KIND = WordList.new(:ident).
add(SPECIAL_FORMS, :keyword).
add(CORE_FORMS, :keyword).
add(PREDEFINED_CONSTANTS, :predefined_constant)

KEYWORD_NEXT_TOKEN_KIND = WordList.new(nil).
add(%w[ def defn defn- definline defmacro defmulti defmethod defstruct defonce declare ], :function).
add(%w[ ns ], :namespace).
add(%w[ defprotocol defrecord ], :class)

BASIC_IDENTIFIER = /[a-zA-Z$%*\/_+!?&<>\-=]=?[a-zA-Z0-9$&*+!\/_?<>\-\#]*/
CLASS_IDENTIFIER = /[A-Z]+[A-Za-z0-9]*|[A-Z]+[A-Za-z0-9]\:\:|\:\:[A-Z]+[A-Za-z0-9]*/
IDENTIFIER = /(?!-\d)(?:(?:#{BASIC_IDENTIFIER}\.)*#{BASIC_IDENTIFIER}(?:\/#{BASIC_IDENTIFIER})?\.?)|\.\.?/
SYMBOL = /\'[A-Za-z]/o
DIGIT = /\d+/
DIGIT10 = DIGIT
DIGIT16 = /[0-9a-f]/i
DIGIT8 = /[0-7]/
DIGIT2 = /[01]/
DECIMAL = /#{DIGIT}\.#{DIGIT}|-#{DIGIT}\.#{DIGIT}/
NUM = /(?:\-)*(?:#{DIGIT}|#{DIGIT16}|#{DIGIT8}|#{DIGIT2}|#{DECIMAL})/
MESSAGE = /[A-Za-z0-9\&\_]+?(?:\:|\?\:)/
CAPTURE = /\|.+?\|/
protected

def scan_tokens encoder, options

state = :initial
kind = nil

until eos?

case state
when :initial
if match = scan(/ \s+ | \n | , /x)
encoder.text_token match, :space
elsif match = scan(/\#.+?$/)
encoder.text_token match, :comment
elsif match = scan(/\{|\}|\@\{|\[|\]|\(|\)/)
encoder.text_token match, :operator
elsif match = scan(Regexp.new((%W(+ - @ > < == != >= <= % ** * = && || =~).map {|x| Regexp.escape x }).join '|'))
encoder.text_token match, :operator
elsif match = scan(/\.|\$/)
encoder.text_token match, :predefined_constant
elsif match = scan(CAPTURE)
encoder.text_token match, :predefined_constant
elsif match = scan(CLASS_IDENTIFIER)
encoder.text_token match, :constant
elsif match = scan(/\:\:/)
encoder.text_token match, :constant
elsif match = scan(MESSAGE)
encoder.text_token match, :keyword
elsif match = scan(/#{IDENTIFIER}/o)
kind = IDENT_KIND[match]
encoder.text_token match, kind
if rest? && kind == :keyword
if kind = KEYWORD_NEXT_TOKEN_KIND[match]
encoder.text_token match, :space if match = scan(/\s+/o)
encoder.text_token match, kind if match = scan(/#{IDENTIFIER}/o)
end
end
elsif match = scan(MESSAGE)
encoder.text_token match, :keyword
elsif match = scan(/#{SYMBOL}/o)
encoder.text_token match, :symbol
elsif match = scan(/\./)
encoder.text_token match, :operator
elsif match = scan(/ \# \^ #{IDENTIFIER} /ox)
encoder.text_token match, :type
elsif match = scan(/ (\#)? " /x)
state = self[1] ? :regexp : :string
encoder.begin_group state
encoder.text_token match, :delimiter
elsif match = scan(/#{NUM}/o) and not matched.empty?
encoder.text_token match, match[/[.e\/]/i] ? :float : :integer
else
encoder.text_token getch, :error
end

when :string, :regexp
if match = scan(/[^"\\]+|\\.?/)
encoder.text_token match, :content
elsif match = scan(/"/)
encoder.text_token match, :delimiter
encoder.end_group state
state = :initial
else
raise_inspect "else case \" reached; %p not handled." % peek(1),
encoder, state
end

else
raise 'else case reached'

end

end

if [:string, :regexp].include? state
encoder.end_group state
end

encoder

end
end
end
end

0 comments on commit 57e0bf0

Please sign in to comment.