module Riml

visits AST nodes and translates them into VimL

Constants

ClassNotFound
ClassRedefinitionError
CompileError
DEFAULT_COMPILE_FILES_OPTIONS
DEFAULT_COMPILE_OPTIONS
DEFAULT_PARSE_OPTIONS
EXTRACT_COMPILE_FILES_OPTIONS
EXTRACT_COMPILE_OPTIONS
EXTRACT_FILENAME_OPTIONS
EXTRACT_PARSE_OPTIONS
FILENAME_OPTION_KEYS
FILE_HEADER
FileNotFound
GET_SID_FUNCTION_SRC
INCLUDE_COMMENT_FMT
IncludeFileLoop
IncludeNotTopLevel
InvalidSuper

super is called in invalid context

SourceFileLoop
UserArgumentError

bad user arguments to Riml functions

Attributes

debug[RW]
warnings[RW]

Public Class Methods

check_syntax(input) click to toggle source

checks syntax of ‘input` (String). lexes + parses without going through AST rewriting or compilation

# File lib/riml.rb, line 164
def self.check_syntax(input)
  raise ArgumentError.new(input) unless input.is_a?(String)
  parse(input, nil)
  true
end
check_syntax_files(*filenames) click to toggle source
# File lib/riml.rb, line 170
def self.check_syntax_files(*filenames)
  filenames.each do |fname|
    File.open(fname) {|f| check_syntax(f.read)}
  end
  true
end
clear_caches() click to toggle source
# File lib/riml.rb, line 212
def self.clear_caches
  @include_cache.clear
  @path_cache.clear
  @rewritten_ast_cache.clear
  Parser.ast_cache.clear
end
compile(input, options = {}) click to toggle source
# File lib/riml.rb, line 53
def self.compile(input, options = {})
  parse_options = Hash[options.select(&EXTRACT_PARSE_OPTIONS)]
  compile_options = Hash[options.select(&EXTRACT_COMPILE_OPTIONS)]
  parser = Parser.new
  parser.options = DEFAULT_PARSE_OPTIONS.merge(parse_options)
  compiler = Compiler.new
  compiler.options = DEFAULT_COMPILE_OPTIONS.merge(compile_options)
  filename_options = Hash[options.select(&EXTRACT_FILENAME_OPTIONS)]
  do_compile(input, parser, compiler, filename_options)
end
compile_files(*filenames) click to toggle source

expects ‘filenames` (String) arguments, to be readable files. Optional options (Hash) as last argument.

# File lib/riml.rb, line 111
def self.compile_files(*filenames)
  filenames = filenames.dup
  parser, compiler = Parser.new, Compiler.new

  # extract parser and compiler options from last argument, or use default
  # options
  if filenames.last.is_a?(Hash)
    options = filenames.pop
    compile_options = Hash[options.select(&EXTRACT_COMPILE_FILES_OPTIONS)]
    parse_options = Hash[options.select(&EXTRACT_PARSE_OPTIONS)]
    compiler.options = DEFAULT_COMPILE_FILES_OPTIONS.merge(compile_options)
    parser.options = DEFAULT_PARSE_OPTIONS.merge(parse_options)
  else
    compiler.options = DEFAULT_COMPILE_FILES_OPTIONS.dup
    parser.options = DEFAULT_PARSE_OPTIONS.dup
  end

  filenames.uniq!
  # compile files using one thread per file, max 4 threads at once
  if filenames.size > 1
    threads = []
    with_file_rollback do
      while filenames.any?
        to_compile = filenames.shift(4)
        to_compile.each do |fname|
          _parser, _compiler = Parser.new, Compiler.new
          _compiler.options = compiler.options.dup
          _parser.options = parser.options.dup
          threads << Thread.new do
            f = File.open(fname)
            # `do_compile` will close file handle
            do_compile(f, _parser, _compiler, :commandline_filename => fname)
          end
        end
        threads.each(&:join)
        threads.clear
      end
    end
  elsif filenames.size == 1
    fname = filenames.first
    f = File.open(fname)
    # `do_compile` will close file handle
    with_file_rollback { do_compile(f, parser, compiler, :commandline_filename => fname) }
  else
    raise ArgumentError, "need filenames to compile"
  end
  true
ensure
  flush_warnings
end
config() click to toggle source

@return OpenStruct|nil

# File lib/riml.rb, line 234
def self.config
  @config
end
config=(config) click to toggle source

possible values: OpenStruct|nil

# File lib/riml.rb, line 239
def self.config=(config)
  unless config.nil? || OpenStruct === config
    raise ArgumentError, "config must be OpenStruct or NilClass, is #{config.class}"
  end
  @config = config
end
do_compile(input, parser = Parser.new, compiler = Compiler.new, filename_options = {}) click to toggle source

compile AST (Nodes), tokens (Array), code (String) or object that returns String from :read to output code (String). Writes file(s) if ‘input` is a File.

# File lib/riml.rb, line 67
def self.do_compile(input, parser = Parser.new, compiler = Compiler.new, filename_options = {})
  if input.is_a?(Nodes)
    nodes = input
  elsif input.is_a?(String) || input.is_a?(Array)
    nodes = parser.parse(input)
  elsif input.respond_to?(:read)
    source = input.read
    path = input.respond_to?(:path) ? input.path : nil
    nodes = parser.parse(source, parser.ast_rewriter || AST_Rewriter.new, path)
  else
    raise ArgumentError, "input must be one of AST (Nodes), tokens (Array), " \
      "code (String) or respond_to?(:read), is #{input.inspect}"
  end

  compiler.parser = parser

  # This is to avoid cases where the file we're compiling from the
  # commandline gets recompiled but put in a different location because
  # it's also sourced, and `Riml.source_path` is set to a non-default value.
  if input.is_a?(File)
    pathname = Pathname.new(input.path)
    full_path =
      if pathname.absolute?
        pathname.to_s
      else
        File.expand_path(input.path, compiler.output_dir || Dir.getwd)
      end
    compiler.sourced_files_compiled << full_path
  end

  output = compiler.compile(nodes)

  if input.is_a?(File)
    write_file(compiler, output, input.path, filename_options)
  else
    output
  end
ensure
  input.close if input.is_a?(File)
  process_compile_queue!(compiler)
end
include_cache() click to toggle source
# File lib/riml.rb, line 195
def self.include_cache
  @include_cache
end
include_path() click to toggle source
# File lib/riml.rb, line 184
def self.include_path
  get_path(:include_path)
end
include_path=(path, force_cache_bust = false) click to toggle source
# File lib/riml.rb, line 187
def self.include_path=(path, force_cache_bust = false)
  set_path(:include_path, path, force_cache_bust)
end
lex(code) click to toggle source

lex code (String) into tokens (Array)

# File lib/riml.rb, line 40
def self.lex(code)
  Lexer.new(code).tokenize
end
parse(input, ast_rewriter = AST_Rewriter.new, filename = nil) click to toggle source

parse tokens (Array) or code (String) into AST (Nodes)

# File lib/riml.rb, line 45
def self.parse(input, ast_rewriter = AST_Rewriter.new, filename = nil)
  unless input.is_a?(Array) || input.is_a?(String)
    raise ArgumentError, "input must be tokens (Array) or code (String), " \
      "is #{input.inspect}"
  end
  Parser.new.parse(input, ast_rewriter, filename)
end
path_cache() click to toggle source
# File lib/riml.rb, line 202
def self.path_cache
  @path_cache
end
rewritten_ast_cache() click to toggle source
# File lib/riml.rb, line 207
def self.rewritten_ast_cache
  @rewritten_ast_cache
end
source_path() click to toggle source
# File lib/riml.rb, line 177
def self.source_path
  get_path(:source_path)
end
source_path=(path, force_cache_bust = false) click to toggle source
# File lib/riml.rb, line 180
def self.source_path=(path, force_cache_bust = false)
  set_path(:source_path, path, force_cache_bust)
end
warn(warning) click to toggle source
# File lib/riml.rb, line 191
def self.warn(warning)
  warning_buffer << warning
end
with_file_rollback(&block) click to toggle source

if error is thrown, all files that were created will be rolled back to their previous state. If the file existed previously, it will be the same as it was. If the file didn’t exist, it will be removed if it was created.

# File lib/riml.rb, line 223
def self.with_file_rollback(&block)
  FileRollback.guard(&block)
end

Private Class Methods

cache_files_in_path(path, force_cache_bust = false) click to toggle source
# File lib/riml.rb, line 280
def self.cache_files_in_path(path, force_cache_bust = false)
  @path_cache[path] = nil if force_cache_bust
  @path_cache[path] || @path_cache.cache(path)
end
flush_warnings() click to toggle source
# File lib/riml.rb, line 249
def self.flush_warnings
  if warnings
    warning_buffer.flush
  else
    warning_buffer.clear
  end
end
get_path(name) click to toggle source
# File lib/riml.rb, line 285
def self.get_path(name)
  ivar = instance_variable_get("@#{name}")
  return ivar if ivar
  # RIML_INCLUDE_PATH or RIML_SOURCE_PATH
  val = if (path = ENV["RIML_#{name.to_s.upcase}"])
    path
  else
    [Dir.getwd]
  end
  set_path(name, val)
end
process_compile_queue!(compiler) click to toggle source

This is for when another file is sourced within a file we’re compiling.

# File lib/riml.rb, line 298
def self.process_compile_queue!(compiler)
  while paths = compiler.compile_queue.shift
    basename, full_path = *paths
    unless compiler.sourced_files_compiled.include?(full_path)
      compiler.sourced_files_compiled << full_path
      do_compile(File.open(full_path), compiler.parser, compiler, :sourced_filename => basename)
    end
  end
end
set_path(name, path, force_cache_bust = false) click to toggle source
# File lib/riml.rb, line 264
def self.set_path(name, path, force_cache_bust = false)
  return instance_variable_set("@#{name}", nil) if path.nil?
  path = path.split(':') if path.is_a?(String)
  path.each do |dir|
    unless File.directory?(dir)
      raise UserArgumentError, "Error trying to set #{name.to_s}. " \
        "Directory #{dir.inspect} doesn't exist"
    end
  end
  instance_variable_set("@#{name}", path)
  cache_files_in_path(path, force_cache_bust)
  path
end
warning_buffer() click to toggle source
# File lib/riml.rb, line 257
def self.warning_buffer
  @warning_buffer
end
write_file(compiler, output, full_path, filename_options = {}) click to toggle source

Files are written following these rules: If a filename is given from the commandline,

1) if the filename is absolute, output it to the directory in which the file resides
2) if there's an `output_dir` given, output the file to that directory
3) otherwise, output it into pwd

If a filename is sourced,

1) if the filename is absolute, output it to the directory in which the file resides
2) otherwise, output it to the directory in which the `riml` file is found, checking `Riml.source_path`
# File lib/riml.rb, line 320
def self.write_file(compiler, output, full_path, filename_options = {})
  fname = filename_options[:commandline_filename] || filename_options[:sourced_filename]
  fname or raise ArgumentError, "must pass correct filename_options"
  # writing out a file that's compiled from cmdline, output into output_dir
  output_dir = if filename_options[:commandline_filename]
    compiler.output_dir || Dir.getwd
  # writing out a riml_source'd file
  else
    # absolute path for filename sent from riml_sourced files,
    # output to that same directory if no --output-dir option is set
    if full_path[0, 1] == File::SEPARATOR && !compiler.output_dir
      Pathname.new(full_path).parent.to_s
    # relative path, join it with output_dir
    else
      rel_dir = Pathname.new(full_path).parent.to_s
      File.join(compiler.output_dir || Dir.getwd, rel_dir)
    end
  end
  basename_without_riml_ext = File.basename(fname).sub(/\.riml\Z/i, '')
  FileUtils.mkdir_p(output_dir) unless File.directory?(output_dir)
  full_path = File.join(output_dir, "#{basename_without_riml_ext}.vim")
  # if a function definition is at the end of a file and the :readable compiler
  # option is `true`, there will be 2 NL at EOF
  if output[-2..-1] == "\n\n"
    output.chomp!
  end
  FileRollback.creating_file(full_path)
  File.open(full_path, 'w') do |f|
    f.write FILE_HEADER + output
  end
end