diff options
author | Martin Odersky <odersky@gmail.com> | 2013-04-17 09:48:22 +0200 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2013-04-17 10:16:22 +0200 |
commit | ca8dc7ada663e44aafe470944dd17256dbde151c (patch) | |
tree | d15939e204042e358e0c83064250f1f18c1c4f25 /src/dotty/tools/dotc/util | |
parent | e32fedb6844eab11a27e365a570b2033a0f6f78d (diff) | |
download | dotty-ca8dc7ada663e44aafe470944dd17256dbde151c.tar.gz dotty-ca8dc7ada663e44aafe470944dd17256dbde151c.tar.bz2 dotty-ca8dc7ada663e44aafe470944dd17256dbde151c.zip |
Scanners added.
Moving Positions, Chars to new packages.
Added Source positions.
Added untyped trees module.
Factored out behavior between typed and untyped trees.
Diffstat (limited to 'src/dotty/tools/dotc/util')
-rw-r--r-- | src/dotty/tools/dotc/util/Chars.scala | 96 | ||||
-rw-r--r-- | src/dotty/tools/dotc/util/FreshNameCreator.scala | 38 | ||||
-rw-r--r-- | src/dotty/tools/dotc/util/Positions.scala | 88 | ||||
-rw-r--r-- | src/dotty/tools/dotc/util/SourceFile.scala | 109 |
4 files changed, 331 insertions, 0 deletions
diff --git a/src/dotty/tools/dotc/util/Chars.scala b/src/dotty/tools/dotc/util/Chars.scala new file mode 100644 index 000000000..99aad620a --- /dev/null +++ b/src/dotty/tools/dotc/util/Chars.scala @@ -0,0 +1,96 @@ +/* NSC -- new Scala compiler + * Copyright 2006-2012 LAMP/EPFL + * @author Martin Odersky + */ +package dotty.tools.dotc +package util + +import scala.annotation.switch +import java.lang.{ Character => JCharacter } +import java.lang.{Character => JCharacter} +import java.lang.Character.LETTER_NUMBER +import java.lang.Character.LOWERCASE_LETTER +import java.lang.Character.OTHER_LETTER +import java.lang.Character.TITLECASE_LETTER +import java.lang.Character.UPPERCASE_LETTER + +/** Contains constants and classifier methods for characters */ +object Chars { + + final val LF = '\u000A' + final val FF = '\u000C' + final val CR = '\u000D' + final val SU = '\u001A' + + /** Convert a character digit to an Int according to given base, + * -1 if no success + */ + def digit2int(ch: Char, base: Int): Int = { + val num = ( + if (ch <= '9') ch - '0' + else if ('a' <= ch && ch <= 'z') ch - 'a' + 10 + else if ('A' <= ch && ch <= 'Z') ch - 'A' + 10 + else -1 + ) + if (0 <= num && num < base) num else -1 + } + /** Buffer for creating '\ u XXXX' strings. */ + private[this] val char2uescapeArray = Array[Char]('\\', 'u', 0, 0, 0, 0) + + /** Convert a character to a backslash-u escape */ + def char2uescape(c: Char): String = { + @inline def hexChar(ch: Int): Char = + ( if (ch < 10) '0' else 'A' - 10 ) + ch toChar + + char2uescapeArray(2) = hexChar((c >> 12) ) + char2uescapeArray(3) = hexChar((c >> 8) % 16) + char2uescapeArray(4) = hexChar((c >> 4) % 16) + char2uescapeArray(5) = hexChar((c ) % 16) + + new String(char2uescapeArray) + } + + /** Is character a line break? */ + def isLineBreakChar(c: Char) = (c: @switch) match { + case LF|FF|CR|SU => true + case _ => false + } + + /** Is character a whitespace character (but not a new line)? */ + def isWhitespace(c: Char) = + c == ' ' || c == '\t' || c == CR + + /** Can character form part of a doc comment variable $xxx? */ + def isVarPart(c: Char) = + '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' + + /** Can character start an alphanumeric Scala identifier? */ + def isIdentifierStart(c: Char): Boolean = + (c == '_') || (c == '$') || Character.isUnicodeIdentifierStart(c) + + /** Can character form part of an alphanumeric Scala identifier? */ + def isIdentifierPart(c: Char) = + (c == '$') || Character.isUnicodeIdentifierPart(c) + + /** Is character a math or other symbol in Unicode? */ + def isSpecial(c: Char) = { + val chtp = Character.getType(c) + chtp == Character.MATH_SYMBOL.toInt || chtp == Character.OTHER_SYMBOL.toInt + } + + private final val otherLetters = Set[Char]('\u0024', '\u005F') // '$' and '_' + private final val letterGroups = { + import JCharacter._ + Set[Byte](LOWERCASE_LETTER, UPPERCASE_LETTER, OTHER_LETTER, TITLECASE_LETTER, LETTER_NUMBER) + } + def isScalaLetter(ch: Char) = letterGroups(JCharacter.getType(ch).toByte) || otherLetters(ch) + + /** Can character form part of a Scala operator name? */ + def isOperatorPart(c : Char) : Boolean = (c: @switch) match { + case '~' | '!' | '@' | '#' | '%' | + '^' | '*' | '+' | '-' | '<' | + '>' | '?' | ':' | '=' | '&' | + '|' | '/' | '\\' => true + case c => isSpecial(c) + } +} diff --git a/src/dotty/tools/dotc/util/FreshNameCreator.scala b/src/dotty/tools/dotc/util/FreshNameCreator.scala new file mode 100644 index 000000000..d0c007d94 --- /dev/null +++ b/src/dotty/tools/dotc/util/FreshNameCreator.scala @@ -0,0 +1,38 @@ +package dotty.tools +package dotc +package util + +import scala.collection.mutable + +trait FreshNameCreator { + def newName(): String + def newName(prefix: String): String + + @deprecated("use newName(prefix)", "2.9.0") + def newName(pos: scala.reflect.internal.util.Position, prefix: String): String = newName(prefix) + @deprecated("use newName()", "2.9.0") + def newName(pos: scala.reflect.internal.util.Position): String = newName() +} + +object FreshNameCreator { + class Default extends FreshNameCreator { + protected var counter = 0 + protected val counters = mutable.HashMap[String, Int]() withDefaultValue 0 + + /** + * Create a fresh name with the given prefix. It is guaranteed + * that the returned name has never been returned by a previous + * call to this function (provided the prefix does not end in a digit). + */ + def newName(prefix: String): String = { + val safePrefix = prefix.replaceAll("""[<>]""", """\$""") + counters(safePrefix) += 1 + + safePrefix + counters(safePrefix) + } + def newName(): String = { + counter += 1 + "$" + counter + "$" + } + } +} diff --git a/src/dotty/tools/dotc/util/Positions.scala b/src/dotty/tools/dotc/util/Positions.scala new file mode 100644 index 000000000..31eb2ff0d --- /dev/null +++ b/src/dotty/tools/dotc/util/Positions.scala @@ -0,0 +1,88 @@ +package dotty.tools.dotc +package util + +/** Position format in little endian: + * Start: unsigned 26 Bits (works for source files up to 64M) + * End: unsigned 26 Bits + * Point: unsigned 12 Bits relative to start + * NoPosition encoded as -1L (this is a normally invalid position + * because point would lie beyond end. + */ +object Positions { + + private val StartEndBits = 26 + private val StartEndMask = (1 << StartEndBits) - 1 + private val PointOffsetLimit = 1L << (64 - StartEndBits * 2) + + class Position(val coords: Long) extends AnyVal { + def point: Int = start + (coords >>> (StartEndBits * 2)).toInt + def start: Int = (coords & StartEndMask).toInt + def end: Int = ((coords >>> StartEndBits) & StartEndMask).toInt + + /** The union of two positions. Tries to keep the point offset of + * the first positon if within range. Otherwise, pointoffset will be 0. + */ + def union(that: Position) = + if (!this.exists) that + else if (!that.exists) this + else { + val start = this.start min that.start + val end = this.end max that.end + var pointOffset = this.point - start + if (pointOffset >= PointOffsetLimit) pointOffset = 0 + Position(start, end, pointOffset) + } + + def exists = this != NoPosition + + def shift(offset: Int) = + if (exists) Position(start + offset, end + offset, point - start) + else this + + def focus = Position(point) + + def withStart(start: Int) = Position(start, this.end, this.point - start) + def withEnd(end: Int) = Position(this.start, end, this.point - this.start) + def withPoint(point: Int) = Position(this.start, this.end, point - this.start) + } + + def Position(start: Int, end: Int, pointOffset: Int = 0): Position = + new Position( + (start & StartEndMask).toLong | + ((end & StartEndMask).toLong << StartEndBits) | + (pointOffset.toLong << (StartEndBits * 2))) + + def Position(point: Int): Position = Position(point, point, 0) + + val NoPosition = new Position(-1L) + + case class SourcePosition(source: SourceFile, pos: Position) { + def point: Int = pos.point + def start: Int = pos.start + def end: Int = pos.end + def exists = pos.exists + } + + val NoSourcePosition = SourcePosition(NoSource, NoPosition) + + /** The coordinate of a symbol. This is either an index or + * a point position. + */ + class Coord(val encoding: Int) extends AnyVal { + def isIndex = encoding > 0 + def isPosition = encoding <= 0 + def toIndex: Int = { + assert(isIndex) + encoding - 1 + } + def toPosition = { + assert(isPosition) + if (this == NoCoord) NoPosition else Position(1 - encoding) + } + } + + def indexCoord(n: Int) = new Coord(n + 1) + def positionCoord(n: Int) = new Coord(-(n + 1)) + + val NoCoord = new Coord(0) +}
\ No newline at end of file diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala new file mode 100644 index 000000000..a7a677560 --- /dev/null +++ b/src/dotty/tools/dotc/util/SourceFile.scala @@ -0,0 +1,109 @@ +package dotty.tools +package dotc +package util + +import scala.collection.mutable.ArrayBuffer +import dotty.tools.io._ +import annotation.tailrec +import java.util.regex.Pattern +import java.io.IOException +import Chars._ +import ScriptSourceFile._ +import Positions._ + +object ScriptSourceFile { + private val headerPattern = Pattern.compile("""^(::)?!#.*(\r|\n|\r\n)""", Pattern.MULTILINE) + private val headerStarts = List("#!", "::#!") + + def apply(file: AbstractFile, content: Array[Char]) = { + /** Length of the script header from the given content, if there is one. + * The header begins with "#!" or "::#!" and ends with a line starting + * with "!#" or "::!#". + */ + val headerLength = + if (headerStarts exists (content startsWith _)) { + val matcher = headerPattern matcher content.mkString + if (matcher.find) matcher.end + else throw new IOException("script file does not close its header with !# or ::!#") + } else 0 + new SourceFile(file, content drop headerLength) { + override val underlying = new SourceFile(file, content) + } + } +} + +case class SourceFile(file: AbstractFile, content: Array[Char]) { + + def this(_file: AbstractFile) = this(_file, _file.toCharArray) + def this(sourceName: String, cs: Seq[Char]) = this(new VirtualFile(sourceName), cs.toArray) + def this(file: AbstractFile, cs: Seq[Char]) = this(file, cs.toArray) + + override def equals(that : Any) = that match { + case that : SourceFile => file.path == that.file.path && start == that.start + case _ => false + } + override def hashCode = file.path.## + start.## + + val length = content.length + + /** true for all source files except `NoSource` */ + def exists: Boolean = true + + /** The underlying source file */ + def underlying: SourceFile = this + + /** The start of this file in the underlying source file */ + def start = 0 + + def atPos(pos: Position): SourcePosition = + SourcePosition(underlying, pos) + + def isSelfContained = underlying eq this + + /** Map a position to a position in the underlying source file. + * For regular source files, simply return the argument. + */ + def positionInUltimateSource(position: SourcePosition): SourcePosition = + SourcePosition(underlying, position.pos shift start) + + def isLineBreak(idx: Int) = + if (idx >= length) false else { + val ch = content(idx) + // don't identify the CR in CR LF as a line break, since LF will do. + if (ch == CR) (idx + 1 == length) || (content(idx + 1) != LF) + else isLineBreakChar(ch) + } + + def calculateLineIndices(cs: Array[Char]) = { + val buf = new ArrayBuffer[Int] + buf += 0 + for (i <- 0 until cs.length) if (isLineBreak(i)) buf += i + 1 + buf += cs.length // sentinel, so that findLine below works smoother + buf.toArray + } + private lazy val lineIndices: Array[Int] = calculateLineIndices(content) + + /** Map line to offset of first character in line */ + def lineToOffset(index : Int): Int = lineIndices(index) + + /** A cache to speed up offsetToLine searches to similar lines */ + private var lastLine = 0 + + /** Convert offset to line in this source file + * Lines are numbered from 0 + */ + def offsetToLine(offset: Int): Int = { + val lines = lineIndices + def findLine(lo: Int, hi: Int, mid: Int): Int = + if (offset < lines(mid)) findLine(lo, mid - 1, (lo + mid - 1) / 2) + else if (offset >= lines(mid + 1)) findLine(mid + 1, hi, (mid + 1 + hi) / 2) + else mid + lastLine = findLine(0, lines.length, lastLine) + lastLine + } +} + +object NoSource extends SourceFile("<no source>", Nil) { + override def exists = false +} + |