I'm creating a DSL based on JSON and JavaScript and I have a requirement to let key values be specified 'raw' and not enclosed in string delimiters. A quick example that hopefully explains this:
{myKey:custom_function('arg1'), myKey2:custom_function("another arg1")}
should become
{myKey:"custom_function('arg1')", myKey2:"custom_function(\"another arg1\")"}
This is because at the time of parsing the JSON object, custom_function will not exist. I need to be able to parse the JSON without evaluating any of the values, then only expand values one by one as I iterate the keys.
What regular expression or other method can I use to turn that 1st snippet into the 2nd one?
I'm assuming a simpler solution will cover 90% of cases but that writing a bullet-proof implementation would take a lot of effort. Based on the research I did into JavaScript's regular expression support (apparantly no lookbehind capability) I'm assuming it will require something more than just 1 or 2 lines of regex patterns.
Also, this is for a node application so any tricks that it has for this will be helpful too.
EDIT:
This question seems to be getting some downvotes, but I've left it up anyway for the benefit of future googlers / my own reference. It's a perfectly valid question about what method / technique would work best for this kind of problem, and there could easily be other node/js newcomers who face a similar problem.
Final answer: Regex just isn't suited to a task as complex as this. Any similarly complex solutions I found online (e.g. removing code comments) all resorted to a mainly custom, iterative approach, only using regex sparingly, so a similar approach ended up being not too painless in this situation.
So in the end the 'best' method I could find didn't involve very much regex or any specialized libraries from node or elsewhere suited to the problem.
Finally, for the benefit of future googlers who might have a similar problem, I've published my solution at https://gist.github.com/2590689 and copied below:
//clothe a hub file that has 'naked' expressions
//e.g. turn {key:$('body p')} into {key:"$('body p')"}
function clothe(contents){
closers = /[\}\]\)\/"']/
openers = /[\{\[\(\/"']/
closing = {
"{": "}",
"[": "]",
"(": ")",
"/": "/",
'"': '"',
"'": "'"
}
contents = contents.split("");
var beforeKey = true;
var inKey = false;
var beforeValue = false;
var inValue = false;
var inArray = false;
var delimiterStack = [];
function inDelimited(){
return delimiterStack.length > 0;
}
function toggleDelimiter(d){
if(openers.exec(d) && !closers.exec(d)){
pushDelimiter(d);
}else if(openers.exec(d) && closers.exec(d)){
if(topDelimiter()){
if(topDelimiter()==d){
popDelimiterIfValid(d);
}else{
pushDelimiter(d);
}
}else{
pushDelimiter(d);
}
}else if(closers.exec(d)){
popDelimiterIfValid(d);
}
}
function topDelimiter(){
if(delimiterStack.length>=0){
return delimiterStack[delimiterStack.length-1];
}else{
return undefined;
}
}
function pushDelimiter(d){
delimiterStack.push(d);
}
function popDelimiterIfValid(d){
if(delimiterStack.length>0)
if(closing[delimiterStack[delimiterStack.length-1]]==d)
delimiterStack.pop(d);
}
function rTrimmedRightBound(rightBound){
while(rightBound>0){
if(!/\s/g.exec(contents[--rightBound])){
return rightBound+1;
}
}
}
for(var i=0; i<contents.length; i++){
function delimiterCheck(c){
if(c=='"'){
toggleDelimiter('"');
contents.splice(i, 0, '\\');
i++;
}else if(openers.exec(c) || closers.exec(c)){
toggleDelimiter(c)
}
}
if(beforeKey){
if(/[a-zA-Z0-9$_!]/.exec(contents[i])){
beforeKey = false;
inKey = true;
}
}else if(inKey){
if(contents[i]==":"){
inKey = false;
beforeValue = true;
}
}else if(beforeValue){
if(/[a-zA-Z0-9$_!'"\(\/]/.exec(contents[i])){
contents.splice(i, 0, '"');
i++;
beforeValue = false;
inValue = true;
delimiterCheck(contents[i]);
}else if(/\{/.exec(contents[i])){
beforeKey = true;
beforeValue = false;
}else if(/\[/.exec(contents[i])){
beforeValue = false;
inArray = true;
}
}else if(inArray && !inValue){
if(/[a-zA-Z0-9$_!'"\(\/]/.exec(contents[i])){
contents.splice(i, 0, '"');
i++;
beforeValue = false;
inValue = true;
delimiterCheck(contents[i]);
}
}else if(inValue){
if(!inDelimited() && /[\},\]]/.exec(contents[i])){
contents.splice(rTrimmedRightBound(i), 0, '"');
i++;
inValue = false;
if(/\]/.exec(contents[i])){
inArray = false;
}
beforeKey = !inArray;
}else{
delimiterCheck(contents[i]);
}
}
}
return contents.join("");
}