Try this simple stack-based parser:
token = /(?:\[(\w+)(.*?)\])|(?:\[\/(\w+)\])|([^\[\]]+)/g
root = {children:[]}
stack = [root]
bbStr.replace(token, function() {
var a = arguments;
if(a[1]) {
var node = {tag: a[1], attr: a[2], children:[]}
stack[0].children.push(node);
stack.unshift(node);
} else if(a[3]) {
if(stack[0].tag != a[3])
throw "Unmatched tag";
stack.shift();
} else if(a[4]) {
stack[0].children.push({tag: "#text", value: a[4]});
}
})
The output format differs from what you've posted, but that should be no problem.