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;
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Dec 16, 2017 at 12:56
\$\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;
}
answered Dec 16, 2017 at 14:07
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.