Skip to main content
Glama
tokenize.test.ts7.76 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { tokenize } from './tokenize'; describe('Tokenizer', () => { test('Single line comment', () => { expect(tokenize('2 + 2 // This is a single-line comment')).toMatchObject([ { id: 'Number', value: '2' }, { id: '+', value: '+' }, { id: 'Number', value: '2' }, { id: 'Comment', value: '// This is a single-line comment' }, ]); }); test('Multi line comment', () => { expect( tokenize(` /* * Comment before */ 2 + 2 /* * Comment after */ `) ).toMatchObject([ { id: 'Comment' }, { id: 'Number', value: '2' }, { id: '+', value: '+' }, { id: 'Number', value: '2' }, { id: 'Comment' }, ]); }); test('Simple string matching', () => { const matches = tokenize(' ( + ) / ( * '); expect(matches).toMatchObject([ { id: '(', value: '(' }, { id: '+', value: '+' }, { id: ')', value: ')' }, { id: '/', value: '/' }, { id: '(', value: '(' }, { id: '*', value: '*' }, ]); }); test('Quantity matching', () => { expect(tokenize("1 'mg'")).toMatchObject([{ id: 'Quantity', value: "1 'mg'" }]); }); test('Number matching', () => { expect(tokenize('1')).toMatchObject([{ id: 'Number', value: '1' }]); expect(tokenize('1 ')).toMatchObject([{ id: 'Number', value: '1' }]); expect(tokenize('1.0')).toMatchObject([{ id: 'Number', value: '1.0' }]); expect(tokenize('-1')).toMatchObject([ { id: '-', value: '-' }, { id: 'Number', value: '1' }, ]); expect(tokenize('-1.0')).toMatchObject([ { id: '-', value: '-' }, { id: 'Number', value: '1.0' }, ]); }); test('DateTime matching', () => { expect(tokenize('@2012-04-15T15:00:00+02:00')).toMatchObject([ { id: 'DateTime', value: '2012-04-15T15:00:00+02:00' }, ]); }); test('Regular expression matching', () => { const matches = tokenize('bc + dsf - // the comment + - dasdas'); expect(matches).toMatchObject([ { id: 'Symbol', value: 'bc' }, { id: '+', value: '+' }, { id: 'Symbol', value: 'dsf' }, { id: '-', value: '-' }, { id: 'Comment', value: '// the comment + - dasdas' }, ]); }); test('Empty string', () => { expect(tokenize('')).toStrictEqual([]); }); describe('String escapes', () => { test('String escape sequence', () => { expect(tokenize("'\\\\\\/\\f\\r\\n\\t\\\"\\`\\'\\u002a'")).toMatchObject([ { id: 'String', value: '\\/\f\r\n\t"`\'*', }, ]); }); test.each([ // See https://build.fhir.org/ig/HL7/FHIRPath/#string ["'\\''", "'"], // \' Single-quote [`'\\"'`, '"'], // \" Double-quote ["'\\`'", '`'], // \` Backtick ["'\\r'", '\r'], // \r Carriage Return ["'\\n'", '\n'], // \n Line Feed ["'\\t'", '\t'], // \t Tab ["'\\f'", '\f'], // \f Form Feed ["'\\\\'", '\\'], // \\ Backslash ["'\\u002a'", '\u002a'], // \uXXXX Unicode character, where XXXX is the hexadecimal representation of the character ])('String escapes 2', (fhirPathString, jsString) => { expect(tokenize(fhirPathString)).toMatchObject([{ id: 'String', value: jsString }]); }); test.each([ // If a \ is used at the beginning of a non-escape sequence, it will be ignored and will not appear in the sequence. ["'\\p'", 'p'], ["'\\\\p'", '\\p'], ["'\\3'", '3'], ["'\\u005'", 'u005'], ["'\\ '", ' '], ])('String escapes 3', (fhirPathString, jsString) => { expect(tokenize(fhirPathString)).toMatchObject([{ id: 'String', value: jsString }]); }); test('String escapes 4', () => { expect(tokenize("'\\'wrapped in single quotes\\''")).toMatchObject([ { id: 'String', value: "'wrapped in single quotes'", }, ]); }); test('String escapes 5', () => { expect(tokenize(`'\\"wrapped in double quotes\\"'`)).toMatchObject([ { id: 'String', value: '"wrapped in double quotes"', }, ]); }); }); describe('Literal unicode', () => { // See spec: https://build.fhir.org/ig/HL7/FHIRPath/#string test('Literal unicode 1', () => { // In FHIR Path land: 'Peter' -> Peter // Reason: Trivial expect(tokenize("'P\u0065ter'")).toMatchObject([{ id: 'String', value: 'Peter' }]); }); test('Literal unicode 2', () => { // In FHIR Path land: 'P\u0065ter' -> Peter // Reason: FHIR Path Unicode characters expect(tokenize("'P\\u0065ter'")).toMatchObject([{ id: 'String', value: 'Peter' }]); }); test('Literal unicode 3', () => { // In FHIR Path land: 'P\eter' -> Peter // Reason: If a \ is used at the beginning of a non-escape sequence, it will be ignored and will not appear in the sequence. expect(tokenize("'P\\\u0065ter'")).toMatchObject([{ id: 'String', value: 'Peter' }]); }); test('Literal unicode 4', () => { // In FHIR Path land: 'P\\u0065ter' -> P\u0065ter // Reason: Unescape backslash expect(tokenize("'P\\\\u0065ter'")).toMatchObject([{ id: 'String', value: 'P\\u0065ter' }]); }); }); test('FHIR Path', () => { const matches = tokenize('Patient.name.given'); expect(matches).toMatchObject([ { id: 'Symbol', value: 'Patient' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'name' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'given' }, ]); }); test('FHIR Path union', () => { const matches = tokenize('Practitioner.name.given | Patient.name.given'); expect(matches).toMatchObject([ { id: 'Symbol', value: 'Practitioner' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'name' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'given' }, { id: '|', value: '|' }, { id: 'Symbol', value: 'Patient' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'name' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'given' }, ]); }); test('FHIR Path function', () => { const matches = tokenize("Patient.telecom.where(system='email')"); expect(matches).toMatchObject([ { id: 'Symbol', value: 'Patient' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'telecom' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'where' }, { id: '(', value: '(' }, { id: 'Symbol', value: 'system' }, { id: '=', value: '=' }, { id: 'String', value: 'email' }, { id: ')', value: ')' }, ]); }); test('FHIR Path index[number]', () => { const matches = tokenize('Patient.name[0]'); expect(matches).toMatchObject([ { id: 'Symbol', value: 'Patient' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'name' }, { id: '[', value: '[' }, { id: 'Number', value: '0' }, { id: ']', value: ']' }, ]); }); test('FHIR Path index[string]', () => { const matches = tokenize('Patient.name[zebra]'); expect(matches).toMatchObject([ { id: 'Symbol', value: 'Patient' }, { id: '.', value: '.' }, { id: 'Symbol', value: 'name' }, { id: '[', value: '[' }, { id: 'Symbol', value: 'zebra' }, { id: ']', value: ']' }, ]); }); test('Quantity units', () => { expect(tokenize("1.0 'mg'")).toMatchObject([{ id: 'Quantity', value: "1.0 'mg'" }]); expect(tokenize('7 days = 1 week')).toMatchObject([ { id: 'Quantity', value: '7 days' }, { id: '=', value: '=' }, { id: 'Quantity', value: '1 week' }, ]); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server