Merge: doc: fixed some typos and other misc. corrections
[nit.git] / src / literal.nit
index bdea381..5146a91 100644 (file)
 # Parsing of literal values in the abstract syntax tree.
 module literal
 
-import parser
-import toolcontext
+import phase
+
+redef class ToolContext
+       # Parses literal values in the whole AST and produces errors if needed
+       var literal_phase: Phase = new LiteralPhase(self, null)
+end
+
+private class LiteralPhase
+       super Phase
+
+       redef fun process_nmodule(nmodule) do nmodule.do_literal(toolcontext)
+end
 
 redef class AModule
        # Visit the module to compute the real value of the literal-related node of the AST.
@@ -35,17 +45,10 @@ private class LiteralVisitor
 
        var toolcontext: ToolContext
 
-       init(toolcontext: ToolContext)
-       do
-               self.toolcontext = toolcontext
-       end
-
        redef fun visit(n)
        do
-               if n != null then
-                       n.accept_literal(self)
-                       n.visit_all(self)
-               end
+               n.accept_literal(self)
+               n.visit_all(self)
        end
 end
 
@@ -53,12 +56,33 @@ redef class ANode
        private fun accept_literal(v: LiteralVisitor) do end
 end
 
-redef class AIntExpr
-       # The value of the literal int once computed.
-       var value: nullable Int
-       redef fun accept_literal(v)
+redef class AExpr
+       # Get `self` as a `String`.
+       # Return null if not a string.
+       fun as_string: nullable String
+       do
+               if not self isa AStringFormExpr then return null
+               return self.value
+       end
+
+       # Get `self` as an `Int`.
+       # Return null if not an integer.
+       fun as_int: nullable Int
        do
-               self.value = self.n_number.text.to_i
+               if not self isa AIntegerExpr then return null
+               return self.value.as(not null).to_i
+       end
+end
+
+redef class AIntegerExpr
+       # The value of the literal int once computed.
+       var value: nullable Numeric
+
+       redef fun accept_literal(v) do
+               value = n_integer.text.to_num
+               if value == null then
+                       v.toolcontext.error(hot_location, "Error: invalid literal `{n_integer.text}`")
+               end
        end
 end
 
@@ -67,80 +91,236 @@ redef class AFloatExpr
        var value: nullable Float
        redef fun accept_literal(v)
        do
-               # FIXME: no method to_f on string so just go ugly
-               var parts = self.n_float.text.split_with(".")
-               self.value = parts.first.to_i.to_f
+               self.value = self.n_float.text.to_f
+       end
+end
+
+# Any kind of literal which supports a prefix or a suffix
+class AAugmentedLiteral
+       # Returns the text of the token
+       private fun text: String is abstract
+
+       # Is the combination of prefixes and suffixes in `self` valid ?
+       fun is_valid_augmentation: Bool is abstract
+
+       private fun delimiter_start: Char is abstract
+
+       private fun delimiter_end: Char is abstract
+
+       # Prefix for the entity, "" if no prefix is found
+       protected var prefix: String is lazy do return text.substring(0, text.index_of(delimiter_start))
+
+       # Suffix for the entity, "" if no prefix is found
+       protected var suffix: String is lazy do return text.substring_from(text.last_index_of(delimiter_end) + 1)
+
+       # Content of the entity, without prefix nor suffix
+       protected var content: String is lazy do
+               var npr = text.substring_from(prefix.length)
+               return npr.substring(0, npr.length - suffix.length)
        end
 end
 
 redef class ACharExpr
+       super AAugmentedLiteral
        # The value of the literal char once computed.
-       var value: nullable Char
+       var value: nullable Char = null
+
+       redef fun delimiter_start do return '\''
+
+       redef fun delimiter_end do return '\''
+
+       # Is the expression returning a Code Point ?
+       fun is_code_point: Bool do return prefix == "u"
+
+       redef fun text do return n_char.text
+
+       redef fun is_valid_augmentation do
+               if suffix != "" then return false
+               if is_code_point then return true
+               if prefix != "" then return false
+               return true
+       end
+
        redef fun accept_literal(v)
        do
-               var txt = self.n_char.text.unescape_nit
+               if not is_valid_augmentation then
+                       v.toolcontext.error(hot_location, "Syntax Error: invalid prefix/suffix combination {prefix}/{suffix}")
+                       return
+               end
+               var txt = content.unescape_nit
                if txt.length != 3 then
-                       v.toolcontext.error(self.hot_location, "Invalid character literal {txt}")
+                       v.toolcontext.error(self.hot_location, "Syntax Error: invalid character literal `{txt}`.")
                        return
                end
-               self.value = txt[1]
+               self.value = txt.chars[1]
+       end
+end
+
+# Any kind of string form with augmentations from prefixes or suffixes
+class AugmentedStringFormExpr
+       super AAugmentedLiteral
+
+       redef var delimiter_start = '"'
+       redef var delimiter_end = '"'
+
+       # Is `self` a regular String object ?
+       fun is_string: Bool do return prefix == "" or prefix == "raw"
+
+       # Is `self` a Regular Expression ?
+       fun is_re: Bool do return prefix == "re"
+
+       # Is `self` a Byte String ?
+       fun is_bytestring: Bool do return prefix == "b"
+
+       redef fun is_valid_augmentation do
+               if is_string and suffix == "" then return true
+               if is_bytestring and suffix == "" then return true
+               if is_re then
+                       var suf = suffix
+                       for i in suf.chars do
+                               if i == 'i' then continue
+                               if i == 'm' then continue
+                               if i == 'b' then continue
+                               return false
+                       end
+                       return true
+               end
+               if prefix != "" or suffix != "" then return false
+               return true
        end
 end
 
 redef class AStringFormExpr
