Commit 481a24cc authored by Phil Jones's avatar Phil Jones

Add Finesse 3 style KatScript highlighter.

The necessary regex in `src/codemirror-finesse3-types.ts` is generated manually by `generateregex.py`, as `finesse` isn't on PyPI at the moment and so can't be used as a build requirement. In the future, this could be done as part of the build, so the pre-made typescript file can be removed.
parent 30fda89e
{ {
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "es5",
"arrowParens": "avoid" "arrowParens": "avoid"
} }
import pprint
from finesse.script.spec import KatSpec
spec = KatSpec()
commands = [*spec.commands, *spec.analyses]
commands.sort(key = lambda item: (-len(item), item))
elements = list(spec.elements)
elements.sort(key = lambda item: (-len(item), item))
functions = list(spec.expression_functions)
functions.sort(key = lambda item: (-len(item), item))
keywords = list(spec.reserved_names)
keywords.sort(key = lambda item: (-len(item), item))
operators = list({*spec.binary_operators, *spec.unary_operators})
# "&" isn't really treated like an operator in KatScript, but highlight it as one
operators.append("&")
# The only multi-character operators are repetitions of the same character, so we remove them to
# allow square brackets in regex to work.
for x in [op for op in operators if len(op) > 1]:
operators.remove(x)
# "-" has to appear at the end of the list, as all these operators will go into square brackets in
# a regex, and "-" in the middle of square brackets implies a range.
operators.remove("-")
operators.append("-")
with open("src/codemirror-finesse3-types.ts", "w") as file:
file.write(f"export const commands = ")
pprint.pprint(commands, stream=file, indent=2)
file.write(f"export const elements = ")
pprint.pprint(elements, stream=file, indent=2)
file.write(f"export const functions = ")
pprint.pprint(functions, stream=file, indent=2)
file.write(f"export const keywords = ")
pprint.pprint(keywords, stream=file, indent=2)
file.write(f"export const operators = ")
pprint.pprint(operators, stream=file, indent=2)
import { ICodeMirror } from '@jupyterlab/codemirror'; import { ICodeMirror } from '@jupyterlab/codemirror';
import { defineMultiplexingMode } from './codemirror-my-multiplex'; import { defineMultiplexingMode } from './codemirror-my-multiplex';
import { defineFinesse2Mode } from './codemirror-finesse2'; import { defineFinesse2Mode } from './codemirror-finesse2';
import { defineFinesse3Mode } from './codemirror-finesse3';
export function setupFinesseCodeMirror(codemirror: ICodeMirror): void { export function setupFinesseCodeMirror(codemirror: ICodeMirror): void {
const cm = codemirror.CodeMirror; const cm = codemirror.CodeMirror;
defineMultiplexingMode(codemirror); defineMultiplexingMode(codemirror);
defineFinesse2Mode(codemirror); defineFinesse2Mode(codemirror);
defineFinesse3Mode(codemirror);
cm.defineMode('finesse-python', (config: any) => { cm.defineMode('finesse-python', (config: any) => {
const pmode = cm.getMode(config, 'python'); const pmode = cm.getMode(config, 'python');
return cm.myMultiplexingMode(pmode, { return cm.myMultiplexingMode(
open: /(?=#kat2code)/, pmode,
{
open: /(?=#kat2)/,
close: /(?=""")/, // Match string end without consuming it close: /(?=""")/, // Match string end without consuming it
mode: cm.getMode(config, 'text/x-finesse2'), mode: cm.getMode(config, 'text/x-finesse2'),
delimStyle: 'delim' delimStyle: 'delim',
}); },
{
open: /(?=#kat)/,
close: /(?=""")/, // Match string end without consuming it
mode: cm.getMode(config, 'text/x-finesse3'),
delimStyle: 'delim',
}
);
}); });
cm.defineMIME('text/x-finesse-python', 'finesse-python'); cm.defineMIME('text/x-finesse-python', 'finesse-python');
...@@ -23,6 +34,6 @@ export function setupFinesseCodeMirror(codemirror: ICodeMirror): void { ...@@ -23,6 +34,6 @@ export function setupFinesseCodeMirror(codemirror: ICodeMirror): void {
name: 'Finesse Python', name: 'Finesse Python',
mime: 'text/x-finesse-python', mime: 'text/x-finesse-python',
mode: 'finesse-python', mode: 'finesse-python',
ext: [] ext: [],
}); });
} }
export const commands = [
'opt_rf_readout_phase',
'propagate_beam_astig',
'frequency_response',
'sensing_matrix_dc',
'noise_projection',
'print_model_attr',
'noise_analysis',
'propagate_beam',
'print_model',
'beam_trace',
'run_locks',
'freqresp',
'parallel',
'noxaxis',
'change',
'intrix',
'lambda',
'series',
'x2axis',
'x3axis',
'debug',
'modes',
'print',
'sweep',
'xaxis',
'abcd',
'fsig',
'link',
'plot',
'tem',
];
export const elements = [
'quantum_shot_noise_detector_demod_1',
'quantum_shot_noise_detector_demod_2',
'quantum_noise_detector_demod_1',
'quantum_noise_detector_demod_2',
'quantum_shot_noise_detector',
'directional_beam_splitter',
'beam_property_detector',
'power_detector_demod_1',
'power_detector_demod_2',
'quantum_noise_detector',
'amplitude_detector',
'degree_of_freedom',
'power_detector_dc',
'optical_bandpass',
'signal_generator',
'motion_detector',
'readout_dc_qpd',
'beam_splitter',
'filter_butter',
'filter_cheby1',
'zpk_actuator',
'ligo_triple',
'filter_zpk',
'readout_dc',
'readout_rf',
'amplifier',
'free_mass',
'ligo_quad',
'modulator',
'actuator',
'isolator',
'pendulum',
'qnoised1',
'qnoised2',
'squeezer',
'variable',
'ccdline',
'nothing',
'qnoised',
'splitpd',
'astigd',
'butter',
'cavity',
'cheby1',
'mirror',
'qshot1',
'qshot2',
'ccdpx',
'fline',
'gauss',
'laser',
'noise',
'qshot',
'space',
'fcam',
'gouy',
'isol',
'knmd',
'lens',
'lock',
'sgen',
'amp',
'cav',
'ccd',
'dbs',
'dof',
'fpx',
'mmd',
'mod',
'obp',
'pd1',
'pd2',
'var',
'zpk',
'ad',
'bp',
'bs',
'cp',
'pd',
'sq',
'xd',
'l',
'm',
's',
];
export const functions = [
'geomspace',
'linspace',
'logspace',
'arctan2',
'deg2rad',
'degrees',
'rad2deg',
'radians',
'arccos',
'arcsin',
'conj',
'imag',
'real',
'sqrt',
'abs',
'cos',
'exp',
'neg',
'pos',
'sin',
'tan',
];
export const keywords = [
'resolution',
'stability',
'bandpass',
'bandstop',
'highpass',
'finesse',
'lowpass',
'modesep',
'length',
'xsplit',
'ysplit',
'abcd',
'even',
'fwhm',
'gouy',
'loss',
'none',
'pole',
'div',
'fsr',
'lin',
'log',
'odd',
'off',
'tau',
'am',
'c0',
'pi',
'pm',
'rc',
'w0',
'zr',
'g',
'l',
'q',
's',
'w',
'x',
'y',
'z',
];
export const operators = ['/', '*', '+', '&', '-'];
import { ICodeMirror } from '@jupyterlab/codemirror';
import {
commands,
elements,
functions,
keywords,
operators,
} from './codemirror-finesse3-types';
const booleans = ['True', 'true', 'False', 'false'];
function wordListRegexp(words: string[]): RegExp {
return new RegExp('((' + words.join(')|(') + '))');
}
function symbolListRegexp(words: string[]): RegExp {
return new RegExp('[' + words.join('') + ']');
}
function fullString(re: RegExp): RegExp {
return new RegExp('^' + re.source + '$');
}
const regex = {
command: wordListRegexp(commands),
fullcommand: fullString(wordListRegexp(commands)),
element: wordListRegexp(elements),
fullelement: fullString(wordListRegexp(elements)),
function: wordListRegexp(functions),
fullfunction: fullString(wordListRegexp(functions)),
keyword: wordListRegexp(keywords),
fullkeyword: fullString(wordListRegexp(keywords)),
operator: symbolListRegexp(operators),
name: /[a-zA-Z_][a-zA-Z0-9._-]*/,
number: /(([+-]?inf)|([+-]?(\d+\.\d*|\d*\.\d+|\d+)([eE]-?\d*\.?\d*)?j?([pnumkMGT])?))/,
boolean: wordListRegexp(booleans),
fullboolean: fullString(wordListRegexp(booleans)),
bracket: /[()]/,
};
/*
* Available tokens:
*
* keyword
* atom
* number
* def
* variable
* variable-2
* variable-3
* property
* operator
* comment
* string
* string-2
* meta
* qualifier
* builtin
* bracket
* tag
* attribute
* header
* quote
* hr
* link
*/
export function defineFinesse3Mode(codemirror: ICodeMirror): void {
const cm = codemirror.CodeMirror;
cm.defineMode('finesse3', () => {
function tokenBase(stream: CodeMirror.StringStream, state: any) {
// Give each line a slight background, to differentiate from Python code.
return 'line-background-finesse ' + tokenLex(stream, state);
}
function tokenLex(stream: CodeMirror.StringStream, state: any) {
if (stream.sol()) {
state.firstOnLine = true;
}
// Skip to the next non-space character
if (stream.eatSpace()) {
return;
}
// If it's a comment, skip the rest of the line
if (/#/.test(stream.peek())) {
stream.skipToEnd();
return 'comment';
}
let style = null;
if (state.firstOnLine) {
// Only check for component definitions and commands at the start of
// the line.
if (stream.match(regex['element']) || stream.match(regex['command'])) {
// Some elements can be substrings of commands, and vice versa e.g.
// "l" (element) and "link" (command). To choose the correct one, we
// first continue grabbing the rest of the current word (if there is
// any more), then test the entire word against element/command
// names.
stream.eatWhile(/\w/);
const cur = stream.current();
if (regex['fullelement'].test(cur)) {
// Component definition
style = 'variable-3';
state.nextIsDef = true;
} else if (regex['fullcommand'].test(cur)) {
// Command
style = 'builtin';
}
}
} else if (state.nextIsDef && stream.match(regex['name'])) {
// Component name
style = 'def';
state.nextIsDef = false;
} else if (
stream.match(regex['keyword']) ||
stream.match(regex['boolean']) ||
stream.match(regex['function'])
) {
// Same reasoning as before, grab the rest of the word then check the
// whole thing.
stream.eatWhile(/\w/);
const cur = stream.current();
if (regex['fullkeyword'].test(cur)) {
style = 'keyword';
} else if (regex['fullboolean'].test(cur)) {
style = 'keyword';
} else if (regex['fullfunction'].test(cur)) {
style = 'builtin';
} else {
// We accidentally grabbed a variable.
style = 'variable';
}
} else if (stream.match(regex['number'])) {
style = 'number';
} else if (stream.match(regex['operator'])) {
style = 'operator';
} else if (stream.match(regex['bracket'])) {
style = 'bracket';
} else if (stream.match(regex['name'])) {
style = 'variable';
}
if (style !== null) {
state.firstOnLine = false;
return style;
}
stream.next();
}
return {
startState: function () {
return {
tokenize: tokenBase,
firstOnLine: true,
nextIsDef: false,
};
},
blankLine: function (state: any) {
return 'line-background-finesse';
},
token: function (stream: CodeMirror.StringStream, state: any) {
return state.tokenize(stream, state);
},
};
});
cm.defineMIME('text/x-finesse3', 'finesse3');
}
import { import {
JupyterFrontEnd, JupyterFrontEnd,
JupyterFrontEndPlugin JupyterFrontEndPlugin,
} from '@jupyterlab/application'; } from '@jupyterlab/application';
import { import {
NotebookPanel, NotebookPanel,
INotebookTracker, INotebookTracker,
Notebook Notebook,
} from '@jupyterlab/notebook'; } from '@jupyterlab/notebook';
import { ICodeMirror } from '@jupyterlab/codemirror'; import { ICodeMirror } from '@jupyterlab/codemirror';
...@@ -19,7 +19,7 @@ const plugin: JupyterFrontEndPlugin<void> = { ...@@ -19,7 +19,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
id: 'finesse-syntax-jupyter', id: 'finesse-syntax-jupyter',
autoStart: true, autoStart: true,
requires: [INotebookTracker, ICodeMirror], requires: [INotebookTracker, ICodeMirror],
activate activate,
}; };
function activate( function activate(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment