diff options
Diffstat (limited to 'src')
| -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 | ||||
| -rw-r--r-- | src/test/resources/features/Printf.fir | 16 | ||||
| -rw-r--r-- | src/test/resources/top.cpp | 88 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/FirrtlSpec.scala | 6 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/StringSpec.scala | 113 |
13 files changed, 395 insertions, 37 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 { diff --git a/src/test/resources/features/Printf.fir b/src/test/resources/features/Printf.fir new file mode 100644 index 00000000..d0c1c775 --- /dev/null +++ b/src/test/resources/features/Printf.fir @@ -0,0 +1,16 @@ +circuit Top : + module Top : + input clk : Clock + input reset : UInt<1> + + reg count : UInt<10>, clk with : + reset => (reset, UInt<6>(0)) + reg const : UInt<32> clk with : + reset => (reset, UInt(123456)) + + node notReset = not(reset) + count <= add(count, UInt(1)) + printf(clk, notReset, "\tcount = %d 0x%x b%b\\\'%d%%\'\n", count, count, count, const) + + when eq(count, UInt(255)) : + stop(clk, UInt(1), 0) diff --git a/src/test/resources/top.cpp b/src/test/resources/top.cpp new file mode 100644 index 00000000..8bfe2a99 --- /dev/null +++ b/src/test/resources/top.cpp @@ -0,0 +1,88 @@ +#include <verilated.h> +#include <iostream> + +#if VM_TRACE +# include <verilated_vcd_c.h> // Trace file format header +#endif + +using namespace std; + +//VGCDTester *top; +TOP_TYPE *top; + +vluint64_t main_time = 0; // Current simulation time + // This is a 64-bit integer to reduce wrap over issues and + // allow modulus. You can also use a double, if you wish. + +double sc_time_stamp () { // Called by $time in Verilog + return main_time; // converts to double, to match + // what SystemC does +} + +// TODO Provide command-line options like vcd filename, timeout count, etc. +const long timeout = 100000000L; + +int main(int argc, char** argv) { + Verilated::commandArgs(argc, argv); // Remember args + top = new TOP_TYPE; + +#if VM_TRACE // If verilator was invoked with --trace + Verilated::traceEverOn(true); // Verilator must compute traced signals + VL_PRINTF("Enabling waves...\n"); + VerilatedVcdC* tfp = new VerilatedVcdC; + top->trace (tfp, 99); // Trace 99 levels of hierarchy + tfp->open ("dump.vcd"); // Open the dump file +#endif + + + top->reset = 1; + + cout << "Starting simulation!\n"; + + while (!Verilated::gotFinish() && main_time < timeout) { + if (main_time > 10) { + top->reset = 0; // Deassert reset + } + if ((main_time % 10) == 1) { + top->clk = 1; // Toggle clock + } + if ((main_time % 10) == 6) { + top->clk = 0; + } + top->eval(); // Evaluate model +#if VM_TRACE + if (tfp) tfp->dump (main_time); // Create waveform trace for this timestamp +#endif + main_time++; // Time passes... + } + + if (main_time >= timeout) { + cout << "Simulation terminated by timeout at time " << main_time << + " (cycle " << main_time / 10 << ")"<< endl; + return -1; + } else { + cout << "Simulation completed at time " << main_time << + " (cycle " << main_time / 10 << ")"<< endl; + } + + // Run for 10 more clocks + vluint64_t end_time = main_time + 100; + while (main_time < end_time) { + if ((main_time % 10) == 1) { + top->clk = 1; // Toggle clock + } + if ((main_time % 10) == 6) { + top->clk = 0; + } + top->eval(); // Evaluate model +#if VM_TRACE + if (tfp) tfp->dump (main_time); // Create waveform trace for this timestamp +#endif + main_time++; // Time passes... + } + +#if VM_TRACE + if (tfp) tfp->close(); +#endif +} + diff --git a/src/test/scala/firrtlTests/FirrtlSpec.scala b/src/test/scala/firrtlTests/FirrtlSpec.scala index 438a5282..a8ccb0a9 100644 --- a/src/test/scala/firrtlTests/FirrtlSpec.scala +++ b/src/test/scala/firrtlTests/FirrtlSpec.scala @@ -90,7 +90,7 @@ trait BackendCompilationUtilities { val e = Process(s"./V${prefix}", dir) ! ProcessLogger(line => { triggered = triggered || line.contains(assertionMsg) - System.out.println(line) + //System.out.println(line) }) triggered } @@ -101,7 +101,7 @@ trait BackendCompilationUtilities { } trait FirrtlRunners extends BackendCompilationUtilities { - lazy val cpp = new File(s"/integration/top.cpp") + lazy val cppHarness = new File(s"/top.cpp") def compileFirrtlTest(prefix: String, srcDir: String): File = { val testDir = createTempDirectory(prefix) copyResourceToFile(s"${srcDir}/${prefix}.fir", new File(testDir, s"${prefix}.fir")) @@ -112,7 +112,7 @@ trait FirrtlRunners extends BackendCompilationUtilities { def runFirrtlTest(prefix: String, srcDir: String) { val testDir = compileFirrtlTest(prefix, srcDir) val harness = new File(testDir, s"top.cpp") - copyResourceToFile(cpp.toString, harness) + copyResourceToFile(cppHarness.toString, harness) verilogToCpp(prefix, testDir, Seq(), harness).! cppToExe(prefix, testDir).! diff --git a/src/test/scala/firrtlTests/StringSpec.scala b/src/test/scala/firrtlTests/StringSpec.scala new file mode 100644 index 00000000..2278a147 --- /dev/null +++ b/src/test/scala/firrtlTests/StringSpec.scala @@ -0,0 +1,113 @@ + +package firrtlTests + +import firrtl._ + +import java.io._ + +import scala.sys.process._ +import org.scalatest._ +import org.scalatest.prop._ +import org.scalatest.Assertions._ +import org.scalacheck._ + +class PrintfSpec extends FirrtlPropSpec { + + property("Printf should correctly print values in each format %x, %d, %b") { + val prefix = "Printf" + val testDir = compileFirrtlTest(prefix, "/features") + val harness = new File(testDir, s"top.cpp") + copyResourceToFile(cppHarness.toString, harness) + + verilogToCpp(prefix, testDir, Seq(), harness).! + cppToExe(prefix, testDir).! + + // Check for correct Printf: + // Count up from 0, match decimal, hex, and binary + // see /features/Print.fir to see what we're matching + val regex = """\tcount\s+=\s+(\d+)\s+0x(\w+)\s+b([01]+).*""".r + var done = false + var expected = 0 + var error = false + val ret = Process(s"./V${prefix}", testDir) ! + ProcessLogger( line => { + line match { + case regex(dec, hex, bin) => { + if (!done) { + // Must mark error before assertion or sbt test will pass + if (Integer.parseInt(dec, 10) != expected) error = true + assert(Integer.parseInt(dec, 10) == expected) + if (Integer.parseInt(hex, 16) != expected) error = true + assert(Integer.parseInt(hex, 16) == expected) + if (Integer.parseInt(bin, 2) != expected) error = true + assert(Integer.parseInt(bin, 2) == expected) + expected += 1 + } + } + case _ => // Do Nothing + } + }) + if (error) fail() + } +} + +class StringSpec extends FirrtlPropSpec { + + // Whitelist is [0x20 - 0x7e] + val whitelist = + """ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ""" + + """[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" + val whitelistBA: Array[Byte] = Array.range(0x20, 0x7e) map (_.toByte) + + property(s"Character whitelist should be supported: [$whitelist] ") { + val lit = firrtl.FIRRTLStringLitHandler.unescape(whitelist) + // Check internal + lit.array zip whitelistBA foreach { case (b, e) => + assert(b == e, s"(${b.toChar} did not equal expected ${e.toChar})") + } + // Check result + assert(lit.serialize == whitelist) + } + + // Valid escapes = \n, \t, \\, \", \' + val esc = """\\\'\"\t\n""" + val validEsc = Seq('n', 't', '\\', '"', '\'') + property(s"Escape characters [$esc] should parse") { + val lit = firrtl.FIRRTLStringLitHandler.unescape(esc) + assert(lit.array(0) == 0x5c) + assert(lit.array(1) == 0x27) + assert(lit.array(2) == 0x22) + assert(lit.array(3) == 0x09) + assert(lit.array(4) == 0x0a) + assert(lit.array.length == 5) + } + + // Generators for random testing + val validChar = for (c <- Gen.choose(0x20.toChar, 0x7e.toChar) if c != '\\') yield c + val validCharSeq = Gen.containerOf[Seq, Char](validChar) + val invalidChar = Gen.oneOf(Gen.choose(0x00.toChar, 0x1f.toChar), + Gen.choose(0x7f.toChar, 0xff.toChar)) + val invalidEsc = for ( + c <- Gen.choose(0x00.toChar, 0xff.toChar + ) if (!validEsc.contains(c))) yield c + + property("Random invalid strings should fail") { + forAll(validCharSeq, invalidChar, validCharSeq) { + (head: Seq[Char], bad: Char, tail: Seq[Char]) => + val str = ((head :+ bad) ++ tail).mkString + intercept[InvalidStringLitException] { + firrtl.FIRRTLStringLitHandler.unescape(str) + } + } + } + + property(s"Invalid escape characters should fail") { + forAll(validCharSeq, invalidEsc, validCharSeq) { + (head: Seq[Char], badEsc: Char, tail: Seq[Char]) => + val str = (head ++ Seq('\\', badEsc) ++ tail).mkString + intercept[InvalidEscapeCharException] { + firrtl.FIRRTLStringLitHandler.unescape(str) + } + } + } +} |
