1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-12-25 01:20:30 +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);
} else if(operand.variable) {
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 {
operand.value = operand.text;
}

View File

@ -17,15 +17,9 @@ Export our filter function
*/
exports.function = function(source,operator,options) {
var functionName = operator.operands[0],
customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName);
if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) {
var variables = Object.create(null);
// 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);
variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName);
if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
return options.widget.evaluateVariable(functionName,{params: operator.operands.slice(1), source: source});
}
// Return the input list if the function wasn't found
var results = [];

View File

@ -22,15 +22,9 @@ Export our filter function
exports["[unknown]"] = function(source,operator,options) {
// Check for a user defined filter operator
if(operator.operator.charAt(0) === ".") {
var customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator);
if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) {
var variables = Object.create(null);
// 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);
var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator);
if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
var list = options.widget.evaluateVariable(operator.operator,{params: operator.operands, source: source});
if(operator.prefix === "!") {
var results = [];
source(function(tiddler,title) {

View File

@ -176,6 +176,48 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}),
srcVariable = variableInfo && variableInfo.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 || "");
if(variableInfo.isCacheable && srcVariable[cacheKey]) {
parser = srcVariable[cacheKey];
@ -185,6 +227,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
srcVariable[cacheKey] = parser;
}
}
}
if(parser) {
// Add parameters widget for procedures and custom widgets
if(srcVariable.isProcedureDefinition || srcVariable.isWidgetDefinition) {
@ -206,25 +249,6 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}
$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 {
// For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
parser = {

View File

@ -170,6 +170,11 @@ Widget.prototype.getVariable = function(name,options) {
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) {
formalParams = formalParams || [];
actualParams = actualParams || [];
@ -310,10 +315,69 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
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.
Options include:
@ -348,15 +412,9 @@ Widget.prototype.computeAttribute = function(attribute) {
} else if(attribute.type === "macro") {
var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params});
if(variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
// It's a function definition
var variables = Object.create(null);
// Go through each of the defined parameters, and make a variable with the value of the corresponding provided parameter
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] || "";
// 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 paramArray = this.resolveVariableParameters(variableInfo.srcVariable.params,attribute.value.params);
value = this.evaluateVariable(attribute.value.name,{params: paramArray})[0] || "";
} else {
value = variableInfo.text;
}

View File

@ -33,4 +33,4 @@ $param$ with a ''buffalo''
+
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