Lurch Deductive Engine

Class

SourceMap

A source map is an object that tracks a specific type of correspondence between two strings, reminiscent of the source maps used when compiling or minifying JavaScript code for use in web browsers. The first string, called the source text, is the original text (which can be computer code or any other text), and remains unchanged throughout the life of the source map object. The second string, called the modified text begins life equal to the source text, but can be changed thereafter with modifications/replacements.

The purpose of the source map is to keep track of which sequences of characters in the source text correspond to which sequences of characters in the modified text, so that any position in one string can be converted to the corresponding position in the other string.

In a simple example, let's say we have the source text "I wonder what 5+9 equals." Then we run on that text an algorithm that finds all occurrences of simple arithmetic expressions and computes them, replacing the original expression with its result, in this case producing "I wonder what 14 equals." The source map would represent the following facts:

  • Characters 0 through 13 in the source text correspond to characters 0 through 13 in the modified text. (They are "I wonder what " in both cases.)
  • Characters 14 through 16 in the source text ("5+9") correspond to characters 14 through 15 in the modified text ("14").
  • Characters 17 through 24 in the source text correspond to characters 16 through 23 in the modified text. (They are " equals." in both cases.)

In general, a source map will always be an array of such correspondences, each of which is a quadruple of integer indices, two into the source text and two into the modified text. Such data would be stored internally as that array of quadruples,

[
    [  0, 13,  0, 13 ],
    [ 14, 16, 14, 15 ],
    [ 17, 24, 16, 23 ]
]

Quadruples at even indices correspond to unmodified sequences of characters while quadruples at odd indices correspond to modified sequences of characters. In the list above, entries 0 and 2 are for unmodified text and entry 1 is the only modified text.

To use this class, the user creates an instance, passing the original (unmodified) source text. Then the user can do modifications (that is, replacements) on the source text by calling modify(), taking care to make such calls in increasing order of index into the modified text. (See documentation for modify(), below.) At any point, functions such as sourcePosition(), modifiedPosition(), sourceLineAndColumn(), and modifiedLineAndColumn() are available to perform lookup in either direction (source to modified, or the reverse).

Constructor

new SourceMap(sourceText)

Construct a new source map with the given source text. The modified text will be initialized to the same text, and the initial mapping will map all characters in the source text to their corresponding position in the modified text, and vice versa.

Parameters

  • sourceText *

    the string to be used as the source text of this source map (although any object can be passed, and will be converted to a string)

Source

Classes

SourceMap

Methods

static

debugCode(code)

A debugging routine useful when working with code in which you care about the specific character indices and/or line and column numbers. Call this routine to print the code to the console, with a line and character number added at the start of each line, using the format L_C_: with the blanks replaced by line and character numbers, respectively.

For example, "function ( x ) {\n\treturn 3;\n}" would be printed as follows. Note that these are not line and column numbers, but rather line numbers and character indices.

L1C0: function ( x ) {
L2C17:        return 3;
L3C27: }

Parameters

  • code string

    arbitrary text to be displayed on the console (typically some kind of code, such as putdown notation or JSON)

Source

static

isMarker(text) → {boolean}

See the documentation for modify() and nextMarker() for an explanation of how markers can be useful. This function is a predicate for testing whether an arbitrary string contains a marker.

Parameters

  • text string

    the text to test whether it is a marker

Returns

  • boolean

    whether the given text is a marker

Source

dataForMarker(marker) → {Object}

See the documentation for modify() for an explanation of how markers work. That explanation mentions that arbitrary data can be associated with a marker as part of a call to the modify() function. To later query the data associated with a given marker, use this function.

Parameters

  • marker string

    the text for the marker whose data should be looked up

Returns

  • Object

    the data associated with the marker, as a JavaScript object amenable to JSON encoding

Source

modified() → {string}

Get the modified text for this source map. At construction time, this will be the same as the source text, but can change via calls to modify().

Returns

  • string

    the modified text for this source map

Source

modifiedLineAndColumn(line, column) → {Array}

See the documentation for modifiedPosition(); this function behaves similarly, except that it takes as input a line number and column number in the source text, and returns a line number and column number in the modified text.

Unlike character indices, line and column numbers start counting at 1, so the first character in the text (at index 0) would be in line 1 and at column 1.

Parameters

  • line integer

    the line number in the source text whose position in the modified text should be looked up

  • column integer

    the column number in the modified text whose position in the source should be looked up

Returns

  • Array

    a pair of integers, the line and column number in the modified text that correspond to the given line and column number in the source text

Source

modifiedPosition(sourcePosition) → {integer}

See the documentation for sourcePosition(); this function is (approximately) the inverse to that function. The only difference is, as documented in that function, we cannot map precisely each character within a section of modified text, and thus we map to the first charcter in the corresponding range. For example, in the source map mentioned at the top of this page, each character in the source text "5+9" would map to the first character in the modified text "14."

Parameters

  • sourcePosition integer

    the position in the source text for which a lookup should be done in the modified text

