I'm trying to implement a simple DSL that parses basic arithmetic expressions. This needs to be done in the browser, so I'm using PEGjs to generate the parser.
Terms in the expression can be numbers (integers or real), variables (variables are properties on a context object passed to the parser), conditionals or properties accessed via dot notation.
I want the conditionals to look like this condition?value, where if condition is true, the term equates to value. The variables on either side of the ? could also be dot notation accessed properties of an object like this object.property1?object.property2.
So if the parser is passed an object like this:
context = {
depth: 100,
material: {
thickness: 20
include: true
}
edge: {
face: 4.5
}
}
The expression:
500 + depth + material.include?edge.face + material.thickness should equate to 624.5.
I've been using the PEGjs online editor. I've tried lots of different approaches, but I can't seem to nail the conditional. Everything else works. Here are the relevant rules:
Variable "variable"
= variable:identifier accessor:("." identifier)* {
var result = context[variable], i
for (i = 0; i < accessor.length; i++) {
result = result[accessor[i][1]]
}
return result
}
identifier
= identifier:$([0-9a-zA-Z_\$]+)
Conditional
= condition:Variable "?" value:Variable {
return condition ? value : 0
}
I've looked at the example grammar for javascript in the PEGjs github repo, and the conditional rule looks a lot like what I've got here, but I still can't get it to work.
What would be the correct way to implement a conditional statement like the one I've described in a PEGjs rule?
I know that this is a bit late, but the issue is that your variable is a string evaluating to "material.include".
Look at this code:
var result = context[variable], i
You are trying to access a property named "material.include" from your context object, which would look like this:
{
"material.include": true
}
Rather than trying to access the object referenced by the "material" property, and then the "include" property off the resulting object, which would look like this:
{
"material": {
"include": true
}
}
The solution would be to split the variable string by "." characters and then recursively find your property:
Variable "variable"
= variable:identifier accessor:("." identifier)* {
var path = variable.split(".");
var result = path.reduce( (nextObject, propName) => nextObject[propName], context );
for (var i = 0; i < accessor.length; i++) {
result = result[accessor[i][1]]
}
return result
}
Note that this solution is not complete, as it will cause an error if you try to access material.include where material is never defined in your context. You may want to add additional error handling, but it does work for the given example.
Related
Given a JavaScript expression as a string, how can I extract the names of variables used inside the expression, and going beyond that, extract the names of any properties accessed on these variables?
These expressions are written by another author using their own personalized names like cabbage and elephant.weight.
The expression could be simple, like: firstName, name.charAt(0), c.radius * Math.PI
But could also be something complex like:
(function(){
var fruit = apple || banana.length;
if (fruit > citrus.lime) {
fruitSalad += "," + citrus.lemon;
// ...
return lemon.split(/[0-9]+/);
}
return fruitBowl.width * fruitBowl['amount'] * Math.SQRT2;
})();
Running the above expressions will throw reference errors unless variables like apple are defined. If I have a way of parsing the expression and extracting the undefined variable names, I can take note of them, and run the expression at a later time without any issues, with values for each variable.
Additionally, if I can take note of which variables have properties and the names of those properties, then I can initialize these also.
fruit = apple = banana = fruitSalad = "";
citrus = { lemon: "0", lime: "1" };
fruitBowl = { width: "10", amount: "30" };
// run the expression
Stuff I've Tried
Simply running the expression and using try/catch to catch each reference error, parsing the exception text for the variable name. This is perilous as exception text is different in each browser. This also doesn't account for parts of the expression which aren't immediately evaluated such as in isTrue? yesText : noText. As accessing undefined properties of objects does not yield an error, these can't be noted.
Parsing the expression into an AST using a library such as Esprima. This is my first time using AST (Abstract Syntax Tree). I think I can access the identifiers used in the code but there doesn't seem to be any clear distinction between variable names and property names. As the output is quite verbose, it would be easy to let something slip. I have looked into AST walkers, but nothing stands out as being a solution.
Note: Assume that eval will not be used. Variables will always be strings or objects whose properties are strings, so knowing the type of variable is not an issue, only the names used. Names of functions used in the expressions can be ignored.
I am looking for information on the difference between acceptable variable names in JavaScript, when they are key names in an object versus when the variables are referenced declaratively.
For example:
in an object, I can do this:
var obj = {
'##A': 1,
'#B': 2,
'C*': 3
};
however, we cannot reference those variables in the code, as this will be some form of syntax error
of course, we cannot do this:
obj.##A++; //syntax error
obj.#B++; //syntax error
obj.C*++; //syntax error
and we definitely cannot do this either:
var ##A = 1; //syntax error
var #B = 2; //syntax error
var C* = 3; //syntax error
I believe the primary difference is that the key of object can take any value of a JS Symbol. Whereas a variable name cannot take any value of Symbol.
So this is a two part question:
How is this disparity described in JavaScript? What was the motivation to have the disparity between Symbol's being valid key names but not valid variable names?
Is there a publicly available regex that can validate a JS variable name? I want to run this regex against keys of a particular object. The reason is that users of a library that I am writing will define variable names with the keys from an object, and they could easily use Symbol characters that would be invalid JS variable names.
To be more specific, this is for a dependency injection facility.
The end-user defines dependencies like so:
const deps = {
'request': function(){
return require('request'); //useless, but as a simple example
},
'users': function(){ //no problems here
return db.users.find({});
},
'users-older-than-13': function(){ //invalid because of "-" chars
return db.users.find({age: {gt: 13}});
}
};
Maybe I'm missing something obvious ... I've just started working with JavaScript, and when looking through an article about unit testing JavaScript with Jasmine, I came across this code:
function Convert(number, fromUnit) {
var conversions = {
distance : {
meters : 1,
cm : 0.01,
feet : 0.3048,
inches : 0.0254,
yards : 0.9144
},
volume : {
litres : 1,
gallons: 3.785411784,
cups : 0.236588236
}
},
betweenUnit = false,
type, unit;
for (type in conversions) {
if (conversions[type]) {
if ( (unit = conversions[type][fromUnit]) ) {
betweenUnit = number * unit * 1000;
}
}
}
return {
to : function (toUnit) {
if (betweenUnit) {
for (type in conversions) {
if (conversions.hasOwnProperty(type)) {
if ( (unit = conversions[type][toUnit]) ) {
return fix(betweenUnit / (unit * 1000));
}
}
}
throw new Error("unrecognized to-unit");
} else {
throw new Error("unrecognized from-unit");
}
function fix (num) {
return parseFloat( num.toFixed(2) );
}
}
};
}
It puzzled me as to why/how it is used, and what's the reason for it. It appears to return an object, which is a labeled function (method really, according to JavaScript naming convention), which wouldn't be called or returned upon creation.
After pondering about this and running it in chrome dev tools, it hit me that being called Convert with a capital C, it might be used as a constructor that would be used with new (again, according to JavaScript naming convention) so I might create an object like:
var tenFeet = new Convert(10, 'feet'); and then use it as tenFeet.to('cm');.
This still makes no sense, since I wouldn't call the object (read: class) Convert, since it's not converting. I'd call the to method convertTo, and probably name Convert to Measurement or something.
Is this simply bad code with bad naming, am I simply rooted too deeply in conventional OO and "formal" languages, or am I missing something basic?
When / where / why would I use something like the above: "return labeled method" from an object in JavaScript?
Couldn't the same be achieved by enhancing the prototype of Convert with the same method?
Cheers, and thanks in advance.
This is following the "read like a sentence" paradigm that some people like:
Convert(10, 'meters').to('feet') === 32.81
// Convert 10 meters to feet
You're right, the function goes against common naming conventions, but you can sort of guess that it shouldn't be created with the new keyword because there are no references to this in the function body.
This problem could've been avoided with proper documentation.
Blender answered this correctly, but just in case other people stumble upon page, here's a little more info on what's happening.
Coming from more "formal" languages, I guess I was having issues with the "label" appearance of the to in the return statement.
from MDN on Label:
Summary
Provides a statement with an identifier that you can refer to using a break or continue statement.
For example, you can use a label to identify a loop, and then use the break or continue statements to indicate whether a program should interrupt the loop or continue its execution.
Syntax
label : statement
Do notice that if you're creating an object, the syntax is similar. For example:
person={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"};
This will result in an object looking like:
object {firstname: "John", lastname: "Doe", age: 50, eyecolor: "blue"}
Another note is that if you're creating an array, you'll just use the commas, like this:
person=["John","Doe",50,"blue"];
This will give an array looking like:
["John", "Doe", 50, "blue"]
It takes a bit of time to get used to JavaScript syntax and logic, but all that really happens in my example is that the function returns an object, that has a method (named to) defined on it.
Once you have that object, you can call the method on it using the usual dot notation, which results in the chaining that is used in the above case. Or reusing Blender's example:
Convert(10, 'meters').to('feet') === 32.81`
I got an 2x10 array and I need set a variable to any member of that array. Make it by hands its not cool, so Im trying to declarate by for operator:
allImages=[
[
'img1-1','img1-2', 'img1-3', 'img1-4', 'img1-5'
],[
'img2-1','img2-2', 'img2-3', 'img2-4', 'img2-5'
]
];
for(i=0;i<1;i++){
console.log(i + ' part ------------------------');
for(j=0;j<5;j++){
x+(i+'-'+j) = allImages[i][j];
console.log((x+(i+'-'+j)) + '-> item');
}
}
But looks like I make a primitive error:
Invalid left-hand side in assignment
Anyway, I cant figure out how to solve this. Can anyone say how to declarate a lot of variables with custom keys throw for operator or with another method?
----- My solution by(https://stackoverflow.com/users/1230836/elias-van-ootegem):
var statImg = {};
var blurImg ={};
for (var i = 0; i < 13; i++) {
var keyName = 'img'+i;
var valOfKey = 'img/'+i+'.png'
statImg[keyName] = valOfKey;
blurImg[keyName] = valOfKey;
};
You'll have to either create an object, and use the left-hand trickery you're trying to generate property names, or you'll have to fall back to the global object (which I hope you don't):
var names = {};//create object
//-> in loop:
names[ x+(i+'-'+j)] = allImages[i][j];
To be complete, but again: don't actually go and do this, you could replace names with window. In which case, you'll be polluting the global scope.
Perhaps you might want to check the values (like x, i and j) for values that make it "difficult" to access the properties, like %, or indeed the dash you're concatenating in your example:
var anObj = {};
anObj['my-property'] = 'this is valid';
console.log(anObj.my-property);//ReferenceError: property is not defined
That is because the dash, or decrement operator isn't seen as part of the property. Eitherway, using separate variables is, in your case, not the best way to go. Programming languages support arrays and objects because of this very reason: grouping related data, making them easy to access through a single variable.
If needs must, just use an object, if not, construct an array you sort using array.sort(function(){});
check MDN on how to acchieve this, if you're stuck down the way, let us know....
i want to create a dynamic generated form using javascript, everything works fine, until i try to pass an array as parameter. When i do this, an error happens. Coulr anyone explain what this is?
Heres my code:
var loadFrm = function(component) {
for(nItem in component) {
var myComponent = "add" + firstToUpper(component[nItem].type);
var callComponent = myComponent + "(" + component[nItem].opt + ");";
eval(callComponent);
}
}
var json = [
{
type: "scale",
opt: {content: [{label: "male", value: "m"}, {label: "female", value: "f"}]}
}
];
loadFrm(json);
Edit Here's the error:
missing ] after element list
[Break on this error] addScale([object Object]);
If you use a debugger to look at the string callComponent, you'll probably find it looks something like this:
addScale([object Object])
...which isn't what you want. That's because you're effectively calling toString on your opt object, and the default toString on objects just looks like that. The eval error is because that's invalid syntax.
Generally speaking, any time you think you need to use eval, there's almost certainly a better answer. In this case, it looks like you're trying to call a function and pass in opt. Assuming these functions are "globals", you can do that like this:
var loadFrm = function(component) {
var nItem, functionName;
for (nItem = 0; nItem < component.length; ++nItem) {
functionName = "add" + firstToUpper(component[nItem].type);
window[functionName](component[nItem].opt);
}
}
Live example
Notes on the above:
Don't use for..in to loop through arrays unless you really know what you're doing. for..in does not enumerate the indexes of an array, it enumerates the properties of an object.
We look up the function by name using window[functionName]. This works because "globals" are actually properties of the window object, and you can look up properties using a string name for them using bracketed notation.
Having gotten the function via window[functionName], we just call it directly, passing in the object opt rather than a string form of it. I assume addScale expects to see an object.
I moved all of the vars to the top of the function because that's where they really are (details).
If you can, I'd recommend moving addScale and the related functions to their own object rather than putting them on window. The window namespace is already pretty crowded. Here's the live example modified to not add any symbols to window at all, instead putting the addScale function on an object called functions and using it from there.
Off-topic: The syntax var loadFrm = function(component) creates an anonymous function that it then assigns to a variable. This is used a lot, but unless you're creating different functions based on a condition, e.g.:
var f;
if (...) {
f = function() { ... };
}
else {
f = function() { ... };
}
...it's not actually useful. (If you are creating different functions based on a condition like that, then it's not only useful, it's necessary.) I recommend using named functions whenever possible, because a function with a name helps your tools help you by showing you the function name in error messages, call stacks, etc.
Off-topic 2: You have a variable called json, but FYI, it's not using JSON notation. It's using a combination of JavaScript array and object literal notation, which is a superset of JSON. You'll see a lot of people confused about this, I mention it because you said you're new and so it's worth nipping in the bud. :-) JSON is purely a notation. (A very useful one.)
Use this:
fn = eval(functionName);
fn(objParameter)