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
Public Class Methods
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
# 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
# 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
# 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
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
@return OpenStruct|nil
# File lib/riml.rb, line 234 def self.config @config end
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
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
# File lib/riml.rb, line 195 def self.include_cache @include_cache end
# File lib/riml.rb, line 184 def self.include_path get_path(:include_path) end
# 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 (String) into tokens (Array)
# File lib/riml.rb, line 40 def self.lex(code) Lexer.new(code).tokenize end
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
# File lib/riml.rb, line 202 def self.path_cache @path_cache end
# File lib/riml.rb, line 207 def self.rewritten_ast_cache @rewritten_ast_cache end
# File lib/riml.rb, line 177 def self.source_path get_path(:source_path) end
# File lib/riml.rb, line 180 def self.source_path=(path, force_cache_bust = false) set_path(:source_path, path, force_cache_bust) end
# File lib/riml.rb, line 191 def self.warn(warning) warning_buffer << warning end
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
# 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
# File lib/riml.rb, line 249 def self.flush_warnings if warnings warning_buffer.flush else warning_buffer.clear end end
# 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
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
# 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
# File lib/riml.rb, line 257 def self.warning_buffer @warning_buffer end
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