1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-11-25 03:34:50 +00:00

Refactor function invocation

* Introduce new widget helper function to evaluate variables.Functions are evaluated as parameterised filter strings, macros as text with textual substitution of parameters and variables, and procedures and widgets as plain text
* Refactor the function operator and unknown operator to use the new helper
* Use the new helper to evaluate variables within filter strings, thus fixing a bug whereby functions called in such a way were being returned as plain text instead of being evaluated
* Refactor the transclude widget to use the new helper
* Update tests
This commit is contained in:
jeremy@jermolene.com
2023-01-21 22:07:34 +00:00
parent d4ab427ceb
commit 34afe4e143
6 changed files with 125 additions and 55 deletions

View File

@@ -269,7 +269,7 @@ exports.compileFilter = function(filterString) {
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle); operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
} else if(operand.variable) { } else if(operand.variable) {
var varTree = $tw.utils.parseFilterVariable(operand.text); var varTree = $tw.utils.parseFilterVariable(operand.text);
operand.value = widget.getVariable(varTree.name,{params:varTree.params,defaultValue: ""}); operand.value = widget.evaluateVariable(varTree.name,{params: varTree.params, source: source})[0] || "";
} else { } else {
operand.value = operand.text; operand.value = operand.text;
} }

View File

@@ -17,15 +17,9 @@ Export our filter function
*/ */
exports.function = function(source,operator,options) { exports.function = function(source,operator,options) {
var functionName = operator.operands[0], var functionName = operator.operands[0],
customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName); variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName);
if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) { if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
var variables = Object.create(null); return options.widget.evaluateVariable(functionName,{params: operator.operands.slice(1), source: source});
// Go through each of the defined parameters, and make a variable with the value of the corresponding operand
$tw.utils.each(customDefinition.srcVariable.params,function(param,index) {
var value = operator.operands[1 + index]; // Skip over the first operand that gives the function name
variables[param.name] = value === undefined ? param["default"] || "" : value;
});
return options.wiki.filterTiddlers(customDefinition.srcVariable.value,options.widget.makeFakeWidgetWithVariables(variables),source);
} }
// Return the input list if the function wasn't found // Return the input list if the function wasn't found
var results = []; var results = [];

View File

