# Simple Declarative Language (SDL) for Ruby # Copyright 2005 Ikayzo, inc. # # This program is free software. You can distribute or modify it under the # terms of the GNU Lesser General Public License version 2.1 as published by # the Free Software Foundation. # # This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND, # INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. # See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, contact the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. require 'jcode' require 'base64' require 'rational' require 'date' # Various SDL related utility methods # module SDL4R MAX_INTEGER_32 = 2**31 - 1 MIN_INTEGER_32 = -(2**31) MAX_INTEGER_64 = 2**63 - 1 MIN_INTEGER_64 = -(2**63) # Returns the milliseonds part of a DateTime (by translating the +sec_fraction+ part) as an # integer. def self.get_date_milliseconds(date) sec_fraction = date.sec_fraction() # in days # 86400000 is the number of milliseconds in a day return (sec_fraction * 86400000).round() end BASE64_WRAP_LINE_LENGTH = 72 # Creates an SDL string representation for a given object and returns it. # # +o+:: the object to format # +add_quotes+:: indicates whether quotes will be added to Strings and characters # (true by default) # +line_prefix+:: the line prefix to use ("" by default) # +indent+:: the indent string to use ("\t" by default) # def self.format(o, add_quotes = true, line_prefix = "", indent = "\t") if o.is_a?(String) if add_quotes o_length = 0 o.scan(/./m) { o_length += 1 } # counts the number of chars (as opposed of bytes) if o_length == 1 return "'" + escape(o, "'") + "'" else return '"' + escape(o, '"') + '"' end else return escape(o) end elsif o.is_a?(Bignum) return o.to_s + "BD" elsif o.is_a?(Integer) if MIN_INTEGER_32 <= o and o <= MAX_INTEGER_32 return o.to_s elsif MIN_INTEGER_64 <= o and o <= MAX_INTEGER_64 return o.to_s + "L" else return o.to_s + "BD" end elsif o.is_a?(Float) return o.to_s + "F" elsif o.is_a?(Rational) return o.to_f.to_s + "F" elsif defined? Flt::DecNum and o.is_a?(Flt::DecNum) return o.to_s + "BD" elsif o.nil? return "null" elsif o.is_a?(SdlBinary) encoded_o = Base64.encode64(o.bytes) encoded_o.gsub!(/[\r\n]/m, "") # Remove the EOL inserted every 60 chars if add_quotes if encoded_o.length > BASE64_WRAP_LINE_LENGTH # FIXME: we should a constant or some parameter instead of hardcoded spaces wrap_lines_in_ascii(encoded_o, BASE64_WRAP_LINE_LENGTH, "#{line_prefix}#{indent}") encoded_o.insert(0, "[#{$/}") encoded_o << "#{$/}#{line_prefix}]" else encoded_o.insert(0, "[") encoded_o << "]" end end return encoded_o # Below, we use "#{o.year}" instead of "%Y" because "%Y" always emit 4 chars at least even if # the date is before 1000. elsif o.is_a?(DateTime) || o.is_a?(Time) milliseconds = o.is_a?(DateTime) ? get_date_milliseconds(o) : (o.usec / 10).to_i if milliseconds == 0 if o.zone return o.strftime("#{o.year}/%m/%d %H:%M:%S%Z") else return o.strftime("#{o.year}/%m/%d %H:%M:%S") end else if o.zone return o.strftime("#{o.year}/%m/%d %H:%M:%S." + milliseconds.to_s.ljust(3, '0') + "%Z") else return o.strftime("#{o.year}/%m/%d %H:%M:%S." + milliseconds.to_s.ljust(3, '0')) end end elsif o.is_a?(Date) return o.strftime("#{o.year}/%m/%d") else return o.to_s end end # Wraps lines in "s" (by modifying it). This method only supports 1-byte character strings. # def self.wrap_lines_in_ascii(s, line_length, line_prefix = nil) # We could use such code if it supported any value for "line_prefix": unfortunately it is capped # at 64 in the regular expressions. # # return "#{line_prefix}" + encoded_o.scan(/.{1,#{line_prefix}}/).join("#{$/}#{line_prefix}") eol_size = "#{$/}".size i = 0 while i < s.size if i > 0 s.insert(i, $/) i += eol_size end if line_prefix s.insert(i, line_prefix) i += line_prefix.size end i += line_length end end ESCAPED_QUOTES = { "\"" => "\\\"", "'" => "\\'", "`" => "\\`", } ESCAPED_CHARS = { "\\" => "\\\\", "\t" => "\\t", "\r" => "\\r", "\n" => "\\n", } ESCAPED_CHARS.merge!(ESCAPED_QUOTES) # Returns an escaped version of +s+ (i.e. where characters which need to be # escaped, are escaped). # def self.escape(s, quote_char = nil) escaped_s = "" s.each_char { |c| escaped_char = ESCAPED_CHARS[c] if escaped_char if ESCAPED_QUOTES.has_key?(c) if quote_char && c == quote_char escaped_s << escaped_char else escaped_s << c end else escaped_s << escaped_char end else escaped_s << c end } return escaped_s end # This method was kept from the Java code but it is not sure if it should have a usefulness yet. # def self.coerce_or_fail(o) return o end # Validates an SDL identifier String. SDL Identifiers must start with a # Unicode letter or underscore (_) and contain only unicode letters, # digits, underscores (_), and dashes(-). # # @param identifier The identifier to validate # @throws IllegalArgumentException if the identifier is not legal # # TODO: support UTF-8 identifiers # def self.validate_identifier(identifier) if identifier.nil? or identifier.empty? raise ArgumentError, "SDL identifiers cannot be null or empty." end # in Java, was if(!Character.isJavaIdentifierStart(identifier.charAt(0))) unless identifier =~ /^[a-zA-Z_]/ raise ArgumentError, "'" + identifier[0..0] + "' is not a legal first character for an SDL identifier. " + "SDL Identifiers must start with a unicode letter or " + "an underscore (_)." end unless identifier.length == 1 or identifier =~ /^[a-zA-Z_][a-zA-Z_0-9\-]*$/ for i in 1..identifier.length unless identifier[i..i] =~ /^[a-zA-Z_0-9\-]$/ raise ArgumentError, "'" + identifier[i..i] + "' is not a legal character for an SDL identifier. " + "SDL Identifiers must start with a unicode letter or " + "underscore (_) followed by 0 or more unicode " + "letters, digits, underscores (_), or dashes (-)" end end end end end