Lurch core classes

source

symbol.js


import { MathConcept } from './math-concept.js'
import { Expression } from './expression.js'

/**
 * A Symbol is an atomic {@link Expression}.  This includes all types of
 * mathematical symbols, from variables like x and y to constants like 1, 2,
 * and pi, as well as symbols for operators like `+` or the capital sigma for
 * summation.
 * 
 * The primary datum stored in a Symbol is a single text string, which defines
 * the symbol.  For more information on this datum, see the constructor.
 * 
 * Note:  It is unfortunate that we have a class named Symbol in our
 * ontology while JavaScript also has a class named Symbol.  We therefore
 * always import this class using code like the following, to ensure no
 * conflicts in the global scope would prevent us from accessing the original
 * JavaScript Symbol functionality, which we occasionally need.
 * 
 * ```js
 * import { Symbol as LurchSymbol } from './symbol.js'
 * ```
 */
export class Symbol extends Expression {
    
    static className = MathConcept.addSubclass( 'Symbol', Symbol )

    /**
     * When constructing a symbol, we must provide the text that defines it.
     * This will typically be the Unicode text for the symbol itself, such as
     * `"x"` or `"1"`, or the Unicode character for the Greek letter pi.  But
     * there is no formal requirement that the text stored in the symbol be
     * exactly the text that would be used to represent the symbol in a
     * typical mathematical document.  There is not even a requirement that
     * the text have any particular form, except that it be a nonempty string.
     * 
     * @param {String} text - any nonempty string to be used as the text for
     *   this symbol.  If this is not a string, it will be converted into
     *   one with the `String` constructor in JavaScript.  If that returns an
     *   empty string, it will be treated as `"undefined"` instead.
     */
    constructor ( text ) {
        super()
        text = String( text )
        this.setAttribute( 'symbol text', text === '' ? 'undefined' : text )
    }

    /**
     * Symbols are supposed to be the atomic type of {@link Expression}.  Thus
     * we override here the default behavior of the
     * {@link MathConcept#insertChild insertChild()} member of the
     * {@link MathConcept} class, making it now do nothing.  Since all other
     * child insertion functions (such as
     * {@link MathConcept#pushChild pushChild()}, etc.) rely internally on
     * {@link MathConcept#insertChild insertChild()}, this effectively makes
     * it impossible to add children to a Symbol instance.
     */
    insertChild () { }

    /**
     * A Symbol never changes its text.  To have a new Symbol, just construct
     * a new one with the new text, rather than trying to re-use an old one
     * and change its text.  Consequently, this function returns the text
     * given at the time the Symbol was constructed.
     * 
     * @returns {String} the text given at construction time
     */
    text () { return this.getAttribute( 'symbol text' ) }

    /**
     * This method overrides {@link MathConcept#toString the implementation in
     * the MathConcept class}, which creates LISP-style S-expressions.  Here we
     * handle the atomic case by writing the name of the symbol instead.
     * 
     * @returns {String} the string representation of this Symbol, which is just
     *   its {@link Symbol#text text()}
     */
    toString () { return this.text() }

    /**
     * The original `value()` function was {@link Expression#value implemented
     * in the Expression class}, but as a pure virtual method, meaning that it
     * defers its implementation to subclasses.  Here, we add support for the
     * following conventions:
     * 
     *  * A Symbol with the `"evaluate as"` attribute set to `"integer"` will
     *    have a `value()` equal to the result of parsing the Symbol's
     *    {@link Symbol#text text()} content as an integer, using the standard
     *    JavaScript `parseInt()` function.  This includes ignoring nonsense
     *    at the end of the string, returning the inital number only.  Nan
     *    will be returned if the string does not even begin with an integer.
     *  * A Symbol with the `"evaluate as"` attribute set to `"real"` will
     *    have a `value()` equal to the result of parsing the Symbol's
     *    {@link Symbol#text text()} content as a real number, using the
     *    standard JavaScript `parseFloat()` function.  Note that this
     *    supports not only standard decimal notation, but also the text
     *    `"Infinity"` and scientific notation of the form `1.2e3` or
     *    `1.2E3`.  Spaces are permitted and nonsense at the end of the string
     *    is ignored.  NaN will be returned if the string does not even begin
     *    with a float.
     *  * A Symbol with the `"evaluate as"` attribute set to `"string"` will
     *    have a `value()` equal to its {@link Symbol#text text()}.
     * 
     * @returns {*} The value of the Symbol, as documented above, or undefined
     *   if none of the above cases applies
     */
    value () {
        switch ( this.getAttribute( 'evaluate as' ) ) {
            case 'integer' : return parseInt( this.text() )
            case 'real' : return parseFloat( this.text() )
            case 'string' : return this.text()
        }
    }
    
}