@@ -22,15 +22,9 @@ Export our filter function
exports["[unknown]"] = function(source,operator,options) { exports["[unknown]"] = function(source,operator,options) {
// Check for a user defined filter operator // Check for a user defined filter operator
if(operator.operator.charAt(0) === ".") { if(operator.operator.charAt(0) === ".") {
var customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator); var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator);
if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) { if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
var variables = Object.create(null); var list = options.widget.evaluateVariable(operator.operator,{params: operator.operands, source: source});
// Go through each of the defined parameters, and make a variable with the value of the corresponding operand
$tw.utils.each(customDefinition.srcVariable.params,function(param,index) {
var value = operator.operands[index];
variables[param.name] = value === undefined ? param["default"] || "" : value;
});
var list = options.wiki.filterTiddlers(customDefinition.srcVariable.value,options.widget.makeFakeWidgetWithVariables(variables),source);
if(operator.prefix === "!") { if(operator.prefix === "!") {
var results = []; var results = [];
source(function(tiddler,title) { source(function(tiddler,title) {

View File

@@ -176,6 +176,48 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}), var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}),
srcVariable = variableInfo && variableInfo.srcVariable; srcVariable = variableInfo && variableInfo.srcVariable;
if(srcVariable) { if(srcVariable) {
if(srcVariable.isFunctionDefinition) {
// Function to return parameters by name or position
var fnGetParam = function(name,index) {
// Parameter names starting with dollar must be escaped to double dollars
if(name.charAt(0) === "$") {
name = "$" + name;
}
// Look for the parameter by name
if(self.hasAttribute(name)) {
return self.getAttribute(name);
// Look for the parameter by index
} else if(self.hasAttribute(index + "")) {
return self.getAttribute(index + "");
} else {
return undefined;
}
},
result = this.evaluateVariable(this.transcludeVariable,{params: fnGetParam})[0] || "";
parser = {
tree: [{
type: "text",
text: result
}],
source: result,
type: "text/vnd.tiddlywiki"
};
if(parseAsInline) {
parser.tree[0] = {
type: "text",
text: result
};
} else {
parser.tree[0] = {
type: "element",
tag: "p",
children: [{
type: "text",
text: result
}]
}
}
} else {
var cacheKey = (parseAsInline ? "inlineParser" : "blockParser") + (this.transcludeType || ""); var cacheKey = (parseAsInline ? "inlineParser" : "blockParser") + (this.transcludeType || "");
if(variableInfo.isCacheable && srcVariable[cacheKey]) { if(variableInfo.isCacheable && srcVariable[cacheKey]) {
parser = srcVariable[cacheKey]; parser = srcVariable[cacheKey];
@@ -185,6 +227,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
srcVariable[cacheKey] = parser; srcVariable[cacheKey] = parser;
} }
} }
}
if(parser) { if(parser) {
// Add parameters widget for procedures and custom widgets // Add parameters widget for procedures and custom widgets
if(srcVariable.isProcedureDefinition || srcVariable.isWidgetDefinition) { if(srcVariable.isProcedureDefinition || srcVariable.isWidgetDefinition) {
@@ -206,25 +249,6 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
} }
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"]) $tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"])
}); });
} else if(srcVariable.isFunctionDefinition) {
var actualParams = this.getOrderedTransclusionParameters(),
variables = {};
$tw.utils.each(srcVariable.params,function(param,index) {
var name = param.name;
// Parameter names starting with dollar must be escaped to double dollars
if(name.charAt(0) === "$") {
name = "$" + name;
}
if(self.hasAttribute(name)) {
variables[name] = self.getAttribute(name);
} else if(self.hasAttribute(index + "")) {
variables[name] = self.getAttribute(index + "");
} else {
variables[name] = param["default"] || "";
}
});
var result = this.wiki.filterTiddlers(srcVariable.value,this.makeFakeWidgetWithVariables(variables),this.wiki.makeTiddlerIterator([]))[0] || "";
parser = this.wiki.parseText(this.transcludeType,result || "",{parseAsInline: parseAsInline, configTrimWhiteSpace: srcVariable.configTrimWhiteSpace});
} else { } else {
// For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__" // For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
parser = { parser = {

View File

@@ -170,6 +170,11 @@ Widget.prototype.getVariable = function(name,options) {
return this.getVariableInfo(name,options).text; return this.getVariableInfo(name,options).text;
}; };
/*
Maps actual parameters onto formal parameters, returning an array of {name:,value:} objects
formalParams - {name:,default:} (default value is optional)
actualParams - {name:,value:} (name is optional)
*/
Widget.prototype.resolveVariableParameters = function(formalParams,actualParams) { Widget.prototype.resolveVariableParameters = function(formalParams,actualParams) {
formalParams = formalParams || []; formalParams = formalParams || [];
actualParams = actualParams || []; actualParams = actualParams || [];
@@ -310,10 +315,69 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
return self.getVariableInfo(name,opts); return self.getVariableInfo(name,opts);
}; };
}, },
makeFakeWidgetWithVariables: self.makeFakeWidgetWithVariables makeFakeWidgetWithVariables: self.makeFakeWidgetWithVariables,
evaluateVariable: self.evaluateVariable,
wiki: self.wiki
}; };
}; };
/*
Evaluate a variable and associated actual parameters and result the resulting array.
The way that the variable is evaluated depends upon its type:
* Functions are evaluated as parameterised filter strings
* Macros are returned as plain text with substitution of parameters
* Procedures and widgets are returned as plain text
Options are:
params - the actual parameters may be one of:
* an array of values that may be an anonymous string value, or a {name:, value:} pair
* a hashmap of {name: value} pairs
* a function invoked with parameters (name,index) that returns a parameter value by name or position
source - iterator for source tiddlers
*/
Widget.prototype.evaluateVariable = function(name,options) {
options = options || {};
var params = options.params || [];
// Get the details of the variable (includes processing text substitution for macros
var variableInfo = this.getVariableInfo(name,{params: params,defaultValue: ""});
// Process function parameters
var variables = Object.create(null);
if(variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
// Apply default parameter values
$tw.utils.each(variableInfo.srcVariable.params,function(param,index) {
if(param["default"]) {
variables[param.name] = param["default"];
}
});
if($tw.utils.isArray(params)) {
// Parameters are an array of values or {name:, value:} pairs
$tw.utils.each(params,function(param,index) {
if(typeof param === "string") {
var paramInfo = variableInfo.srcVariable.params[index];
if(paramInfo) {
variables[paramInfo.name] = param;
}
} else {
variables[param.name] = param.value;
}
});
} else if(typeof params === "function") {
// Parameters are passed via a function
$tw.utils.each(variableInfo.srcVariable.params,function(param,index) {
variables[param.name] = params(param.name,index) || param["default"] || "";
});
} else {
// Parameters are a hashmap
$tw.utils.each(params,function(value,name) {
variables[name] = value;
});
}
return this.wiki.filterTiddlers(variableInfo.text,this.makeFakeWidgetWithVariables(variables),options.source);
} else {
return [variableInfo.text];
}
};
/* /*
Compute the current values of the attributes of the widget. Returns a hashmap of the names of the attributes that have changed. Compute the current values of the attributes of the widget. Returns a hashmap of the names of the attributes that have changed.
Options include: Options include:
@@ -348,15 +412,9 @@ Widget.prototype.computeAttribute = function(attribute) {
} else if(attribute.type === "macro") { } else if(attribute.type === "macro") {
var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params}); var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params});
if(variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) { if(variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
// It's a function definition // It is a function definition. Go through each of the defined parameters, and make a variable with the value of the corresponding provided parameter
var variables = Object.create(null); var paramArray = this.resolveVariableParameters(variableInfo.srcVariable.params,attribute.value.params);
// Go through each of the defined parameters, and make a variable with the value of the corresponding provided parameter value = this.evaluateVariable(attribute.value.name,{params: paramArray})[0] || "";
var params = this.resolveVariableParameters(variableInfo.srcVariable.params,attribute.value.params);
$tw.utils.each(params,function(param,index) {
variables[param.name] = param.value;
});
var list = self.wiki.filterTiddlers(variableInfo.text,this.makeFakeWidgetWithVariables(variables));
value = list[0] || "";
} else { } else {
value = variableInfo.text; value = variableInfo.text;
} }

View File

@@ -33,4 +33,4 @@ $param$ with a ''buffalo''
+ +
title: ExpectedResult title: ExpectedResult
<p>Going to lunch with a <strong>buffalo</strong></p><p>Going to breakfastwith a<strong>buffalo</strong></p><p>Going to dinner with a <strong>buffalo</strong></p>Going to lunch with a buffalo with a buffaloGoing to dinner with a buffalo <p>Going to lunch with a ''buffalo''</p><p>Going to breakfastwith a<strong>buffalo</strong></p><p>Going to dinner with a <strong>buffalo</strong></p>Going to lunch with a buffalo with a buffaloGoing to dinner with a buffalo