1 /**
2 	Helpers for working with user-defined attributes that can be attached to
3 	function or method to modify its behavior. In some sense those are similar to
4     Python decorator. D does not support this feature natively but
5 	it can be emulated within certain code generation framework.
6 
7 	Copyright: © 2013 RejectedSoftware e.K.
8 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
9 	Authors: Михаил Страшун
10  */
11 
12 module vson.meta.funcattr;
13 
14 import std.traits : isInstanceOf, ReturnType;
15 
16 /// example
17 unittest
18 {
19 	struct Context
20 	{
21 		int increment;
22 		string token;
23 		bool updated = false;
24 	}
25 
26 	static int genID(Context* context)
27 	{
28 		static int id = 0;
29 		return (id += context.increment);
30 	}
31 
32 	static string update(string result, Context* context)
33 	{
34 		context.updated = true;
35 		return result ~ context.token;
36 	}
37 
38 	class API
39 	{
40 		@before!genID("id") @after!update()
41 		string handler(int id, string name, string text)
42 		{
43 			import std..string : format;
44 
45 			return format("[%s] %s : %s", id, name, text);
46 		}
47 	}
48 
49 	auto api = new API();
50 	auto context = new Context(5, " | token");
51 	auto funcattr = createAttributedFunction!(API.handler)(context);
52 	auto result = funcattr(&api.handler, "Developer", "Hello, World!");
53 
54 	assert (result == "[5] Developer : Hello, World! | token");
55 	assert (context.updated);
56 }
57 
58 /**
59 	Marks function/method for usage with `AttributedFunction`.
60 	
61 	Former will call a Hook before calling attributed function/method and
62 	provide its return value as input parameter.
63 
64 	Params:
65 		Hook = function/method symbol to run before attributed function/method
66 		parameter_name = name in attributed function/method parameter list to bind result to
67 
68 	Returns:
69 		internal attribute struct that embeds supplied information
70 */
71 auto before(alias Hook)(string parameter_name)
72 {
73 	return InputAttribute!Hook(parameter_name);
74 }
75 
76 ///
77 unittest
78 {
79 	int genID() { return 42; }
80 
81 	@before!genID("id")
82 	void foo(int id, double something) {}
83 }
84 
85 /**
86 	Marks function/method for usage with `AttributedFunction`.
87 	
88 	Former will call a Hook after calling attributed function/method and provide
89 	its return value as a single input parameter for a Hook.
90 
91 	There can be only one "after"-attribute attached to a single symbol.
92 
93 	Params:
94 		Hook = function/method symbol to run after attributed function/method
95 
96 	Returns:
97 		internal attribute struct that embeds supplied information
98 */
99 auto after(alias Function)()
100 {
101 	return OutputAttribute!Function();
102 }
103 
104 ///
105 unittest
106 {
107 	auto filter(int result)
108 	{
109 		return result;
110 	}
111 
112 	@after!filter()
113 	int foo() { return 42; }
114 }
115 /**
116 	Checks if parameter is calculated by one of attached
117 	functions.
118 
119 	Params:
120 		Function = function symbol to query for attributes
121 		name = parameter name to check
122 	
123 	Returns:
124 		`true` if it is calculated
125 */
126 template IsAttributedParameter(alias Function, string name)
127 {
128 	import std.traits : FunctionTypeOf;
129 
130 	static assert (is(FunctionTypeOf!Function));
131 
132 	private {
133 		alias Data = AttributedParameterMetadata!Function;
134 
135 		template Impl(T...)
136 		{
137 			static if (T.length == 0) {
138 				enum Impl = false;
139 			}
140 			else {
141 				static if (T[0].name == name) {
142 					enum Impl = true;
143 				}
144 				else {
145 					enum Impl = Impl!(T[1..$]);
146 				}
147 			}
148 		}
149 	}
150 
151 	enum IsAttributedParameter = Impl!Data;
152 }
153 
154 /**
155 	Computes the given attributed parameter using the corresponding @before modifier.
156 */
157 auto computeAttributedParameter(alias FUNCTION, string NAME, ARGS...)(ARGS args)
158 {
159 	import std.typetuple : Filter;
160 	static assert(IsAttributedParameter!(FUNCTION, NAME), "Missing @before attribute for parameter "~NAME);
161 	alias input_attributes = Filter!(isInputAttribute, __traits(getAttributes, FUNCTION));
162 	foreach (att; input_attributes)
163 		static if (att.parameter == NAME) {
164 			return att.evaluator(args);
165 		}
166 	assert(false);
167 }
168 
169 
170 /**
171 	Computes the given attributed parameter using the corresponding @before modifier.
172 
173 	This overload tries to invoke the given function as a member of the $(D ctx)
174 	parameter. It also supports accessing private member functions using the
175 	$(D PrivateAccessProxy) mixin.
176 */
177 auto computeAttributedParameterCtx(alias FUNCTION, string NAME, T, ARGS...)(T ctx, ARGS args)
178 {
179 	import std.typetuple : Filter;
180 	static assert(IsAttributedParameter!(FUNCTION, NAME), "Missing @before attribute for parameter "~NAME);
181 	alias input_attributes = Filter!(isInputAttribute, __traits(getAttributes, FUNCTION));
182 	foreach (att; input_attributes)
183 		static if (att.parameter == NAME) {
184 			static if (is(typeof(__traits(parent, att.evaluator).init) == T)) {
185 				static if (is(typeof(ctx.invokeProxy__!(att.evaluator)(args))))
186 					return ctx.invokeProxy__!(att.evaluator)(args);
187 				else return __traits(getMember, ctx, __traits(identifier, att.evaluator))(args);
188 			} else {
189 				return att.evaluator(args);
190 			}
191 		}
192 	assert(false);
193 }
194 
195 
196 /** 
197 	Helper mixin to support private member functions for $(D @before) attributes.
198 */
199 mixin template PrivateAccessProxy() {
200 	auto invokeProxy__(alias MEMBER, ARGS...)(ARGS args) { return MEMBER(args); }
201 }
202 ///
203 unittest {
204 	class MyClass {
205 		@before!computeParam("param")
206 		void method(bool param)
207 		{
208 			assert(param == true);
209 		}
210 
211 		private bool computeParam()
212 		{
213 			return true;
214 		}
215 	}
216 }
217 
218 
219 /**
220 	Processes the function return value using all @after modifiers.
221 */
222 ReturnType!FUNCTION evaluateOutputModifiers(alias FUNCTION)(ReturnType!FUNCTION result)
223 {
224 	import std.typetuple : Filter;
225 	alias output_attributes = Filter!(isOutputAttribute, __traits(getAttributes, FUNCTION));
226 	foreach (OA; output_attributes) {
227 		import std.typetuple : TypeTuple;
228 
229 		static assert (
230 			Compare!(
231 				Group!(ParameterTypeTuple!(OA.modificator)),
232 				Group!(ReturnType!Function, StoredArgTypes.expand)
233 			),
234 			format(
235 				"Output attribute function '%s%s' argument list " ~
236 				"does not match provided argument list %s",
237 				fullyQualifiedName!(OA.modificator),
238 				ParameterTypeTuple!(OA.modificator).stringof,
239 				TypeTuple!(ReturnType!Function, StoredArgTypes.expand).stringof
240 			)
241 		);
242 
243 		result = OA.modificator(result, m_storedArgs);
244 	}
245 	return result;
246 }
247 
248 ///
249 unittest
250 {
251 	int foo()
252 	{
253 		return 42;
254 	}
255 
256 	@before!foo("name1")
257 	void bar(int name1, double name2)
258 	{
259 	}
260 
261 	static assert (IsAttributedParameter!(bar, "name1"));
262 	static assert (!IsAttributedParameter!(bar, "name2"));
263 	static assert (!IsAttributedParameter!(bar, "oops"));
264 }
265 
266 // internal attribute definitions
267 private {
268 
269 	struct InputAttribute(alias Function)
270 	{
271 		alias evaluator = Function;
272 		string parameter;
273 	}
274 
275 	struct OutputAttribute(alias Function)
276 	{
277 		alias modificator = Function;
278 	}
279 
280 	template isInputAttribute(T...)	
281 	{
282 		enum isInputAttribute = (T.length == 1) && isInstanceOf!(InputAttribute, typeof(T[0]));
283 	}
284 
285 	unittest
286 	{
287 		void foo() {}
288 
289 		enum correct = InputAttribute!foo("name");
290 		enum wrong = OutputAttribute!foo();
291 
292 		static assert (isInputAttribute!correct);
293 		static assert (!isInputAttribute!wrong);
294 	}
295 
296 	template isOutputAttribute(T...)	
297 	{
298 		enum isOutputAttribute = (T.length == 1) && isInstanceOf!(OutputAttribute, typeof(T[0]));
299 	}
300 
301 	unittest
302 	{
303 		void foo() {}
304 
305 		enum correct = OutputAttribute!foo();
306 		enum wrong = InputAttribute!foo("name");
307 
308 		static assert (isOutputAttribute!correct);
309 		static assert (!isOutputAttribute!wrong);
310 	}
311 }
312 
313 //  tools to operate on InputAttribute tuple
314 private {
315 
316 	// stores metadata for single InputAttribute "effect"
317 	struct Parameter
318 	{
319 		// evaluated parameter name
320 		string name;
321 		// that parameter index in attributed function parameter list
322 		int index;
323 		// fully qualified return type of attached function
324 		string type; 
325 		// for non-basic types - module to import
326 		string origin;
327 	}
328 
329 	/**
330 		Used to accumulate various parameter-related metadata in one
331 		tuple in one go.
332 
333 		Params:
334 			Function = attributed functon / method symbol
335 
336 		Returns:
337 			TypeTuple of Parameter instances, one for every Function
338 			parameter that will be evaluated from attributes.
339 	*/
340 	template AttributedParameterMetadata(alias Function)
341 	{
342 		import std.array : join;
343 		import std.typetuple : Filter, staticMap, staticIndexOf;
344 		import std.traits : ParameterIdentifierTuple, ReturnType,
345 			fullyQualifiedName, moduleName;
346 	
347 		private alias attributes = Filter!(
348 			isInputAttribute,
349 			__traits(getAttributes, Function)
350 		);
351 
352 		private	alias parameter_names = ParameterIdentifierTuple!Function;
353 
354 		/*
355 			Creates single Parameter instance. Used in pair with
356 			staticMap.
357 		*/	
358 		template BuildParameter(alias attribute)
359 		{
360 			enum name = attribute.parameter;
361 
362 			static assert (
363 				is (ReturnType!(attribute.evaluator)) && !(is(ReturnType!(attribute.evaluator) == void)),
364 				"hook functions attached for usage with `AttributedFunction` " ~
365 				"must have a return type"
366 			);
367 		
368 			static if (is(typeof(moduleName!(ReturnType!(attribute.evaluator))))) {
369 				enum origin = moduleName!(ReturnType!(attribute.evaluator));
370 			}
371 			else {
372 				enum origin = "";
373 			}
374 
375 			enum BuildParameter = Parameter(
376 				name,
377 				staticIndexOf!(name, parameter_names),
378 				fullyQualifiedName!(ReturnType!(attribute.evaluator)),
379 				origin
380 			);
381 
382 			import std..string : format;
383 
384 			static assert (
385 				BuildParameter.index >= 0,
386 				format(
387 					"You are trying to attach function result to parameter '%s' " ~
388 					"but there is no such parameter for '%s(%s)'",
389 					name,
390 					fullyQualifiedName!Function,
391 					join([ parameter_names ], ", ")
392 				)
393 			);
394 		}
395 
396 		alias AttributedParameterMetadata = staticMap!(BuildParameter, attributes);
397 	}
398 
399 	// no false attribute detection
400 	unittest
401 	{
402 		@(42) void foo() {}
403 		static assert (AttributedParameterMetadata!foo.length == 0);
404 	}
405 
406 	// does not compile for wrong attribute data
407 	unittest
408 	{
409 		int attached1() { return int.init; }		
410 		void attached2() {}
411 
412 		@before!attached1("doesnotexist")
413 		void bar(int param) {}
414 
415 		@before!attached2("param")
416 		void baz(int param) {}
417 
418 		// wrong name
419 		static assert (!__traits(compiles, AttributedParameterMetadata!bar));
420 		// no return type
421 		static assert (!__traits(compiles, AttributedParameterMetadata!baz));
422 	}
423 
424 	// generates expected tuple for valid input
425 	unittest
426 	{		
427 		int attached1() { return int.init; }
428 		double attached2() { return double.init; }
429 
430 		@before!attached1("two") @before!attached2("three")
431 		void foo(string one, int two, double three) {}
432 
433 		alias result = AttributedParameterMetadata!foo;
434 		static assert (result.length == 2);
435 		static assert (result[0] == Parameter("two", 1, "int"));
436 		static assert (result[1] == Parameter("three", 2, "double"));
437 	}
438 
439 	/**
440 		Combines types from arguments of initial `AttributedFunction` call
441 		with parameters (types) injected by attributes for that call.
442 
443 		Used to verify that resulting argument list can be passed to underlying
444 		attributed function.
445 
446 		Params:
447 			ParameterMeta = Group of Parameter instances for extra data to add into argument list
448 			ParameterList = Group of types from initial argument list
449 
450 		Returns:
451 			type tuple of expected combined function argument list
452 	*/
453 	template MergeParameterTypes(alias ParameterMeta, alias ParameterList)
454 	{	
455 		import vson.meta.typetuple : isGroup, Group;
456 
457 		static assert (isGroup!ParameterMeta);
458 		static assert (isGroup!ParameterList);
459 
460 		static if (ParameterMeta.expand.length) {
461 			enum Parameter meta = ParameterMeta.expand[0];
462 
463 			static assert (meta.index <= ParameterList.expand.length);
464 			static if (meta.origin != "") {
465 				mixin("static import " ~ meta.origin ~ ";");
466 			}
467 			mixin("alias type = " ~ meta.type ~ ";");
468 
469 			alias PartialResult = Group!(
470 				ParameterList.expand[0..meta.index],
471 				type,
472 				ParameterList.expand[meta.index..$]
473 			);
474 
475 			alias MergeParameterTypes = MergeParameterTypes!(
476 				Group!(ParameterMeta.expand[1..$]),
477 				PartialResult
478 			);
479 		}
480 		else {
481 			alias MergeParameterTypes = ParameterList.expand;
482 		}
483 	}
484 	
485 	// normal
486 	unittest
487 	{
488 		import vson.meta.typetuple : Group, Compare;
489 
490 		alias meta = Group!(
491 			Parameter("one", 2, "int"),
492 			Parameter("two", 3, "string")
493 		);
494 
495 		alias initial = Group!( double, double, double );
496 
497 		alias merged = Group!(MergeParameterTypes!(meta, initial));
498 
499 		static assert (
500 			Compare!(merged, Group!(double, double, int, string, double))
501 		);
502 	}
503 
504 	// edge
505 	unittest
506 	{
507 		import vson.meta.typetuple : Group, Compare;
508 
509 		alias meta = Group!(
510 			Parameter("one", 3, "int"),
511 			Parameter("two", 4, "string")
512 		);
513 
514 		alias initial = Group!( double, double, double );
515 
516 		alias merged = Group!(MergeParameterTypes!(meta, initial));
517 
518 		static assert (
519 			Compare!(merged, Group!(double, double, double, int, string))
520 		);
521 	}
522 
523 	// out-of-index
524 	unittest
525 	{
526 		import vson.meta.typetuple : Group;
527 
528 		alias meta = Group!(
529 			Parameter("one", 20, "int"),
530 		);
531 
532 		alias initial = Group!( double );
533 
534 		static assert (
535 			!__traits(compiles,  MergeParameterTypes!(meta, initial))
536 		);
537 	}
538 
539 }
540 
541 /**
542 	Entry point for `funcattr` API.
543 
544 	Helper struct that takes care of calling given Function in a such
545 	way that part of its arguments are evalutated by attached input attributes
546 	(see `before`) and output gets post-processed by output attribute
547 	(see `after`).
548 
549 	One such structure embeds single attributed function to call and
550 	specific argument type list that can be passed to attached functions.
551 
552 	Params:
553 		Function = attributed function
554 		StoredArgTypes = Group of argument types for attached functions
555 
556 */
557 struct AttributedFunction(alias Function, alias StoredArgTypes)	
558 {
559 	import std.traits : isSomeFunction, ReturnType, FunctionTypeOf,
560 		ParameterTypeTuple, ParameterIdentifierTuple;
561 	import vson.meta.typetuple : Group, isGroup, Compare;
562 	import std.functional : toDelegate;
563 	import std.typetuple : Filter;
564 
565 	static assert (isGroup!StoredArgTypes);
566 	static assert (is(FunctionTypeOf!Function));
567 
568 	/**
569 		Stores argument tuple for attached function calls
570 
571 		Params:
572 			args = tuple of actual argument values
573 	*/
574 	void storeArgs(StoredArgTypes.expand args)
575 	{
576 		m_storedArgs = args;
577 	}
578 
579 	/**
580 		Used to invoke configured function/method with
581 		all attached attribute functions.
582 
583 		As aliased method symbols can't be called without
584 		the context, explicit providing of delegate to call
585 		is required
586 
587 		Params:
588 			dg = delegated created from function / method to call
589 			args = list of arguments to dg not provided by attached attribute function
590 
591 		Return:
592 			proxies return value of dg
593 	*/
594 	ReturnType!Function opCall(T...)(FunctionDg dg, T args)
595 	{
596                 import std.traits : fullyQualifiedName;
597                 import std..string : format;
598 
599 		enum hasReturnType = is(ReturnType!Function) && !is(ReturnType!Function == void);
600 
601 		static if (hasReturnType) {
602 			ReturnType!Function result;
603 		}
604 
605 		// check that all attached functions have conforming argument lists
606 		foreach (uda; input_attributes) {
607 			static assert (
608 				Compare!(
609 					Group!(ParameterTypeTuple!(uda.evaluator)),
610 					StoredArgTypes
611 				),
612 				format(
613 					"Input attribute function '%s%s' argument list " ~
614 					"does not match provided argument list %s",
615 					fullyQualifiedName!(uda.evaluator),
616 					ParameterTypeTuple!(uda.evaluator).stringof,
617 					StoredArgTypes.expand.stringof
618 				)
619 			);
620 		}
621 
622 		static if (hasReturnType) {
623 			result = prepareInputAndCall(dg, args);
624 		}
625 		else {
626 			prepareInputAndCall(dg, args);
627 		}
628 
629 		static assert (
630 			output_attributes.length <= 1,
631 			"Only one output attribute (@after) is currently allowed"
632 		);
633 
634 		static if (output_attributes.length) {
635 			import std.typetuple : TypeTuple;
636 
637 			static assert (
638 				Compare!(
639 					Group!(ParameterTypeTuple!(output_attributes[0].modificator)),
640 					Group!(ReturnType!Function, StoredArgTypes.expand)
641 				),
642 				format(
643 					"Output attribute function '%s%s' argument list " ~
644 					"does not match provided argument list %s",
645 					fullyQualifiedName!(output_attributes[0].modificator),
646 					ParameterTypeTuple!(output_attributes[0].modificator).stringof,
647 					TypeTuple!(ReturnType!Function, StoredArgTypes.expand).stringof
648 				)
649 			);
650 
651 			static if (hasReturnType) {
652 				result = output_attributes[0].modificator(result, m_storedArgs);
653 			}
654 			else {
655 				output_attributes[0].modificator(m_storedArgs);
656 			}
657 		}
658 
659 		static if (hasReturnType) {
660 			return result;
661 		}
662 	}
663 	
664 	/**
665 		Convenience wrapper tha creates stub delegate for free functions.
666 
667 		As those do not require context, passing delegate explicitly is not
668 		required.
669 	*/
670 	ReturnType!Function opCall(T...)(T args)
671 		if (!is(T[0] == delegate))
672 	{
673 		return this.opCall(toDelegate(&Function), args);
674 	}
675 
676 	private {
677 		// used as an argument tuple when function attached
678 		// to InputAttribute is called
679 		StoredArgTypes.expand m_storedArgs;
680 
681 		// used as input type for actual function pointer so
682 		// that both free functions and methods can be supplied
683 		alias FunctionDg = typeof(toDelegate(&Function));
684 
685 		// information about attributed function arguments
686 		alias ParameterTypes = ParameterTypeTuple!Function;
687 		alias parameter_names = ParameterIdentifierTuple!Function;
688 
689 		// filtered UDA lists
690 		alias input_attributes = Filter!(isInputAttribute, __traits(getAttributes, Function));
691 		alias output_attributes = Filter!(isOutputAttribute, __traits(getAttributes, Function));
692 	}
693 
694 	private {
695 
696 		/**
697 			Does all the magic necessary to prepare argument list for attributed
698 			function based on `input_attributes` and `opCall` argument list.
699 
700 			Catches all name / type / size mismatch erros in that domain via
701 			static asserts.
702 
703 			Params:
704 				dg = delegate for attributed function / method
705 				args = argument list from `opCall`
706 
707 			Returns:
708 				proxies return value of dg
709 		*/
710 		ReturnType!Function prepareInputAndCall(T...)(FunctionDg dg, T args)
711 			if (!Compare!(Group!T, Group!(ParameterTypeTuple!Function)))
712 		{
713 			alias attributed_parameters = AttributedParameterMetadata!Function;
714 			// calculated combined input type list
715 			alias Input = MergeParameterTypes!(
716 				Group!attributed_parameters,
717 				Group!T
718 			);
719 
720 			import std.traits : fullyQualifiedName;
721 			import std..string : format;
722 
723 			static assert (
724 				Compare!(Group!Input, Group!ParameterTypes),
725 				format(
726 					"Calculated input parameter type tuple %s does not match " ~
727 					"%s%s",
728 					Input.stringof,
729 					fullyQualifiedName!Function,
730 					ParameterTypes.stringof
731 				)
732 			);
733 
734 			// this value tuple will be used to assemble argument list
735 			Input input;
736 
737 			foreach (i, uda; input_attributes) {
738 				// each iteration cycle is responsible for initialising `input`
739 				// tuple from previous spot to current attributed parameter index
740 				// (including)
741 
742 				enum index = attributed_parameters[i].index;
743 
744 				static if (i == 0) {
745 					enum lStart = 0;
746 					enum lEnd = index;
747 					enum rStart = 0;
748 					enum rEnd = index;
749 				}
750 				else {
751 					enum previousIndex = attributed_parameters[i - 1].index;
752 					enum lStart = previousIndex + 1;
753 					enum lEnd = index;
754 					enum rStart = previousIndex + 1 - i;
755 					enum rEnd = index - i;
756 				}				
757 
758 				static if (lStart != lEnd) {
759 					input[lStart..lEnd] = args[rStart..rEnd];
760 				}
761 
762 				// during last iteration cycle remaining tail is initialised
763 				// too (if any)
764 
765 				static if ((i == input_attributes.length - 1) && (index != input.length - 1)) {
766 					input[(index + 1)..$] = args[(index - i)..$];
767 				}
768 
769 				input[index] = uda.evaluator(m_storedArgs);
770 			}
771 
772 			// handle degraded case with no attributes separately
773 			static if (!input_attributes.length) {
774 				input[] = args[];
775 			}
776 
777 			return dg(input);
778 		}
779 
780 		/**
781 			`prepareInputAndCall` overload that operates on argument tuple that exactly
782 			matches attributed function argument list and thus gets updated by
783 			attached function instead of being merged with it
784 		*/
785 		ReturnType!Function prepareInputAndCall(T...)(FunctionDg dg, T args)
786 			if (Compare!(Group!T, Group!(ParameterTypeTuple!Function)))
787 		{
788 			alias attributed_parameters = AttributedParameterMetadata!Function;
789 
790 			foreach (i, uda; input_attributes) {
791 				enum index = attributed_parameters[i].index;
792 				args[index] = uda.evaluator(m_storedArgs);
793 			}
794 
795 			return dg(args);
796 		} 
797 	}
798 }
799 
800 /// example
801 unittest
802 {
803 	import std.conv;
804 
805 	static string evaluator(string left, string right)
806 	{
807 		return left ~ right;
808 	}
809 
810 	// all attribute function must accept same stored parameters
811 	static int modificator(int result, string unused1, string unused2)
812 	{
813 		return result * 2;
814 	}
815 
816 	@before!evaluator("a") @before!evaluator("c") @after!modificator()
817 	static int sum(string a, int b, string c, double d)
818 	{
819 		return to!int(a) + to!int(b) + to!int(c) + to!int(d);
820 	}
821 
822 	// ("10", "20") - stored arguments for evaluator()
823 	auto funcattr = createAttributedFunction!sum("10", "20");
824 
825 	// `b` and `d` are unattributed, thus `42` and `13.5` will be
826 	// used as their values
827 	int result = funcattr(42, 13.5);
828 
829 	assert(result == (1020 + 42 + 1020 + to!int(13.5)) * 2);
830 }
831 
832 // testing other prepareInputAndCall overload
833 unittest
834 {
835 	import std.conv;
836 
837 	static string evaluator(string left, string right)
838 	{
839 		return left ~ right;
840 	}
841 
842 	// all attribute function must accept same stored parameters
843 	static int modificator(int result, string unused1, string unused2)
844 	{
845 		return result * 2;
846 	}
847 
848 	@before!evaluator("a") @before!evaluator("c") @after!modificator()
849 	static int sum(string a, int b, string c, double d)
850 	{
851 		return to!int(a) + to!int(b) + to!int(c) + to!int(d);
852 	}
853 
854 	auto funcattr = createAttributedFunction!sum("10", "20");
855 
856 	// `a` and `c` are expected to be simply overwritten
857 	int result = funcattr("1000", 42, "1000", 13.5);
858 
859 	assert(result == (1020 + 42 + 1020 + to!int(13.5)) * 2);
860 }
861 
862 /**
863 	Syntax sugar in top of AttributedFunction
864 
865 	Creates AttributedFunction with stored argument types that
866 	match `T` and stores `args` there before returning.	
867 */
868 auto createAttributedFunction(alias Function, T...)(T args)
869 {
870 	import vson.meta.typetuple : Group;
871 
872 	AttributedFunction!(Function, Group!T) result;
873 	result.storeArgs(args);
874 	return result;
875 }
876 
877 ///
878 unittest
879 {
880 	void foo() {}
881 
882 	auto funcattr = createAttributedFunction!foo(1, "2", 3.0);
883 
884 	import std.typecons : tuple;
885 	assert (tuple(funcattr.m_storedArgs) == tuple(1, "2", 3.0));
886 }