# encoding: utf-8 require "strscan" require "antelope/ace/scanner/first" require "antelope/ace/scanner/second" require "antelope/ace/scanner/third" module Antelope module Ace # Scans a given input. The input should be a properly formatted ACE file; # see the Ace module for more information. This scanner uses the # StringScanner class internally; see the ruby documentation for more on # that. This scanner seperates scanning into three seperate stages: # First, Second, and Third, for each section of the file, respectively. # # @see Ace # @see http://ruby-doc.org/stdlib-2.1.2/libdoc/strscan/rdoc/StringScanner.html class Scanner include First include Second include Third # The string scanner that we're using to scan the string with. # # @return [StringScanner] attr_reader :scanner # An array of the tokens that the scanner scanned. # # @return [Array>] attr_reader :tokens # The boundry between each section. Placed here to be easily modifiable. # **MUST** be a regular expression. # # @return [RegExp] CONTENT_BOUNDRY = /%%/ # The value regular expression. It should match values; for example, # things quoted in strings or word letters without quotes. Must respond # to #to_s, since it is embedded within other regular expressions. The # regular expression should place the contents of the value in the # groups 2 or 3. # # @return [#to_s] VALUE = %q{(?: (?:("|')((?:\\\\|\\"|\\'|.)+?)\\1) | ([[:word:]]+) )} # Scans a file. It returns the tokens resulting from scanning. # # @param source [String] the source to scan. This should be compatible # with StringScanner. # @return [Array>] # @see #tokens def self.scan(source) new(source).scan_file end # Initialize the scanner with the input. # # @param input [String] The source to scan. def initialize(input) @scanner = StringScanner.new(input) @tokens = [] end # Scans the file in parts. # # @raise [SyntaxError] if the source is malformed in some way. # @return [Array>] the tokens that # were scanned in this file. # @see #scan_first_part # @see #scan_second_part # @see #scan_third_part # @see #tokens def scan_file scan_first_part scan_second_part scan_third_part tokens end # Scans for whitespace. If the next character is whitespace, it # will consume all whitespace until the next non-whitespace # character. # # @return [Boolean] if any whitespace was matched. def scan_whitespace @scanner.scan(/\s+/) end private # Raises an error; first creates a small snippet to give the developer # some context. # # @raise [SyntaxError] always. # @return [void] def error! start = [@scanner.pos - 8, 0].max stop = [@scanner.pos + 8, @scanner.string.length].min snip = @scanner.string[start..stop].strip char = @scanner.string[@scanner.pos] raise SyntaxError, "invalid syntax near `#{snip.inspect}' (#{char.inspect})" end end end end