Настало время написать на нашем языке первую программу с функцией.
Вообще, напомним что XTX будет исповедовать функциональную парадигму программирования. И разумеется первое что мы напишем на нем - программу которая считает факториал.
Для отступления, напомним как выглядит эта программа при императивном подходе(C#):
- Код: Выделить всё
int F(int n)
{
int r = 1;
for(int i=1; i<=n; i++)
r*=i;
return r;
}
- Код: Выделить всё
let rec F n = if n=1 then 1 else n*F(n-1)
На нашем языке эта же функция выглядит вот так:
- Код: Выделить всё
<?xml version="1.0" encoding="utf-8" ?>
<concat>
<f isdefinition = "1">
<if>
<equals>
<?param *[1]?>
<n>1</n>
</equals>
<n>1</n>
<multiple>
<?param *[1]?>
<f>
<dec>
<?param *[1]?>
</dec>
</f>
</multiple>
</if>
</f>
<f>
<n>10</n>
</f>
</concat>
Немного длинновато?
Если разобраться, то функция вводится очень просто - любой узел с атрибутом isdefinition = "1" является объявлением функции.
Вызов функции выглядит так: <имя функции>узел-параметр</имя функции>
Что бы получить значение параметра из функции используется конструкция <?param xpath-выражение?>
Когда интерпретатор встречает такой узел, он вычисляет значение xpath-выражение относительно узла-параметра и выполняет содержащийся в нем код.
Благодаря этому можно передавать в функцию произвольное число параметров - и это совершенно естественно и это делается без каких либо специальных конструкций языка, куда более естественно чем тот же paramarray.
Тут есть кое что особенное
В вычислении параметров есть одна довольно существенная деталь. Во всех нормальных яп параметры сначала вычисляются, а потом передаются в вызывающую функцию. В XTX это не так - в функцию передаются не параметры, а скорее ссылка на код, который вычисляет эти параметры. Параметры вычисляются в момент первого обращения к ним из вызванной функции. У этого явления есть одно важное преимущество - на XTX пользователю можно написать, к примеру функцию iif, которая не будет вычислять значение "неверной" ветки. Huh?
Ну и в конце в очередной раз отмечу что функции - это всего лишь расширение языка, приведем модуль который его реализует(C#):
- Код: Выделить всё
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XTX.parser;
using System.Xml;
using XTX.parser.extensions;
namespace XTX.library
{
namespace XTX.library
{
class Functions
{
IRunTime w;
Stack<StackFrame> paramStack = new Stack<StackFrame>();
public Functions(IRunTime _w)
{
if (_w == null)
throw new ArgumentNullException();
w = _w;
}
[Preparser()]
public void createFunctions(XmlNode e)
{
foreach (XmlNode n in e.SelectNodes("//*[@isdefinition = '1']"))
{
w.add_object_handleMap(new Function(this, n));
w.runTimeSkip(n);
}
}
[RegisterAction()]
public object param(XmlNode e)
{
string xpath;
if (e.NodeType == XmlNodeType.Element)
xpath = w.extractParam(e, "w").Value;
else if (e.NodeType == XmlNodeType.ProcessingInstruction)
xpath = e.Value;
else
throw new ArgumentException();
StackFrame localFrame = null;
try
{
localFrame = paramStack.Pop();
object value;
if (localFrame.paramCache.TryGetValue(xpath, out value))
return using_cache(value);
XmlNode param = localFrame.node.SelectSingleNode(xpath);
if (param == null)
return w.warn_null();
value = w.handle(param);
localFrame.paramCache.Add(xpath, value);
return not_using_cache(value);
}
finally
{
if (localFrame != null)
paramStack.Push(localFrame);
}
}
#region("Cache statistics")
bool print_statistic = false;
int cache_usage;
private object using_cache(object value)
{
if (print_statistic)
{
cache_usage++;
Console.WriteLine("Cache is used " + cache_usage + " times");
}
return value;
}
int cache_nonusage;
private object not_using_cache(object value)
{
if (print_statistic)
{
cache_nonusage++;
Console.WriteLine("Cache was not used " + cache_nonusage + " times");
}
return value;
}
#endregion
protected class Function
{
Functions f;
IRunTime w;
XmlNode n;
public Function(Functions f, XmlNode n)
{
if (f == null || n==null)
throw new ArgumentNullException();
this.f = f;
this.n = n;
w = f.w;
}
[RegisterAction(dynamicName=true)]
public object run(XmlNode e)
{
if (e == null)
return n.Name;
try
{
// How to guarantee that pop operation runs only if push will be successful?
f.paramStack.Push(new StackFrame(e));
return w.handle(w.xmlFirstChild(n));
}
finally
{
f.paramStack.Pop();
}
}
}
private class StackFrame
{
public XmlNode node;
public Dictionary<string, object> paramCache = new Dictionary<string, object>();
public StackFrame(XmlNode _node)
{
if (_node == null)
throw new ArgumentNullException("_node");
node = _node;
}
}
}
}
}