diff options
| author | jackkoenig | 2016-03-14 22:35:06 -0700 |
|---|---|---|
| committer | jackkoenig | 2016-03-15 13:43:57 -0700 |
| commit | 373d3cfcb5566c448dcad6b679dee43bf66f878a (patch) | |
| tree | 6a80e496588f56f5c52be40bcc0155ba8987811a /src/main | |
| parent | 5737a8ccbf54a6d22095023205867e851e204c3f (diff) | |
Revamp string literal handling
Diffstat (limited to 'src/main')
| -rw-r--r-- | src/main/scala/firrtl/Emitter.scala | 20 | ||||
| -rw-r--r-- | src/main/scala/firrtl/IR.scala | 4 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Parser.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Serialize.scala | 5 | ||||
| -rw-r--r-- | src/main/scala/firrtl/StringLit.scala | 119 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Translator.scala | 24 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Visitor.scala | 10 | ||||
| -rw-r--r-- | src/main/scala/firrtl/passes/Checks.scala | 17 | ||||
| -rw-r--r-- | src/main/scala/firrtl/passes/Passes.scala | 8 |
9 files changed, 175 insertions, 34 deletions
diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index 4b88f526..2aee699d 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -60,21 +60,6 @@ object VerilogEmitter extends Emitter { var w:Option[Writer] = None var mname = "" def wref (n:String,t:Type) = WRef(n,t,ExpKind(),UNKNOWNGENDER) - def escape (s:String) : String = { - val sx = ArrayBuffer[String]() - //sx += '"'.toString - var percent:Boolean = false - for (c <- s) { - if (c == '\n') sx += "\\n" - else if (c == '"') sx += '\\'.toString + '"'.toString - else { - if((c == 'x') && percent) sx += "h" else sx += c.toString - } - percent = (c == '%') - } - //sx += '"'.toString - sx.reduce(_ + _) - } def remove_root (ex:Expression) : Expression = { (ex.as[WSubField].get.exp) match { case (e:WSubField) => remove_root(e) @@ -367,9 +352,10 @@ object VerilogEmitter extends Emitter { def stop (ret:Int) : Seq[Any] = { Seq("$fdisplay(32'h80000002,\"",ret,"\");$finish;") } - def printf (str:String,args:Seq[Expression]) : Seq[Any] = { + def printf (str:StringLit,args:Seq[Expression]) : Seq[Any] = { val q = '"'.toString - val strx = (Seq(q + escape(str) + q) ++ args.flatMap(x => Seq(",",x))) + val strx = Seq(q + VerilogStringLitHandler.escape(str) + q) ++ + args.flatMap(x => Seq(",",x)) Seq("$fwrite(32'h80000002,",strx,");") } def delay (e:Expression, n:Int, clk:Expression) : Expression = { diff --git a/src/main/scala/firrtl/IR.scala b/src/main/scala/firrtl/IR.scala index 17b16052..f79e70ab 100644 --- a/src/main/scala/firrtl/IR.scala +++ b/src/main/scala/firrtl/IR.scala @@ -48,6 +48,8 @@ trait AST { def serialize: String = firrtl.Serialize.serialize(this) } +case class StringLit(array: Array[Byte]) extends AST + trait PrimOp extends AST case object ADD_OP extends PrimOp case object SUB_OP extends PrimOp @@ -107,7 +109,7 @@ case class BulkConnect(info: Info, loc: Expression, exp: Expression) extends Stm case class Connect(info: Info, loc: Expression, exp: Expression) extends Stmt case class IsInvalid(info: Info, exp: Expression) extends Stmt case class Stop(info: Info, ret: Int, clk: Expression, en: Expression) extends Stmt -case class Print(info: Info, string: String, args: Seq[Expression], clk: Expression, en: Expression) extends Stmt +case class Print(info: Info, string: StringLit, args: Seq[Expression], clk: Expression, en: Expression) extends Stmt case class Empty() extends Stmt trait Width extends AST { diff --git a/src/main/scala/firrtl/Parser.scala b/src/main/scala/firrtl/Parser.scala index 6e93e7e6..b7a74e84 100644 --- a/src/main/scala/firrtl/Parser.scala +++ b/src/main/scala/firrtl/Parser.scala @@ -39,6 +39,8 @@ import antlr._ class ParserException(message: String) extends Exception(message) case class ParameterNotSpecifiedException(message: String) extends ParserException(message) case class ParameterRedefinedException(message: String) extends ParserException(message) +case class InvalidStringLitException(message: String) extends ParserException(message) +case class InvalidEscapeCharException(message: String) extends ParserException(message) object Parser extends LazyLogging { diff --git a/src/main/scala/firrtl/Serialize.scala b/src/main/scala/firrtl/Serialize.scala index 6692063c..02b9ebc2 100644 --- a/src/main/scala/firrtl/Serialize.scala +++ b/src/main/scala/firrtl/Serialize.scala @@ -45,6 +45,7 @@ private object Serialize { case r: Port => serialize(r) case r: Module => serialize(r) case r: Circuit => serialize(r) + case r: StringLit => serialize(r) case _ => throw new Exception("serialize called on unknown AST node!") } } @@ -55,6 +56,8 @@ private object Serialize { def serialize(op: PrimOp): String = op.getString + def serialize(lit: StringLit): String = FIRRTLStringLitHandler.escape(lit) + def serialize(exp: Expression): String = { exp match { case v: UIntValue => s"UInt${serialize(v.width)}(${serialize(v.value)})" @@ -131,7 +134,7 @@ private object Serialize { case s: Stop => s"stop(${serialize(s.clk)}, ${serialize(s.en)}, ${s.ret})" case p: Print => { val q = '"'.toString - s"printf(${serialize(p.clk)}, ${serialize(p.en)}, ${q}${p.string}${q}" + + s"printf(${serialize(p.clk)}, ${serialize(p.en)}, ${q}${serialize(p.string)}${q}" + (if (p.args.nonEmpty) p.args.map(serialize).mkString(", ", ", ", "") else "") + ")" } case s:Empty => "skip" diff --git a/src/main/scala/firrtl/StringLit.scala b/src/main/scala/firrtl/StringLit.scala new file mode 100644 index 00000000..b3d67064 --- /dev/null +++ b/src/main/scala/firrtl/StringLit.scala @@ -0,0 +1,119 @@ +/* +Copyright (c) 2014 - 2016 The Regents of the University of +California (Regents). All Rights Reserved. Redistribution and use in +source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + two paragraphs of disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + two paragraphs of disclaimer in the documentation and/or other materials + provided with the distribution. + * Neither the name of the Regents nor the names of its contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. +IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF +ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION +TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. +*/ + +package firrtl + +import java.nio.charset.StandardCharsets.UTF_8 +import scala.annotation.tailrec + +// Default implementations are for valid FIRRTL Strings +trait StringLitHandler { + + // Apply correct escaping for strings in a given language + def escape(lit: StringLit): String = { + @tailrec + def escape(in: Array[Byte], str: String): String = { + if (in.isEmpty) str + else { + val s = in.head match { + case 0x5c => "\\\\" + case 0x0a => "\\n" + case 0x09 => "\\t" + case 0x22 => "\\\"" + case c => + if (c >= 0x20 && c <= 0x7e) new String(Array(c), UTF_8) + else throw new FIRRTLException(s"Unsupported byte in StringLit: $c") + } + escape(in.tail, str + s) + } + } + escape(lit.array, "") + } + + // Turn raw parsed strings into String Literals + def unescape(raw: String): StringLit = { + @tailrec + def unescape(in: Array[Byte], out: Array[Byte]): Array[Byte] = { + if (in.isEmpty) out + else { + val c = in.head + if ((c < 0x20) || (c > 0x7e)) { // Range from ' ' to '~' + val msg = "Unsupported unescaped UTF-8 Byte 0x%02x! ".format(c) + + "Valid range [0x20-0x7e]" + throw new InvalidStringLitException(msg) + } + // Handle escaped characters + if (c == 0x5c) { // backslash + val (n: Int, bytes: Array[Byte]) = in(1) match { + case 0x5c => (2, Array[Byte](0x5c)) // backslash + case 0x6e => (2, Array[Byte](0x0a)) // newline + case 0x74 => (2, Array[Byte](0x09)) // tab + case 0x27 => (2, Array[Byte](0x27)) // single quote + case 0x22 => (2, Array[Byte](0x22)) // double quote + // TODO Finalize supported escapes, implement hex2Bytes + //case 0x78 => (4, hex2Bytes(in.slice(2, 3)))) // hex escape + //case 0x75 => (6, hex2Bytes(in.slice(2, 5))) // unicode excape + case e => { // error + val msg = s"Invalid escape character ${e.toChar}! " + + "Valid characters [nt'\"\\]" + throw new InvalidEscapeCharException(msg) + } + } + unescape(in.drop(n), out ++ bytes) // consume n + } else { + unescape(in.tail, out :+ in.head) + } + } + } + + val byteArray = raw.getBytes(java.nio.charset.StandardCharsets.UTF_8) + StringLit(unescape(byteArray, Array())) + } + + // Translate from FIRRTL formatting to the specific language's formatting + def format(lit: StringLit): StringLit = ??? + // Translate from the specific language's formatting to FIRRTL formatting + def unformat(lit: StringLit): StringLit = ??? +} + +object FIRRTLStringLitHandler extends StringLitHandler + +object VerilogStringLitHandler extends StringLitHandler { + // Change FIRRTL format string to Verilog format + override def format(lit: StringLit): StringLit = { + @tailrec + def format(in: Array[Byte], out: Array[Byte], percent: Boolean): Array[Byte] = { + if (in.isEmpty) out + else { + if (percent && in.head == 'x') format(in.tail, out :+ 'h'.toByte, false) + else format(in.tail, out :+ in.head, in.head == '%' && !percent) + } + } + StringLit(format(lit.array, Array(), false)) + } +} + diff --git a/src/main/scala/firrtl/Translator.scala b/src/main/scala/firrtl/Translator.scala index 1ca20346..07dd1b42 100644 --- a/src/main/scala/firrtl/Translator.scala +++ b/src/main/scala/firrtl/Translator.scala @@ -26,11 +26,9 @@ MODIFICATIONS. */ /* TODO - * - Add support for comments (that being said, current Scopers regex should ignore commented lines) * - Add better error messages for illformed FIRRTL * - Add support for files that do not have a circuit (like a module by itself in a file) * - Improve performance? Replace regex? - * - Add proper commnad-line arguments? * - Wrap in Reader subclass. This would have less memory footprint than creating a large string */ @@ -47,7 +45,27 @@ object Translator def addBrackets(inputIt: Iterator[String]): StringBuilder = { def countSpaces(s: String): Int = s.prefixLength(_ == ' ') - def stripComments(s: String): String = s takeWhile (!";".contains(_)) + def stripComments(s: String): String = { + // Delete anything after first semicolon that's not in a comment + var done = false + var inComment = false + var escape = false + var i = 0 + while (!done && i < s.length) { + val c = s(i) + if (c == ';') { + if (!inComment) { + done = true + i = i - 1 // remove semicolon as well as what follows + } + } else { + if (c == '"' && !escape) inComment = !inComment + escape = if (c == '\\' && !escape) true else false + } + i += 1 + } + s.take(i) + } val scopers = """(circuit|module|when|else|mem|with)""" val MultiLineScope = ("""(.*""" + scopers + """)(.*:\s*)""").r diff --git a/src/main/scala/firrtl/Visitor.scala b/src/main/scala/firrtl/Visitor.scala index 5204ce8e..222daf8a 100644 --- a/src/main/scala/firrtl/Visitor.scala +++ b/src/main/scala/firrtl/Visitor.scala @@ -41,6 +41,7 @@ import scala.collection.JavaConversions._ import antlr._ import PrimOps._ import FIRRTLParser._ +import scala.annotation.tailrec class Visitor(val fullFilename: String) extends FIRRTLBaseVisitor[AST] { @@ -166,6 +167,12 @@ class Visitor(val fullFilename: String) extends FIRRTLBaseVisitor[AST] map.getOrElse("writer", Seq()).map(x => (x.getText)), map.getOrElse("readwriter", Seq()).map(x => (x.getText))) } + // visitStringLit + private def visitStringLit[AST](node: TerminalNode): StringLit = { + val raw = node.getText.tail.init // Remove surrounding double quotes + FIRRTLStringLitHandler.unescape(raw) + } + // visitStmt private def visitStmt[AST](ctx: FIRRTLParser.StmtContext): Stmt = { val info = getInfo(ctx) @@ -201,8 +208,7 @@ class Visitor(val fullFilename: String) extends FIRRTLBaseVisitor[AST] Conditionally(info, visitExp(ctx.exp(0)), visitBlock(ctx.block(0)), alt) } case "stop(" => Stop(info, string2Int(ctx.IntLit(0).getText), visitExp(ctx.exp(0)), visitExp(ctx.exp(1))) - // Stip first and last character of string since they are the surrounding double quotes - case "printf(" => Print(info, ctx.StringLit.getText.tail.init, ctx.exp.drop(2).map(visitExp), + case "printf(" => Print(info, visitStringLit(ctx.StringLit), ctx.exp.drop(2).map(visitExp), visitExp(ctx.exp(0)), visitExp(ctx.exp(1))) case "skip" => Empty() // If we don't match on the first child, try the next one diff --git a/src/main/scala/firrtl/passes/Checks.scala b/src/main/scala/firrtl/passes/Checks.scala index 9a4e5eb6..8e278120 100644 --- a/src/main/scala/firrtl/passes/Checks.scala +++ b/src/main/scala/firrtl/passes/Checks.scala @@ -59,7 +59,6 @@ object CheckHighForm extends Pass with LazyLogging { class NegWidthException extends PassException(s"${sinfo}: [module ${mname}] Width cannot be negative or zero.") class NegVecSizeException extends PassException(s"${sinfo}: [module ${mname}] Vector type size cannot be negative.") class NegMemSizeException extends PassException(s"${sinfo}: [module ${mname}] Memory size cannot be negative or zero.") - // Note the following awkward strings are due to an issue with Scala string interpolation and escaped double quotes class BadPrintfException(x: Char) extends PassException(s"${sinfo}: [module ${mname}] Bad printf format: " + "\"%" + x + "\"") class BadPrintfTrailingException extends PassException(s"${sinfo}: [module ${mname}] Bad printf format: trailing " + "\"%\"") class BadPrintfIncorrectNumException extends PassException(s"${sinfo}: [module ${mname}] Bad printf format: incorrect number of arguments") @@ -164,16 +163,16 @@ object CheckHighForm extends Pass with LazyLogging { } } - def checkFstring(s: String, i: Int) = { - val validFormats = "bedxs" + def checkFstring(s: StringLit, i: Int) = { + val validFormats = "bdx" var percent = false - var ret = true var npercents = 0 - for (x <- s) { - if (!validFormats.contains(x) && percent) - errors.append(new BadPrintfException(x)) - if (x == '%') npercents = npercents + 1 - percent = (x == '%') + s.array.foreach { b => + if (percent) { + if (validFormats.contains(b)) npercents += 1 + else if (b != '%') errors.append(new BadPrintfException(b.toChar)) + } + percent = if (b == '%') !percent else false // %% -> percent = false } if (percent) errors.append(new BadPrintfTrailingException) if (npercents != i) errors.append(new BadPrintfIncorrectNumException) diff --git a/src/main/scala/firrtl/passes/Passes.scala b/src/main/scala/firrtl/passes/Passes.scala index 1e8ceae2..0bedcbeb 100644 --- a/src/main/scala/firrtl/passes/Passes.scala +++ b/src/main/scala/firrtl/passes/Passes.scala @@ -1260,7 +1260,13 @@ object VerilogWrap extends Pass { case (e) => e } } - def v_wrap_s (s:Stmt) : Stmt = s map (v_wrap_s) map (v_wrap_e) + def v_wrap_s (s:Stmt) : Stmt = { + s map (v_wrap_s) map (v_wrap_e) match { + case s: Print => + Print(s.info, VerilogStringLitHandler.format(s.string), s.args, s.clk, s.en) + case s => s + } + } def run (c:Circuit): Circuit = { val modulesx = c.modules.map{ m => { (m) match { |
