diff --git a/components.js b/components.js index 01cba3a46e..774bf2a88b 100644 --- a/components.js +++ b/components.js @@ -266,6 +266,10 @@ var components = { "title": "NASM", "owner": "rbmj" }, + "nim": { + "title": "Nim", + "owner": "Golmote" + }, "nsis": { "title": "NSIS", "owner": "idleberg" diff --git a/components/prism-nim.js b/components/prism-nim.js new file mode 100644 index 0000000000..8592bc3a4b --- /dev/null +++ b/components/prism-nim.js @@ -0,0 +1,30 @@ +Prism.languages.nim = { + 'comment': /#.*/, + // Double-quoted strings can be prefixed by an identifier (Generalized raw string literals) + // Character literals are handled specifically to prevent issues with numeric type suffixes + 'string': /(?:(?:\b(?!\d)(?:\w|\\x[8-9a-fA-F][0-9a-fA-F])+)?(?:"""[\s\S]*?"""(?!")|"(?:\\[\s\S]|""|[^"\\])*")|'(?:\\(?:\d+|x[\da-fA-F]{2}|.)|[^'])')/, + // The negative look ahead prevents wrong highlighting of the .. operator + 'number': /\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:(?!\.\.)\.[\d_]*)?(?:[eE][+-]?\d[\d_]*)?)(?:'?[iuf]\d*)?/, + 'keyword': /\b(?:addr|as|asm|atomic|bind|block|break|case|cast|concept|const|continue|converter|defer|discard|distinct|do|elif|else|end|enum|except|export|finally|for|from|func|generic|if|import|include|interface|iterator|let|macro|method|mixin|nil|object|out|proc|ptr|raise|ref|return|static|template|try|tuple|type|using|var|when|while|with|without|yield)\b/, + 'function': { + pattern: /(?:(?!\d)(?:\w|\\x[8-9a-fA-F][0-9a-fA-F])+|`[^`\r\n]+`)\*?(?:\[[^\]]+\])?(?=\s*\()/, + inside: { + 'operator': /\*$/ + } + }, + // We don't want to highlight operators inside backticks + 'ignore': { + pattern: /`[^`\r\n]+`/, + inside: { + 'punctuation': /`/ + } + }, + 'operator': { + // Look behind and look ahead prevent wrong highlighting of punctuations [. .] {. .} (. .) + // but allow the slice operator .. to take precedence over them + // One can define his own operators in Nim so all combination of operators might be an operator. + pattern: /(^|[({\[](?=\.\.)|(?![({\[]\.).)(?:(?:[=+\-*\/<>@$~&%|!?^:\\]|\.\.|\.(?![)}\]]))+|\b(?:and|div|of|or|in|is|isnot|mod|not|notin|shl|shr|xor)\b)/m, + lookbehind: true + }, + 'punctuation': /[({\[]\.|\.[)}\]]|[`(){}\[\],:]/ +}; \ No newline at end of file diff --git a/components/prism-nim.min.js b/components/prism-nim.min.js new file mode 100644 index 0000000000..e45372ed29 --- /dev/null +++ b/components/prism-nim.min.js @@ -0,0 +1 @@ +Prism.languages.nim={comment:/#.*/,string:/(?:(?:\b(?!\d)(?:\w|\\x[8-9a-fA-F][0-9a-fA-F])+)?(?:"""[\s\S]*?"""(?!")|"(?:\\[\s\S]|""|[^"\\])*")|'(?:\\(?:\d+|x[\da-fA-F]{2}|.)|[^'])')/,number:/\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:(?!\.\.)\.[\d_]*)?(?:[eE][+-]?\d[\d_]*)?)(?:'?[iuf]\d*)?/,keyword:/\b(?:addr|as|asm|atomic|bind|block|break|case|cast|concept|const|continue|converter|defer|discard|distinct|do|elif|else|end|enum|except|export|finally|for|from|func|generic|if|import|include|interface|iterator|let|macro|method|mixin|nil|object|out|proc|ptr|raise|ref|return|static|template|try|tuple|type|using|var|when|while|with|without|yield)\b/,"function":{pattern:/(?:(?!\d)(?:\w|\\x[8-9a-fA-F][0-9a-fA-F])+|`[^`\r\n]+`)\*?(?:\[[^\]]+\])?(?=\s*\()/,inside:{operator:/\*$/}},ignore:{pattern:/`[^`\r\n]+`/,inside:{punctuation:/`/}},operator:{pattern:/(^|[({\[](?=\.\.)|(?![({\[]\.).)(?:(?:[=+\-*\/<>@$~&%|!?^:\\]|\.\.|\.(?![)}\]]))+|\b(?:and|div|of|or|in|is|isnot|mod|not|notin|shl|shr|xor)\b)/m,lookbehind:!0},punctuation:/[({\[]\.|\.[)}\]]|[`(){}\[\],:]/}; \ No newline at end of file diff --git a/examples/prism-nim.html b/examples/prism-nim.html new file mode 100644 index 0000000000..5575374f03 --- /dev/null +++ b/examples/prism-nim.html @@ -0,0 +1,235 @@ +

Nim

+

To use this language, use the class "language-nim".

+ +

Comments

+
# This is a comment
+ +

Strings

+
"This is a string."
+"This is a string with \"quotes\" in it."
+"""This is
+a "multi-line"
+string."""
+""""A long string within quotes.""""
+R"This is a raw string."
+r"Some ""quotes"" inside a raw string."
+r"""Raw strings
+can also be multi-line."""
+foo"This is a generalized raw string literal."
+bar"""This is also
+a generalized raw string literal."""
+ +

Characters

+
'a'
+'\''
+'\t'
+'\15'
+'\xFC'
+ +

Numbers

+
42
+0xaf
+0xf_2_c
+0o07
+0b1111_0000
+0B0_10001110100_0000101001000111101011101111111011000101001101001001'f64
+9_000'u
+32.
+32.1f32
+32.e-5
+32.2e+2
+2'i16
+2i16
+0xfe'f32
+ +

Full example

+
# Example from http://nim-by-example.github.io/oop_macro/
+import macros
+
+macro class*(head: expr, body: stmt): stmt {.immediate.} =
+  # The macro is immediate so that it doesn't
+  # resolve identifiers passed to it
+
+  var typeName, baseName: NimNode
+
+  if head.kind == nnkIdent:
+    # `head` is expression `typeName`
+    # echo head.treeRepr
+    # --------------------
+    # Ident !"Animal"
+    typeName = head
+
+  elif head.kind == nnkInfix and $head[0] == "of":
+    # `head` is expression `typeName of baseClass`
+    # echo head.treeRepr
+    # --------------------
+    # Infix
+    #   Ident !"of"
+    #   Ident !"Animal"
+    #   Ident !"RootObj"
+    typeName = head[1]
+    baseName = head[2]
+
+  else:
+    quit "Invalid node: " & head.lispRepr
+
+  # echo treeRepr(body)
+  # --------------------
+  # StmtList
+  #   VarSection
+  #     IdentDefs
+  #       Ident !"name"
+  #       Ident !"string"
+  #       Empty
+  #     IdentDefs
+  #       Ident !"age"
+  #       Ident !"int"
+  #       Empty
+  #   MethodDef
+  #     Ident !"vocalize"
+  #     Empty
+  #     Empty
+  #     FormalParams
+  #       Ident !"string"
+  #     Empty
+  #     Empty
+  #     StmtList
+  #       StrLit ...
+  #   MethodDef
+  #     Ident !"age_human_yrs"
+  #     Empty
+  #     Empty
+  #     FormalParams
+  #       Ident !"int"
+  #     Empty
+  #     Empty
+  #     StmtList
+  #       DotExpr
+  #         Ident !"this"
+  #         Ident !"age"
+
+  # create a new stmtList for the result
+  result = newStmtList()
+
+  # var declarations will be turned into object fields
+  var recList = newNimNode(nnkRecList)
+
+  # Iterate over the statements, adding `this: T`
+  # to the parameters of functions
+  for node in body.children:
+    case node.kind:
+
+      of nnkMethodDef, nnkProcDef:
+        # inject `this: T` into the arguments
+        let p = copyNimTree(node.params)
+        p.insert(1, newIdentDefs(ident"this", typeName))
+        node.params = p
+        result.add(node)
+
+      of nnkVarSection:
+        # variables get turned into fields of the type.
+        for n in node.children:
+          recList.add(n)
+
+      else:
+        result.add(node)
+
+  # The following prints out the AST structure:
+  #
+  # import macros
+  # dumptree:
+  #   type X = ref object of Y
+  #     z: int
+  # --------------------
+  # TypeSection
+  #   TypeDef
+  #     Ident !"X"
+  #     Empty
+  #     RefTy
+  #       ObjectTy
+  #         Empty
+  #         OfInherit
+  #           Ident !"Y"
+  #         RecList
+  #           IdentDefs
+  #             Ident !"z"
+  #             Ident !"int"
+  #             Empty
+
+  result.insert(0,
+    if baseName == nil:
+      quote do:
+        type `typeName` = ref object of RootObj
+    else:
+      quote do:
+        type `typeName` = ref object of `baseName`
+  )
+  # Inspect the tree structure:
+  #
+  # echo result.treeRepr
+  # --------------------
+  # StmtList
+  #   StmtList
+  #     TypeSection
+  #       TypeDef
+  #         Ident !"Animal"
+  #         Empty
+  #         RefTy
+  #           ObjectTy
+  #             Empty
+  #             OfInherit
+  #               Ident !"RootObj"
+  #             Empty   <= We want to replace this
+  #   MethodDef
+  #   ...
+
+  result[0][0][0][2][0][2] = recList
+
+  # Lets inspect the human-readable version of the output
+  # echo repr(result)
+  # Output:
+  #  type
+  #    Animal = ref object of RootObj
+  #      name: string
+  #      age: int
+  #
+  #  method vocalize(this: Animal): string =
+  #    "..."
+  #
+  #  method age_human_yrs(this: Animal): int =
+  #    this.age
+
+# ---
+
+class Animal of RootObj:
+  var name: string
+  var age: int
+  method vocalize: string = "..."
+  method age_human_yrs: int = this.age # `this` is injected
+
+class Dog of Animal:
+  method vocalize: string = "woof"
+  method age_human_yrs: int = this.age * 7
+
+class Cat of Animal:
+  method vocalize: string = "meow"
+
+# ---
+
+var animals: seq[Animal] = @[]
+animals.add(Dog(name: "Sparky", age: 10))
+animals.add(Cat(name: "Mitten", age: 10))
+
+for a in animals:
+  echo a.vocalize()
+  echo a.age_human_yrs()
+ +

Known failures

+

There are certain edge cases where Prism will fail. + There are always such cases in every regex-based syntax highlighter. + However, Prism dares to be open and honest about them. + If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. +

+ +

Comment-like substrings

+
"This # is a broken string"
\ No newline at end of file diff --git a/tests/languages/nim/comment_feature.test b/tests/languages/nim/comment_feature.test new file mode 100644 index 0000000000..09493ab581 --- /dev/null +++ b/tests/languages/nim/comment_feature.test @@ -0,0 +1,13 @@ +# +# Foobar + +---------------------------------------------------- + +[ + ["comment", "#"], + ["comment", "# Foobar"] +] + +---------------------------------------------------- + +Checks for comments. \ No newline at end of file diff --git a/tests/languages/nim/function_feature.test b/tests/languages/nim/function_feature.test new file mode 100644 index 0000000000..899ad50f30 --- /dev/null +++ b/tests/languages/nim/function_feature.test @@ -0,0 +1,17 @@ +fo\x9ao( +class*( +takeV[T]( +`$`( + +---------------------------------------------------- + +[ + ["function", ["fo\\x9ao"]], ["punctuation", "("], + ["function", ["class", ["operator", "*"]]], ["punctuation", "("], + ["function", ["takeV[T]"]], ["punctuation", "("], + ["function", ["`$`"]], ["punctuation", "("] +] + +---------------------------------------------------- + +Checks for functions. \ No newline at end of file diff --git a/tests/languages/nim/keyword_feature.test b/tests/languages/nim/keyword_feature.test new file mode 100644 index 0000000000..02d92d6779 --- /dev/null +++ b/tests/languages/nim/keyword_feature.test @@ -0,0 +1,123 @@ +addr +as +asm +atomic +bind +block +break +case +cast +concept +const +continue +converter +defer +discard +distinct +do +elif +else +end +enum +except +export +finally +for +from +func +generic +if +import +include +interface +iterator +let +macro +method +mixin +nil +object +out +proc +ptr +raise +ref +return +static +template +try +tuple +type +using +var +when +while +with +without +yield + +---------------------------------------------------- + +[ + ["keyword", "addr"], + ["keyword", "as"], + ["keyword", "asm"], + ["keyword", "atomic"], + ["keyword", "bind"], + ["keyword", "block"], + ["keyword", "break"], + ["keyword", "case"], + ["keyword", "cast"], + ["keyword", "concept"], + ["keyword", "const"], + ["keyword", "continue"], + ["keyword", "converter"], + ["keyword", "defer"], + ["keyword", "discard"], + ["keyword", "distinct"], + ["keyword", "do"], + ["keyword", "elif"], + ["keyword", "else"], + ["keyword", "end"], + ["keyword", "enum"], + ["keyword", "except"], + ["keyword", "export"], + ["keyword", "finally"], + ["keyword", "for"], + ["keyword", "from"], + ["keyword", "func"], + ["keyword", "generic"], + ["keyword", "if"], + ["keyword", "import"], + ["keyword", "include"], + ["keyword", "interface"], + ["keyword", "iterator"], + ["keyword", "let"], + ["keyword", "macro"], + ["keyword", "method"], + ["keyword", "mixin"], + ["keyword", "nil"], + ["keyword", "object"], + ["keyword", "out"], + ["keyword", "proc"], + ["keyword", "ptr"], + ["keyword", "raise"], + ["keyword", "ref"], + ["keyword", "return"], + ["keyword", "static"], + ["keyword", "template"], + ["keyword", "try"], + ["keyword", "tuple"], + ["keyword", "type"], + ["keyword", "using"], + ["keyword", "var"], + ["keyword", "when"], + ["keyword", "while"], + ["keyword", "with"], + ["keyword", "without"], + ["keyword", "yield"] +] + +---------------------------------------------------- + +Checks for keywords. \ No newline at end of file diff --git a/tests/languages/nim/number_feature.test b/tests/languages/nim/number_feature.test new file mode 100644 index 0000000000..0587edb094 --- /dev/null +++ b/tests/languages/nim/number_feature.test @@ -0,0 +1,31 @@ +0xBad_Face +0o754_173 +0B1111_0000 +42_000 +3.14_15_9 +3E7 +0.5e-84_741 +9.8e+54 +9000'u +2'i16 +2i16 +0xfe'f32 + +---------------------------------------------------- + +[ + ["number", "0xBad_Face"], + ["number", "0o754_173"], + ["number", "0B1111_0000"], + ["number", "42_000"], + ["number", "3.14_15_9"], + ["number", "3E7"], + ["number", "0.5e-84_741"], + ["number", "9.8e+54"], + ["number", "9000'u"], + ["number", "2'i16"], + ["number", "2i16"], + ["number", "0xfe'f32"] +] + +---------------------------------------------------- diff --git a/tests/languages/nim/operator_feature.test b/tests/languages/nim/operator_feature.test new file mode 100644 index 0000000000..9981f4a402 --- /dev/null +++ b/tests/languages/nim/operator_feature.test @@ -0,0 +1,39 @@ += + - +* / +< > +@ $ ~ +& % | +! ? ^ +: \ +.. . + ++*<> + +and div of or +in is isnot mod +not notin +shl shr xor + +---------------------------------------------------- + +[ + ["operator", "="], ["operator", "+"], ["operator", "-"], + ["operator", "*"], ["operator", "/"], + ["operator", "<"], ["operator", ">"], + ["operator", "@"], ["operator", "$"], ["operator", "~"], + ["operator", "&"], ["operator", "%"], ["operator", "|"], + ["operator", "!"], ["operator", "?"], ["operator", "^"], + ["operator", ":"], ["operator", "\\"], + ["operator", ".."], ["operator", "."], + + ["operator", "+*<>"], + + ["operator", "and"], ["operator", "div"], ["operator", "of"], ["operator", "or"], + ["operator", "in"], ["operator", "is"], ["operator", "isnot"], ["operator", "mod"], + ["operator", "not"], ["operator", "notin"], + ["operator", "shl"], ["operator", "shr"], ["operator", "xor"] +] + +---------------------------------------------------- + +Checks for operators. \ No newline at end of file diff --git a/tests/languages/nim/string_feature.test b/tests/languages/nim/string_feature.test new file mode 100644 index 0000000000..cd41819d1d --- /dev/null +++ b/tests/languages/nim/string_feature.test @@ -0,0 +1,38 @@ +"" +"Fo\"obar" + +"""""" +"""Fo"o +bar""" + +R"Raw ""string" + +r"Raw +""string" + +fo\x8Fo"Foobar" + +bar"""Foo +bar""" + +'\'' +'\xFC' + +---------------------------------------------------- + +[ + ["string", "\"\""], + ["string", "\"Fo\\\"obar\""], + ["string", "\"\"\"\"\"\""], + ["string", "\"\"\"Fo\"o\r\nbar\"\"\""], + ["string", "R\"Raw \"\"string\""], + ["string", "r\"Raw\r\n\"\"string\""], + ["string", "fo\\x8Fo\"Foobar\""], + ["string", "bar\"\"\"Foo\r\nbar\"\"\""], + ["string", "'\\''"], + ["string", "'\\xFC'"] +] + +---------------------------------------------------- + +Checks for strings and character literals. \ No newline at end of file