2
\$\begingroup\$

I'm in the process of learning to write idiomatic JavaScript code so naturally I'm parsing command line arguments.

My approach to this is to have a simple state machine. The parser should work as follows:

To get tags pass to the application, you would do the following:

application tag tag1 tag2

The parser raises an event when the requirements are met. To register an event for a particular token, you would:

parser.registerAction({
        tag: function(token, result) {
          // do stuff with tag arguments
        }
});

You can see the usage in some tests here.

To get names, you dont need to specify the token. You just write out the names:

application name1 name2

You can register to get the names values the same way as tags.

This is kinda based on the generic CommandParser. I just wanted to write up something myself.

I'm mostly looking for feedback about writing idiomatic JS code. My background is mostly C# and some C++ and I'm trying to change the way I think about code.

The code actually lives here.

class Parser {
    constructor(args) {

        /**
         * arguments being parsed
         **/
        this._args = args;

        /**
         * stores the registered actions callbacks
         **/
        this._actions = {};

        /**
         * stores the values associated with the tokens
         **/
        this._actionValues = {};

        /**
         * current state of the state machine
         **/
        this._currentState = this.endCommandState;

        /**
         * current token being parsed
         **/
        this._currentValue = '';
    }

    get listToken() {
        return 'list';
    }

    get tagToken() {
        return 'tag';
    }

    get nameToken() {
        return 'names';
    }

    _isReservedToken(token) {
        let reservedTokens = [
            this.tagToken,
            this.listToken
        ];

        return reservedTokens.indexOf(token) > -1;
    }

    _getCommandFromToken(token) {
        if (token === this.listToken) {
            return this.listCommandState;
        }

        if (token === this.tagToken) {
            return this.tagCommandState;
        }

        // name requires no token
        return this.namesCommandState;
    }

    _raiseRegisteredAction(token) {
        if (this._actions[token]) {
            this._actions[token](token, this._actionValues);
        }
        this._actionValues = [];
    }

    parse() {
        if (this._args.length == 0) {
            console.log('nothing to parse');
            return;
        }

        if (!this._getNextValue()) {
            this._currentState = this.endCommandState;
            return;
        }

        this._currentState = this._getCommandFromToken(this._currentValue);
        // if (this._isReservedToken(this._currentValue)) {

        while (this._currentState !== this.endCommandState) {
            this._currentState();
        }

        // the final state
        this._currentState();
    }

    endCommandState() {
        console.log('parsing ended');
    }

    namesCommandState() {
        const token = this.nameToken;
        this._actionValues[token] = this._actionValues[token] || [];
        this._actionValues[token].push(this._currentValue.toLowerCase());

        this._currentState = this._getNextValue() ?
            this.namesCommandState : this.endCommandState;

        if (this._currentState === this.endCommandState) {
            this._raiseRegisteredAction(token);
        }       
    }

    listCommandState() {
        const token = this.listToken;
        this._actionValues[token] = [];
        this._raiseRegisteredAction(token);
        this._currentState = this.endCommandState;
    }

    tagCommandState() {
        const token = this.tagToken;
        this._actionValues[token] = [];
        if (this._getNextValue()) {
            this._currentState = this.tagEventIdCommandState;
        }
        else {
            this._currentState = this.endCommandState;
        }
    }

    tagEventIdCommandState() {
        const token = this.tagToken;
        this._actionValues[token].push({
            eventid: this._currentValue
        });
        if (this._getNextValue()) {
            this._currentState = this.tagValueCommandState;
        }
        else {
            this._raiseRegisteredAction(token);
            this._currentState = this.endCommandState;
        }
    }

    tagValueCommandState() {
        const token = this.tagToken;
        this._actionValues[token].push({
            tagValue: this._currentValue
        });
        if (this._getNextValue()) {
            this._currentState = this.tagValueCommandState;
        }
        else {
            this._raiseRegisteredAction(token);
            this._currentState = this.endCommandState;
        }
    }

    _getNextValue() {
        let currentValue = this._args.shift();
        if (currentValue) {
            this._currentValue = currentValue;
            return true;
        }
        return false;
    }

    registerAction(action) {
        Object.assign(this._actions, action);
    }
}

module.exports = Parser;
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I would probably use an if/else statement here

_getCommandFromToken(token) {
    if (token === this.listToken) {
        return this.listCommandState;
    }

    if (token === this.tagToken) {
        return this.tagCommandState;
    }

    // name requires no token
    return this.namesCommandState;
}

like this:

_getCommandFromToken(token) {
    if (token === this.listToken) {
        return this.listCommandState;
    } else if (token === this.tagToken) {
        return this.tagCommandState;
    } else {
        // name requires no token
        return this.namesCommandState;
    }
}

I think that the comment is good here, but I haven't read the rest of the code thoroughly yet.


I also noticed that you are using a bracing style that differs from what most use.

    if (this._getNextValue()) {
        this._currentState = this.tagValueCommandState;
    }
    else {
        this._raiseRegisteredAction(token);
        this._currentState = this.endCommandState;
    }

you should put the else on the same line as the trailing brace of the if statement.

if (this._getNextValue()) {
    this._currentState = this.tagValueCommandState;
} else {
    this._raiseRegisteredAction(token);
    this._currentState = this.endCommandState;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.