Returns

  • integer

    the position in the modified text corresponding to the given position in the source text

Source

modify(start, length, replacement, Objectopt)

Source map objects contain both a source text and a modified text, which are equal at construction time. But through one or more calls to this function, the modified text can change, diverging from the source text. This function not only makes such modifications, but it records where they were made, and any data associated with them, so that queries can be made later about correspondences between the source and modified text, as documented at the top of this file.

The first three parameters are straightforward, as documented below. However, the fourth parameter (which is optional) requires additional explanation. When replacing a portion of the source text, it is sometimes convenient to replace it not with text, but with arbitrary data. We support this more advanced usage as follows.

  • First, the newly inserted text cannot be arbitrary text, but should instead be a special marker that indicates the location where non-text data resides. To create such markers, use the nextMarker() function.
  • Second, the fourth parameter (data) should be provided, and it can be any JavaScript object amenable to JSON encoding, which will be stored in this object for later querying.
  • Later, when the client processes the modified text, upon encountering a marker (see isMarker()) and desiring to know what data it represents, the client can find out with a call to dataForMarker().

For example, let's say our source text was "Get me employee #16" and we wanted to replace "employee #16" with the full data for that employee, which is not text data, but rather something more complex, such as { name:'Henrietta', age:35, ... }. Assuming we have that data in an object obj, we could make a call like the following.

sourcemap.modify( 7, 12, sourcemap.nextMarker(), obj )

Notes:

  1. When passing data as the optional fourth parameter, you must pass sourcemap.nextMarker() as the third parameter, because data is stored internally only associated with markers, and cannot be retrieved later otherwise.
  2. The starting index documented below is in the modified text, not the source text, but if this is inconvenient for you, simply make use of the modifiedPosition() function to convert what you have to what you need.
  3. Modifications must be made in increasing order of index in the modified text. For example, if you have a replacement to do on characters 5 through 10 and another to do on characters 50 through 70, you must do them in that order, not the reverse. This class does not support modifications in arbitrary order.

Parameters

  • start integer

    the starting index, in the modified text, where this next modification should be recorded

  • length integer

    the length of the text to be replaced, starting at the index start

  • replacement string

    the text with which to replace the chosen portion of the source text

  • Object <optional>

    data arbitrary JSON data to store in the modified text, as documented above

Source

nextMarker() → {string}

See the documentation for modify() for an explanation of how markers work. That explanation mentions that it is useful to have a sequence of markers, each of which is a unique snippet of text that does not appear anywhere in the original source text. This function can be used as a generator, returning a different string each time it is called, none of which appeared in the original source text. In that way it can be used to generate markers that can be passed as the replacement parameter to modfiy().

Returns

  • string

    a new marker

Source

nextModificationPosition() → {integer}

As discussed in the documentation for modify(), modifications must be made in increasing order of position in the modified text. Thus at any point, we have a minimum index at which the next modification could take place, so that it falls after all modifications made so far. This function returns that minimum index.

For example, in the source map described at the top of this file (in which "5+9" became "14" inside the source text), the next viable index in the modified text where a modification could be made is immediately after the text "14," that is, at index 16.

If no modifications have been made to the source text, this function will return zero.

Returns

  • integer

    the earliest index at which a modification could be added to this source map

Source

source() → {string}

Get the source text given at the time this source map was constructed. This class provides no API for altering the source text, so it should be the same any time this function is called.

Returns

  • string

    the source text given at construction time

Source

sourceLineAndColumn(line, column) → {Array}

See the documentation for sourcePosition(); this function behaves similarly, except that it takes as input a line number and column number in the modified text, and returns a line number and column number in the source text.

Unlike character indices, line and column numbers start counting at 1, so the first character in the text (at index 0) would be in line 1 and at column 1.

Parameters

  • line integer

    the line number in the modified text whose position in the source text should be looked up

  • column integer

    the column number in the modified text whose position in the source text should be looked up

Returns

  • Array

    a pair of integers, the line and column number in the source text that correspond to the given line and column number in the modified text

Source

sourcePosition(modifiedPosition) → {integer}

The major purpose of the SourceMap class is to track the correspondence of character positions between its source text and its modified text. This function can be used to do a lookup from the latter to the former; you provide a character position in the modified text and it returns the corresponding position in the source text.

If the character position sits inside a section of modified text, there may not be an exact correspondence of which character in the source text relates to it; for example, in the source map mentioned at the top of this documentation page, which characters in "5+9" correspond to each character in the text "14?" In such cases, the first character in the corresponding section is returned. So both characters in "14" would be said to correspond to the first character in "5+9."

If the character position sits inside a section of unmodified text, then there is an exact copy of that unmodified text in the source text, and thus the corresponding position with it will be returned.

If an invalid index is provided, undefined is returned. Indices into both the source and modified texts are zero-based, so the first valid index is 0 and the last is the length of the text minus 1.

Parameters

  • modifiedPosition integer

    the position in the modified text for which a lookup should be done in the source text

Returns

  • integer

    the position in the source text corresponding to the given position in the modified text

Source