summaryrefslogtreecommitdiffstats
path: root/public/prism/prism-qsharp.js
blob: 1623dec67c3d48642779047a99cb3e02439bf0c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
(function (Prism) {
  /**
   * Replaces all placeholders "<<n>>" of given pattern with the n-th replacement (zero based).
   *
   * Note: This is a simple text based replacement. Be careful when using backreferences!
   *
   * @param {string} pattern the given pattern.
   * @param {string[]} replacements a list of replacement which can be inserted into the given pattern.
   * @returns {string} the pattern with all placeholders replaced with their corresponding replacements.
   * @example replace(/a<<0>>a/.source, [/b+/.source]) === /a(?:b+)a/.source
   */
  function replace(pattern, replacements) {
    return pattern.replace(/<<(\d+)>>/g, function (m, index) {
      return '(?:' + replacements[+index] + ')';
    });
  }
  /**
   * @param {string} pattern
   * @param {string[]} replacements
   * @param {string} [flags]
   * @returns {RegExp}
   */
  function re(pattern, replacements, flags) {
    return RegExp(replace(pattern, replacements), flags || '');
  }

  /**
   * Creates a nested pattern where all occurrences of the string `<<self>>` are replaced with the pattern itself.
   *
   * @param {string} pattern
   * @param {number} depthLog2
   * @returns {string}
   */
  function nested(pattern, depthLog2) {
    for (var i = 0; i < depthLog2; i++) {
      pattern = pattern.replace(/<<self>>/g, function () {
        return '(?:' + pattern + ')';
      });
    }
    return pattern.replace(/<<self>>/g, '[^\\s\\S]');
  }

  // https://docs.microsoft.com/en-us/azure/quantum/user-guide/language/typesystem/
  // https://github.com/microsoft/qsharp-language/tree/main/Specifications/Language/5_Grammar
  var keywordKinds = {
    // keywords which represent a return or variable type
    type: 'Adj BigInt Bool Ctl Double false Int One Pauli PauliI PauliX PauliY PauliZ Qubit Range Result String true Unit Zero',
    // all other keywords
    other:
      'Adjoint adjoint apply as auto body borrow borrowing Controlled controlled distribute elif else fail fixup for function if in internal intrinsic invert is let mutable namespace new newtype open operation repeat return self set until use using while within',
  };
  // keywords
  function keywordsToPattern(words) {
    return '\\b(?:' + words.trim().replace(/ /g, '|') + ')\\b';
  }
  var keywords = RegExp(
    keywordsToPattern(keywordKinds.type + ' ' + keywordKinds.other)
  );

  // types
  var identifier = /\b[A-Za-z_]\w*\b/.source;
  var qualifiedName = replace(/<<0>>(?:\s*\.\s*<<0>>)*/.source, [identifier]);

  var typeInside = {
    keyword: keywords,
    punctuation: /[<>()?,.:[\]]/,
  };

  // strings
  var regularString = /"(?:\\.|[^\\"])*"/.source;

  Prism.languages.qsharp = Prism.languages.extend('clike', {
    comment: /\/\/.*/,
    string: [
      {
        pattern: re(/(^|[^$\\])<<0>>/.source, [regularString]),
        lookbehind: true,
        greedy: true,
      },
    ],
    'class-name': [
      {
        // open Microsoft.Quantum.Canon;
        // open Microsoft.Quantum.Canon as CN;
        pattern: re(/(\b(?:as|open)\s+)<<0>>(?=\s*(?:;|as\b))/.source, [
          qualifiedName,
        ]),
        lookbehind: true,
        inside: typeInside,
      },
      {
        // namespace Quantum.App1;
        pattern: re(/(\bnamespace\s+)<<0>>(?=\s*\{)/.source, [qualifiedName]),
        lookbehind: true,
        inside: typeInside,
      },
    ],
    keyword: keywords,
    number:
      /(?:\b0(?:x[\da-f]+|b[01]+|o[0-7]+)|(?:\B\.\d+|\b\d+(?:\.\d*)?)(?:e[-+]?\d+)?)l?\b/i,
    operator:
      /\band=|\bor=|\band\b|\bnot\b|\bor\b|<[-=]|[-=]>|>>>=?|<<<=?|\^\^\^=?|\|\|\|=?|&&&=?|w\/=?|~~~|[*\/+\-^=!%]=?/,
    punctuation: /::|[{}[\];(),.:]/,
  });

  Prism.languages.insertBefore('qsharp', 'number', {
    range: {
      pattern: /\.\./,
      alias: 'operator',
    },
  });

  // single line
  var interpolationExpr = nested(
    replace(/\{(?:[^"{}]|<<0>>|<<self>>)*\}/.source, [regularString]),
    2
  );

  Prism.languages.insertBefore('qsharp', 'string', {
    'interpolation-string': {
      pattern: re(/\$"(?:\\.|<<0>>|[^\\"{])*"/.source, [interpolationExpr]),
      greedy: true,
      inside: {
        interpolation: {
          pattern: re(/((?:^|[^\\])(?:\\\\)*)<<0>>/.source, [
            interpolationExpr,
          ]),
          lookbehind: true,
          inside: {
            punctuation: /^\{|\}$/,
            expression: {
              pattern: /[\s\S]+/,
              alias: 'language-qsharp',
              inside: Prism.languages.qsharp,
            },
          },
        },
        string: /[\s\S]+/,
      },
    },
  });
})(Prism);

Prism.languages.qs = Prism.languages.qsharp;
the expression. E.g. `"${42}"` * @returns {Token} */ function tokenizeInterpolationExpression(expression) { var tempGrammar = {}; tempGrammar['interpolation-punctuation'] = interpolationPunctuationObject; /** @type {Array} */ var tokens = Prism.tokenize(expression, tempGrammar); if (tokens.length === 3) { /** * The token array will look like this * [ * ["interpolation-punctuation", "${"] * "..." // JavaScript expression of the interpolation * ["interpolation-punctuation", "}"] * ] */ var args = [1, 1]; args.push.apply( args, tokenizeWithHooks(tokens[1], Prism.languages.javascript, 'javascript') ); tokens.splice.apply(tokens, args); } return new Prism.Token( 'interpolation', tokens, interpolationObject.alias, expression ); } /** * Tokenizes the given code with support for JavaScript interpolation expressions mixed in. * * This function has 3 phases: * * 1. Replace all JavaScript interpolation expression with a placeholder. * The placeholder will have the syntax of a identify of the target language. * 2. Tokenize the code with placeholders. * 3. Tokenize the interpolation expressions and re-insert them into the tokenize code. * The insertion only works if a placeholder hasn't been "ripped apart" meaning that the placeholder has been * tokenized as two tokens by the grammar of the embedded language. * * @param {string} code * @param {object} grammar * @param {string} language * @returns {Token} */ function tokenizeEmbedded(code, grammar, language) { // 1. First filter out all interpolations // because they might be escaped, we need a lookbehind, so we use Prism /** @type {(Token|string)[]} */ var _tokens = Prism.tokenize(code, { interpolation: { pattern: RegExp(interpolationPattern), lookbehind: true, }, }); // replace all interpolations with a placeholder which is not in the code already var placeholderCounter = 0; /** @type {Object<string, string>} */ var placeholderMap = {}; var embeddedCode = _tokens .map(function (token) { if (typeof token === 'string') { return token; } else { var interpolationExpression = token.content; var placeholder; while ( code.indexOf( (placeholder = getPlaceholder(placeholderCounter++, language)) ) !== -1 ) { /* noop */ } placeholderMap[placeholder] = interpolationExpression; return placeholder; } }) .join(''); // 2. Tokenize the embedded code var embeddedTokens = tokenizeWithHooks(embeddedCode, grammar, language); // 3. Re-insert the interpolation var placeholders = Object.keys(placeholderMap); placeholderCounter = 0; /** * * @param {(Token|string)[]} tokens * @returns {void} */ function walkTokens(tokens) { for (var i = 0; i < tokens.length; i++) { if (placeholderCounter >= placeholders.length) { return; } var token = tokens[i]; if (typeof token === 'string' || typeof token.content === 'string') { var placeholder = placeholders[placeholderCounter]; var s = typeof token === 'string' ? token : /** @type {string} */ (token.content); var index = s.indexOf(placeholder); if (index !== -1) { ++placeholderCounter; var before = s.substring(0, index); var middle = tokenizeInterpolationExpression( placeholderMap[placeholder] ); var after = s.substring(index + placeholder.length); var replacement = []; if (before) { replacement.push(before); } replacement.push(middle); if (after) { var afterTokens = [after]; walkTokens(afterTokens); replacement.push.apply(replacement, afterTokens); } if (typeof token === 'string') { tokens.splice.apply(tokens, [i, 1].concat(replacement)); i += replacement.length - 1; } else { token.content = replacement; } } } else { var content = token.content; if (Array.isArray(content)) { walkTokens(content); } else { walkTokens([content]); } } } } walkTokens(embeddedTokens); return new Prism.Token( language, embeddedTokens, 'language-' + language, code ); } /** * The languages for which JS templating will handle tagged template literals. * * JS templating isn't active for only JavaScript but also related languages like TypeScript, JSX, and TSX. */ var supportedLanguages = { javascript: true, js: true, typescript: true, ts: true, jsx: true, tsx: true, }; Prism.hooks.add('after-tokenize', function (env) { if (!(env.language in supportedLanguages)) { return; } /** * Finds and tokenizes all template strings with an embedded languages. * * @param {(Token | string)[]} tokens * @returns {void} */ function findTemplateStrings(tokens) { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; if (typeof token === 'string') { continue; } var content = token.content; if (!Array.isArray(content)) { if (typeof content !== 'string') { findTemplateStrings([content]); } continue; } if (token.type === 'template-string') { /** * A JavaScript template-string token will look like this: * * ["template-string", [ * ["template-punctuation", "`"], * ( * An array of "string" and "interpolation" tokens. This is the simple string case. * or * ["embedded-code", "..."] This is the token containing the embedded code. * It also has an alias which is the language of the embedded code. * ), * ["template-punctuation", "`"] * ]] */ var embedded = content[1]; if ( content.length === 3 && typeof embedded !== 'string' && embedded.type === 'embedded-code' ) { // get string content var code = stringContent(embedded); var alias = embedded.alias; var language = Array.isArray(alias) ? alias[0] : alias; var grammar = Prism.languages[language]; if (!grammar) { // the embedded language isn't registered. continue; } content[1] = tokenizeEmbedded(code, grammar, language); } } else { findTemplateStrings(content); } } } findTemplateStrings(env.tokens); }); /** * Returns the string content of a token or token stream. * * @param {string | Token | (string | Token)[]} value * @returns {string} */ function stringContent(value) { if (typeof value === 'string') { return value; } else if (Array.isArray(value)) { return value.map(stringContent).join(''); } else { return stringContent(value.content); } } })(Prism);