Fantom

 

//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   30 Jul 08  Brian Frank  Creation
//

using syntax
using fwt

class SyntaxTest : Test
{

//////////////////////////////////////////////////////////////////////////
// Setup
//////////////////////////////////////////////////////////////////////////

  const TextEditorOptions opt := TextEditorOptions.load
  const RichTextStyle t := opt.text
  const RichTextStyle s := opt.literal
  const RichTextStyle c := opt.comment
  const RichTextStyle b := opt.bracket
  const RichTextStyle bm := opt.bracketMatch

  Void testSetup()
  {
    verifyNotSame(t, s)
    verifyNotSame(t, c)
    verifyNotSame(t, b)
    verifyNotSame(s, c)
    verifyNotSame(s, b)
    verifyNotSame(c, b)
    verifyNotSame(b, bm)
  }

//////////////////////////////////////////////////////////////////////////
// Single line comments
//////////////////////////////////////////////////////////////////////////

  Void testComments()
  {
    verifySyntax("fan",
    "x // y\n" +
    "// z",
    [
      [0, t, 2, c],
      [0, c],
    ])
  }

//////////////////////////////////////////////////////////////////////////
// Block comments
//////////////////////////////////////////////////////////////////////////

  Void testMultiline1()
  {
    verifySyntax("fan",
   //0123456789
    "aa /* bb\n" +  // 0
    "ccc\n" +       // 1
    "dd */ eee",    // 2
    [
      [0, t, 3, c],
      [0, c],
      [0, c, 5, t],
    ])
  }

  Void testMultilineNested1()
  {
    verifySyntax("fan",
   //0123456789
    "x/* bb\n" +  // 0
    "{}\n" +      // 1
    "/*\n" +      // 2
    "a /* b /* c\n" +  // 3
    "c */ b */ c\n" +  // 4
    "*/\n" +      // 5
    "{}\n" +      // 6
    "dd */ eee",  // 7
    [
      [0, t, 1, c],
      [0, c],
      [0, c],
      [0, c],
      [0, c],
      [0, c],
      [0, c],
      [0, c, 5, t],
    ])
  }

  Void testMultilineNested2()
  {
    verifySyntax("fan",
   //0123456789
    "x /* y */ z /*\n" +   // 0
    "a /* b */ xx\n" +     // 1
    "\"foo\"\n" +          // 2
    "*/ c*d /* e */\n" +   // 3
    "x/* /* /* x */ x\n" + // 4
    "*/\n" +               // 5
    "*/foo",               // 6
    [
      [0, t, 2, c, 9, t, 12, c],
      [0, c],
      [0, c],
      [0, c, 2, t, 7, c],
      [0, t, 1, c],
      [0, c],
      [0, c, 2, t],
    ])
  }

  Void testMultilineUnnested()
  {
    verifySyntax("java",
   //0123456789
    "x /* y */ z /*\n" +   // 0
    "a /* {cool}\n" +      // 1
    "ab */ xx\n" +         // 2
    "/*\"foo\"\n" +        // 3
    "*/ c*d",              // 4
    [
      [0, t, 2, c, 9, t, 12, c],
      [0, c],
      [0, c, 5, t],
      [0, c],
      [0, c, 2, t],
    ])
  }

//////////////////////////////////////////////////////////////////////////
// Strs
//////////////////////////////////////////////////////////////////////////

  Void testStrs()
  {
    verifySyntax("fan",
                          //    0123456789_12345
    "x\"foo\"y\n" +       // 0  x"foo"y
    "x'c'y\n" +           // 1  x'c'y
    "`/bar`y\n" +         // 2  `/bar`y
    "a\"b\\\"c\"d\n" +    // 3  a"b\"c"d
    "'\\\\'+`x\\`x`!\n" + // 4  '\\'+`x\`x`!
    "\"x\\\\\"!\n" +      // 5  "x\\"!
    "{\"x\\\\\\\"y\"}\n"+ // 6  {"x\\\"y"}
    "\"a\",\"b\",`c`,`d`",// 7  "a","b",`c`,`d`
                          //    0123456789_12345
    [
      [0, t, 1, s, 6, t],
      [0, t, 1, s, 4, t],
      [0, s, 6, t],
      [0, t, 1, s, 7, t],
      [0, s, 4, t, 5, s, 11, t],
      [0, s, 5, t],
      [0, b, 1, s, 9, b],
      [0, s, 3, t, 4, s, 7, t, 8, s, 11, t, 12, s],
    ])
  }

  Void testMultiLineStr()
  {
    verifySyntax("fan",
                       //    0123456789_12345
    "x\"foo\n" +       // 0  x"foo
    "// string!\n" +   // 1  // string
    "a=\\\"b\\\"\n" +  // 2  a=\"b\"
    "bar\"baz\"\n" +   // 3  bar"baz"
    "\";",             // 4  ";
    [
      [0, t, 1, s],
      [0, s],
      [0, s],
      [0, s, 4, t, 7, s],
      [0, s, 1, t],
    ])
  }

//////////////////////////////////////////////////////////////////////////
// Mixed Blocks
//////////////////////////////////////////////////////////////////////////

  Void testMixedBlocks()
  {
    verifySyntax("fan",
                       //    0123456789_12345
    "x\"foo/*\n" +     // 0  x"foo/*
    "/*\n" +           // 1  /*
    "bar*/ */\"baz",   // 2  bar*/ */"baz
    [
      [0, t, 1, s],
      [0, s],
      [0, s, 9, t],
    ])
  }

//////////////////////////////////////////////////////////////////////////
// Bracket Matching
//////////////////////////////////////////////////////////////////////////

  Void testBracketMatching()
  {
    // basics
    verifyBrackets("fan", "{}",  0, 0, [[0, bm, 1, bm]])
    verifyBrackets("fan", "x{}", 0, 1, [[0, t, 1, bm, 2, bm]])
    verifyBrackets("fan", "{x}", 0, 2, [[0, bm, 1, t, 2, bm]])
    verifyBrackets("fan", "{}x", 0, 1, [[0, bm, 1, bm, 2, t]])
    verifyBrackets("fan", "xx{}", 0, 2, [[0, t, 2, bm, 3, bm]])
    verifyBrackets("fan", "{xx}", 0, 3, [[0, bm, 1, t, 3, bm]])
    verifyBrackets("fan", "{}xx", 0, 1, [[0, bm, 1, bm, 2, t]])
    verifyBrackets("fan", "x{}x", 0, 1, [[0, t, 1, bm, 2, bm, 3, t]])
    verifyBrackets("fan", "x{x}x", 0, 1, [[0, t, 1, bm, 2, t, 3, bm, 4, t]])

    // nested
    verifyBrackets("fan", "[[]]",    0, 0, [[0, bm, 1, b, 3, bm]])
    verifyBrackets("fan", "[[]]",    0, 1, [[0, b, 1, bm, 2, bm, 3, b]])
    verifyBrackets("fan", "[[[]]]",  0, 0, [[0, bm, 1, b, 5, bm]])
    verifyBrackets("fan", "[[[]]]",  0, 1, [[0, b, 1, bm, 2, b, 4, bm, 5, b]])

    // multi-line (have spurious extra no-length text segments)
    verifyBrackets("fan", "{\n}",   0, 0, [[0, bm, 1, t], [0, bm, 1, t]])
    verifyBrackets("fan", "{\n\n}", 0, 0, [[0, bm, 1, t], [0, t], [0, bm, 1, t]])
    verifyBrackets("fan", "x{\n}",  0, 1, [[0, t, 1, bm], [0, bm, 1, t]])
    verifyBrackets("fan", "{x\n}",  0, 0, [[0, bm, 1, t], [0, bm, 1, t]])
    verifyBrackets("fan", "{\nx}",  0, 0, [[0, bm, 1, t], [0, t, 1, bm]])
    verifyBrackets("fan", "{\n}x",  0, 0, [[0, bm, 1, t], [0, bm, 1, t]])
  }

  Void verifyBrackets(Str ext, Str src, Int line, Int col, Obj[][] styling, Bool testReverse := true)
  {
    doc := Doc(TextEditorOptions.load,
               SyntaxRules.loadForExt(ext))
    doc.text = src

    a := doc.offsetAtLine(line) + col
    b := doc.matchBracket(a)
    verify(b != null)

    bLine := doc.lineAtOffset(b)
    bCol  := b - doc.offsetAtLine(bLine)
    doc.setBracketMatch(line, col, bLine, bCol)

    verifySyntaxDoc(doc, styling)

    if (testReverse)
      verifyBrackets(ext, src, doc.bracketLine2, doc.bracketCol2, styling, false)
  }

//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////

  Void verifySyntax(Str ext, Str src, Obj[][] styling)
  {
    doc := Doc(TextEditorOptions.load,
               SyntaxRules.loadForExt(ext))
    doc.text = src
    verifySyntaxDoc(doc, styling)
  }

  Void verifySyntaxDoc(Doc doc, Obj[][] styling)
  {
    trace := false
    if (trace) echo("###################")
    if (trace) doc.dump
    if (trace) echo("")
    styling.each |Obj[] expected, Int i|
    {
      actual := doc.lineStyling(i)
      if (trace) echo("Line $i: " + doc.line(i))
      if (trace) echo("         " + stylingToStr(expected))
      if (trace) echo("         " + stylingToStr(actual))
      verifyEq(expected, actual)
      expected.size.times |Int j|
      {
        verifySame(expected[j], actual[j])
      }
    }
    verifyEq(doc.lineCount, styling.size)
  }

  Str stylingToStr(Obj[] styling)
  {
    return styling.join(", ") |Obj x, Int i->Str|
    {
      if (i.isEven) return x.toStr
      if (x === t)  return "t"
      if (x === c)  return "c"
      if (x === s)  return "s"
      if (x === b)  return "b"
      if (x === bm) return "bm"
      return "?"
    }
  }

}