Skip to content
Browse files

Improvements to the REPL (ifancy) backwards compatibility has been pr…

…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...
1 parent a7ed481 commit 57e0bf0ecf6d5ada15cf5a62cae425abb6fdd626 @swarley swarley committed Oct 5, 2012
Showing with 300 additions and 33 deletions.
  1. +112 −32 bin/ifancy
  2. +1 −1 fancy.gemspec
  3. +57 −0 ruby_lib/interactive/coderay.rb
  4. +130 −0 ruby_lib/interactive/hilight.rb
View
144 bin/ifancy
@@ -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 {
@@ -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
}
View
2 fancy.gemspec
@@ -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)/ }
View
57 ruby_lib/interactive/coderay.rb
@@ -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]+/
+
View
130 ruby_lib/interactive/hilight.rb
@@ -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.
Something went wrong with that request. Please try again.