# Parsing of literal values in the abstract syntax tree.
module literal
-import parser
-import toolcontext
import phase
-import modelbuilder #FIXME useless
-
redef class ToolContext
+ # Parses literal values in the whole AST and produces errors if needed
var literal_phase: Phase = new LiteralPhase(self, null)
end
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
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
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 an ASCII byte value ?
+ fun is_ascii: Bool do return prefix == "b"
+
+ # 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_ascii then return true
+ 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]
+ if is_ascii and txt.chars[1].code_point > 127 then v.toolcontext.error(self.hot_location, "Syntax Error: usage of byte prefix on multibyte character.")
+ 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