+       super AugmentedStringFormExpr
+
        # The value of the literal string once computed.
-       var value: nullable String
+       var value: String is noinit
+
+       # The underlying bytes of the String, non-cleaned for UTF-8
+       var bytes: Bytes is noinit
+
+       redef fun text do return n_string.text
+
+       # Returns the raw text read by the lexer
+       var raw_text: String is lazy do
+               var txt = content
+               var behead = 1
+               var betail = 1
+               if txt.chars[0] == txt.chars[1] and txt.length >= 6 then
+                       behead = 3
+                       betail = 3
+                       if txt.chars[0] == delimiter_start and txt.chars[3] == '\n' then behead = 4 # ignore first \n in """
+               end
+               return txt.substring(behead, txt.length - behead - betail)
+       end
+
+       redef fun accept_literal(v) do
+               value = raw_text
+               bytes = raw_text.to_bytes
+       end
+end
+
+redef class AEndStringExpr
+       redef var delimiter_end is lazy do return '"'
+       redef fun prefix do return ""
+end
+
+redef class AStartStringExpr
+       redef var delimiter_start is lazy do
+               var str = n_string.text
+               for i in [0 .. str.length[ do
+                       var c = str[i]
+                       if c == '"' or c == '\'' then
+                               return c
+                       end
+               end
+               # Cannot happen, unless the parser is bugged
+               abort
+       end
+
+       redef fun suffix do return ""
+end
+
+redef class AMidStringExpr
+       redef fun prefix do return ""
+       redef fun suffix do return ""
+end
+
+redef class AStringExpr
+       redef var delimiter_start is lazy do
+               var str = text
+               for i in [0 .. str.length[ do
+                       var c = str[i]
+                       if c == '"' or c == '\'' then
+                               delimiter_end = c
+                               return c
+                       end
+               end
+               # Cannot happen, unless the parser is bugged
+               abort
+       end
+
+       redef var delimiter_end is lazy do return delimiter_start
+
        redef fun accept_literal(v)
        do
-               var txt
-               if self isa AStringExpr then
-                       txt = self.n_string.text
-               else if self isa AStartStringExpr then
-                       txt = self.n_string.text
-               else if self isa AMidStringExpr then
-                       txt = self.n_string.text
-               else if self isa AEndStringExpr then
-                       txt = self.n_string.text
-               else abort
-               self.value = txt.substring(1, txt.length-2).unescape_nit
-       end
-end
-
-redef class String
-       # Return a string where Nit escape sequences are transformed.
-       #
-       # Example:
-       #     var s = "\\n"
-       #     print s.length # -> 2
-       #     var u = s.unescape_nit
-       #     print s.length # -> 1
-       #     print s[0].ascii # -> 10 (the ASCII value of the "new line" character)
-       fun unescape_nit: String
+               super
+               if not is_valid_augmentation then
+                       v.toolcontext.error(hot_location, "Error: invalid prefix/suffix combination {prefix}/{suffix}")
+                       return
+               end
+               if prefix != "raw" then
+                       bytes = raw_text.unescape_to_bytes
+                       value = bytes.to_s
+               end
+       end
+end
+
+redef class ASuperstringExpr
+       super AugmentedStringFormExpr
+
+       redef var prefix is lazy do
+               var fst = n_exprs.first
+               if fst isa AugmentedStringFormExpr then
+                       var prf = fst.prefix
+                       delimiter_start = fst.delimiter_start
+                       delimiter_end = delimiter_start
+                       return prf
+               end
+               return ""
+       end
+
+       redef var suffix is lazy do
+               var lst = n_exprs.last
+               # Forces the system to update the delimiter's value
+               prefix
+               if lst isa AugmentedStringFormExpr then
+                       lst.delimiter_end = delimiter_start
+                       return lst.suffix
+               end
+               return ""
+       end
+
+       redef fun accept_literal(v)
        do
-               var res = new Buffer.with_capacity(self.length)
-               var was_slash = false
-               for c in self do
-                       if not was_slash then
-                               if c == '\\' then
-                                       was_slash = true
-                               else
-                                       res.add(c)
-                               end
-                               continue
-                       end
-                       was_slash = false
-                       if c == 'n' then
-                               res.add('\n')
-                       else if c == 'r' then
-                               res.add('\r')
-                       else if c == 't' then
-                               res.add('\t')
-                       else if c == '0' then
-                               res.add('\0')
-                       else
-                               res.add(c)
+               if is_bytestring then
+                       v.toolcontext.error(hot_location, "Error: cannot produce a ByteString on a Superstring")
+                       return
+               end
+               if not is_valid_augmentation then
+                       v.toolcontext.error(hot_location, "Error: invalid prefix/suffix combination {prefix}/{suffix}")
+                       return
+               end
+       end
+
+       redef fun visit_all(v) do
+               super
+               if prefix != "raw" then
+                       for i in n_exprs do
+                               if not i isa AStringFormExpr then continue
+                               i.bytes = i.raw_text.unescape_to_bytes
+                               i.value = i.bytes.to_s
                        end
                end
-               return res.to_s
        end
 end