Skip to content

functions.stx

pdmosses/webdsl-statix/webdslstatix/trans/static-semantics/actions/functions.stx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
module 

imports
  static-semantics/types/built-ins

  static-semantics/webdsl-actions
  static-semantics/webdsl-entities
  static-semantics/webdsl-types
  static-semantics/webdsl

rules // functions calls

  typeOfExp(s, ThisCall2Exp(ThisCall(f, argExps))) = t :-
    t == typeOfFunctionCall(s, f, argExps).

  typeOfSimpleExp(s, SimpleThisCall(ThisCall(f, argExps))) = t :-
    t == typeOfFunctionCall(s, f, argExps).

  typeOfPlaceholderExp(s, PHThisCall(ThisCall(f, argExps))) = t :-
    t == typeOfFunctionCall(s, f, argExps).

  typeOfExp(, Call(exp, f, argExps)) = t :- {   }
    expType == typeOfExp(s, exp),
    new s_eval, s_eval -P-> s,
    t == typeOfCall(s_eval, expType, f, argExps).

  typeOfSimpleExp(, SimpleCall(exp, f, argExps)) = t :- {   }
    expType == typeOfSimpleExp(s, exp),
    new s_eval, s_eval -P-> s,
    t == typeOfCall(s_eval, expType, f, argExps).

  typeOfPlaceholderExp(, PHCall(exp, f, argExps)) = t :- {   }
    expType == typeOfExp(s, exp),
    new s_eval, s_eval -P-> s,
    t == typeOfCall(s_eval, expType, f, argExps).

   : scope * string * list(Exp)-> TYPE
  typeOfFunctionCall(s, f, args) = typeOfFunctionCallInternal(s, f, args, funSigs) :-
    funSigs == resolveFunction(s, f).

   : scope * TYPE * string * list(Exp)-> TYPE
  typeOfCall(s, ENTITY(_, s_ent), f, args) = typeOfFunctionCallInternal(s, f, args, funSigs) :-
    funSigs == resolveEntityFunction(s_ent, f).

  typeOfCall(s, STATICENTITY(_, s_ent), f, args) = typeOfFunctionCallInternal(s, f, args, funSigs) :-
    funSigs == resolveStaticEntityFunction(s_ent, f).

  typeOfCall(s, REF(t), f, args) = typeOfCall(s, t, f, args).
  typeOfCall(s, t, f, args) = UNTYPED() :- false | error $[First subexpression of a function call must be of type entity, [t] found].

   : scope * string * list(Exp) * list((path * (string * TYPE))) -> TYPE
  typeOfFunctionCallInternal(s, , args, funSigs) =  :- {    }
    argTypes == typesOfExps(s, args),
    result == mostSpecificSigs(argTypes, typeCompatibleSigs(funSigs, argTypes)),
    [(f', FUNCTION(_, _, t, _))] == result | error $[Cannot resolve function [f] with compatible argument types] @f,
    @f.ref := f',
    @f.type := t.

   : list((string * TYPE)) * string * string
  singleSignature([], type, ) :- false | error $[Cannot resolve [type] [f] with compatible argument types] @f.
  singleSignature([_ | [_ | _]], type, ) :- false | error $[Found multiple definitions of [type] [f] with equally specific argument types] @f.
  singleSignature(_, _, _).

rules // invoke time interval

  defOk(, InvokeEvery(exp, TimeInterval(parts))) :-
    typed(s, exp),
    timeIntervalPartsOk(s, parts).

  timeIntervalPartsOk maps timeIntervalPartOk(*, list(*))
   : scope * TimeIntervalPart
  timeIntervalPartOk(, Weeks(exp))        :- typeOfExp(s, exp) == int(s).
  timeIntervalPartOk(, Days(exp))         :- typeOfExp(s, exp) == int(s).
  timeIntervalPartOk(, Hours(exp))        :- typeOfExp(s, exp) == int(s).
  timeIntervalPartOk(, Minutes(exp))      :- typeOfExp(s, exp) == int(s).
  timeIntervalPartOk(, Seconds(exp))      :- typeOfExp(s, exp) == int(s).
  timeIntervalPartOk(, Milliseconds(exp)) :- typeOfExp(s, exp) == int(s).

rules // function definitions

  // global functions
  defOk(s, GlobalFunction(f)) :- globalFunctionOk(s, f).
  defOk(, CachedGlobalFunction()) :-
    canBeCached(s, f),
    globalFunctionOk(s, f).

  defOk(s, ExtendGlobalFunction(_)) :- try { false } | warning $[This definition is not yet implemented].

  // analyze a function:
  // - argument types and return type must be defined
  // - declare arguments as variables in function scope
  // - analyze function body
  // - declare function in given scope
   : scope * Function
  globalFunctionOk(, Function(name, FormalArgs(), OptSortSome(returnSort), Block(stmts))) :- {     }
    returnType == typeOfSort(s_outer, returnSort),
    new s_function, s_function -F-> s_outer,
    argTypes == typesOfArgs(s_outer, args),
    declareParameters(s_function, zipArgTypes(args, argTypes)),
    new s_body, s_body -P-> s_function,
    stmtsOk(s_body, stmts, returnType),
    declFunctionGlobal(s_outer, name, FUNCTION_ORIGIN(args), argTypes, returnType).

   : scope * Function * BOOL
  entityFunctionOk(, Function(, FormalArgs(), OptSortSome(returnSort), Block(stmts)), static) :- {     }
    returnType == typeOfSort(s_ent, returnSort),
    new s_function, s_function -F-> s_ent,
    argTypes == typesOfArgs(s_ent, args),
    declareParameters(s_function, zipArgTypes(args, argTypes)),
    new s_body, s_body -P-> s_function,
    stmtsOk(s_body, stmts, returnType),
    declFunctionEntity(s_ent, name, FUNCTION_ORIGIN(args), argTypes, returnType, static),
    noDuplicateFunDefsEntity(s_ent, name, argTypes).

  entityFunctionOk(s_ent, Function(@"save", FormalArgs([]), _, _), _) :-
    false | error $[Entity function with name [name] collides with a built-in function that you are not allowed to overwrite] @name.

  entityFunctionOk(s_ent, Function(@"delete", FormalArgs([]), _, _), _) :-
    false | error $[Entity function with name [name] collides with a built-in function that you are not allowed to overwrite] @name.

  // predicate that defines when a function can be cached
   : scope * Function
  canBeCached(s, Function(function_name, FormalArgs([]), OptSortSome(returnSort), _)) :- inequalType(typeOfSort(s, returnSort), VOID()) | error $[Only functions that have a return type can be cached] @function_name.
  canBeCached(s, Function(function_name, FormalArgs([_|_]), _, _)) :- false | error $[Only functions without arguments can be cached] @function_name.

  // declare a function in a given scope
  // and define the relation typeOfFunDecl with signature (argument types  * return type)
   : scope * string * ORIGIN * list(TYPE) * TYPE
  declFunctionGlobal(, , origin, , return) :-
    declareFunction(s, f, origin, args, return),
    noDuplicateFunDefsGlobal(s, f, args).

   : scope * string * ORIGIN * list(TYPE) * TYPE * BOOL
  declFunctionEntity(s, f, origin, args, return, TRUE()) :- declareStaticFunction(s, f, origin, args, return).
  declFunctionEntity(s, f, origin, args, return, FALSE()) :- declareFunction(s, f, origin, args, return).

  // map syntactic types to semantic types
   maps typeOfArg(*, list(*)) = list(*)
   : scope * FormalArg -> TYPE
  typeOfArg(s, Arg(_, )) =  :-
    t == typeOfSort(s, sort),
    inequalType(t, UNTYPED()) | error $[Unknown type [sort]] @sort.

  // create tuples of (arg_name * arg_type)
   maps zipArgType(list(*), list(*)) = list(*)
   : FormalArg * TYPE -> (string * TYPE)
  zipArgType(Arg(x, _), t) = (x, t).

  // predicate that defines when there are overlapping function signatures
   : scope * string * list(TYPE)
  noDuplicateFunDefsGlobal(s, , ) :- {  }
    resolveFunction(s, f) == ps,
    amountOfFunDeclsWithArgs(ps, ts, 0) == 1
        | error $[Function with name [f] and argument types [ts] is already defined] @f. // correct error message for tests

   : scope * string * list(TYPE)
  noDuplicateFunDefsEntity(s, , ) :- {  }
    query function filter EXTEND?
                   and { f' :- f' == (f, _, _) }
                   in s |-> ps,
    amountOfFunDeclsWithArgs(filterFunctionResults(typesOfStripOrigin(ps), FALSE()), types, 0) == 1
        | error $[Function with name [f] and argument types [types] is already defined] @f. // correct error message for tests

  // helper function for noDuplicateFunDefs that counts the amount of function with a given name and argument types
   : list((path * (string * TYPE))) * list(TYPE) * int -> int
  amountOfFunDeclsWithArgs([], _, n) = n.
  amountOfFunDeclsWithArgs([(_, (_, FUNCTION(_, types, _, _))) | tail], types, n) = amountOfFunDeclsWithArgs(tail, types, i) :- i #= n + 1.
  amountOfFunDeclsWithArgs([_ | tail], types, n) = amountOfFunDeclsWithArgs(tail, types, n).

rules  // rules and functions for function overloading

  // function that gets all functions/templates with matching name and compatible argument types
   : list((path * (string * TYPE))) * list(TYPE) -> list((string * TYPE))
  typeCompatibleSigs(nameCompatibleSigs, args) = result :- {}
    sigsZippedWithTypeCompatibility == zipSigsWithTypesCompatible(args, dropPaths(nameCompatibleSigs)),
    result == filterCompatibleArgTypes(sigsZippedWithTypeCompatibility).

  // helper function for typeCompatibleSigs that prunes the list
  // of functions/templates based on the zipped BOOL with the signature
   : list((BOOL * (string * TYPE))) -> list((string * TYPE))
  filterCompatibleArgTypes([]) = [].
  filterCompatibleArgTypes([(TRUE() , f) | fs]) = [f | filterCompatibleArgTypes(fs)].
  filterCompatibleArgTypes([(FALSE(), _) | fs]) = filterCompatibleArgTypes(fs).

  // helper function for typeCompatibleSigs that zips the
  // signatures with whether the types are compatible with given argument types
  zipSigsWithTypesCompatible maps zipSigWithTypesCompatible(*, list(*)) = list(*)
   : list(TYPE) * (string * TYPE) -> (BOOL * (string * TYPE))
  zipSigWithTypesCompatible(args, f@(_, FUNCTION(_, funArgs, _, _))) = (typesCompatible(args, funArgs), f).
  zipSigWithTypesCompatible(args, f@(_, TEMPLATE(_, templArgs, _))) = (typesCompatible(args, templArgs), f).

  // function that prunes the list of compatible signatures
  // to a list of most specific signatures
   : list(TYPE) * list((string * TYPE)) -> list((string * TYPE))
  mostSpecificSigs(args, []) = [].     // In case no functions are compatible, return empty list
  mostSpecificSigs(args, fs@[_]) = fs. // In case of only one compatible signature, return that
  mostSpecificSigs(args, sigs) = mostSpecificSigs_helper(args, sigs, matchingSigs(stripRefTypes(args), sigs)).

  // helper function for mostSpecificFunSigs that returns
  // the exactly matching signatures if they exists,
  // else return the most specific (least inheritance) signatures
   : list(TYPE) * list((string * TYPE)) * list((string * TYPE)) -> list((string * TYPE))
  mostSpecificSigs_helper(args, sigs, matching) = matching.
  mostSpecificSigs_helper(args, sigs, [])       = filterLeastInheritanceAmount(minOfList(inheritanceAmounts), zipInheritanceAmountWithSig(inheritanceAmounts, sigs)) :-
    inheritanceAmounts == inheritanceAmounts(args, sigs).

  // helper function for mostSpecificFunSigs that returns the exactly matching signatures
   : list(TYPE) * list((string * TYPE)) -> list((string * TYPE))
  matchingSigs(_, []) = [].
  matchingSigs(args, [(x, FUNCTION(f, params, rt, static)) | fs]) = matchingSigs_helper(args, (x, FUNCTION(f, stripRefTypes(params), rt, static)), fs).
  matchingSigs(args, [(x, TEMPLATE(t, params, ajax))       | fs]) = matchingSigs_helper(args, (x, TEMPLATE(t, stripRefTypes(params), ajax)), fs).

  // helper function for matchingSigs that compares the argument types after stripping the Ref<> sorts
   : list(TYPE) * (string * TYPE) * list((string * TYPE)) -> list((string * TYPE))
  matchingSigs_helper(args, f@(_, FUNCTION(_, args, _, _)), fs) = [f | matchingSigs(args, fs)].
  matchingSigs_helper(args, f@(_, TEMPLATE(_, args, _))   , fs) = [f | matchingSigs(args, fs)].
  matchingSigs_helper(args, _, fs) = matchingSigs(args, fs).

  // function that computes the total amount of inheritance edges from caller arguments to defined arguments
  inheritanceAmounts maps inheritanceAmount(*, list(*)) = list(*)
   : list(TYPE) * (string * TYPE) -> int
  inheritanceAmount(args, (_, FUNCTION(_, sigTypes, _, _))) = inheritanceAmount_helper(args, sigTypes).
  inheritanceAmount(args, (_, TEMPLATE(_, sigTypes, _)))    = inheritanceAmount_helper(args, sigTypes).

  // helper function for getInheritanceAmount that computers the total amount of inheritance edges
   : list(TYPE) * list(TYPE) -> int
  inheritanceAmount_helper([], []) = 0.
  inheritanceAmount_helper([argT | argtl], [sigT | sigtl]) = x :- {   y z }
    s_arg == scopeFromType(argT),
    s_sig == scopeFromType(sigT),
    y == inheritEdgesAmount(s_arg, s_sig),
    z == inheritanceAmount_helper(argtl, sigtl),
    x #= y + z.

  // in case of two built-in types, the same built-in type is +0, two compatible (but not equal) types is +1
  inheritanceAmount_helper([arg@BUILTINTYPE(t, _) | argtl], [sig@BUILTINTYPE(t, _) | sigtl]) = x :-
    x == inheritanceAmount_helper(argtl, sigtl).

  inheritanceAmount_helper([@BUILTINTYPE(_, _) | argtl], [@BUILTINTYPE(_, _) | sigtl]) = x :- { y }
    typeCompatible(arg, sig) | error $[Argument type [arg] is not compatible with signature type [sig]],
    y == inheritanceAmount_helper(argtl, sigtl),
    x #= y + 1.

  // in case of two template variable arguments, +0 for exactly the same types, +1 for different types
  inheritanceAmount_helper([arg@TEMPLATEVARARG(ts, _) | argtl], [sig@TEMPLATEVARARG(ts, _) | sigtl]) = x :-
    x == inheritanceAmount_helper(argtl, sigtl).

  inheritanceAmount_helper([@TEMPLATEVARARG(_, _) | argtl], [@TEMPLATEVARARG(_, _) | sigtl]) = x :- { y }
    typeCompatible(arg, sig) | error $[Argument type [arg] is not compatible with signature type [sig]],
    y == inheritanceAmount_helper(argtl, sigtl),
    x #= y + 1.

   : TYPE -> scope
  scopeFromType(REF(t)) = scopeFromType(t).
  scopeFromType(LIST(t)) = scopeFromType(t).
  scopeFromType(SET(t)) = scopeFromType(t).
  scopeFromType(ENTITY(_, s)) = s.
  scopeFromType(NATIVECLASS(_, s)) = s.
  scopeFromType(BUILTINTYPE(_, s)) = s.

  // function that zips the inheritance amount with the signature
   : list(int) * list((string * TYPE)) -> list((int * (string * TYPE)))
  zipInheritanceAmountWithSig([], []) = [].
  zipInheritanceAmountWithSig([x|xs], [y|ys]) = [(x, y) | zipInheritanceAmountWithSig(xs, ys)].

  // function that prunes the signatures to only keep the lowest inheritance amount
   : int * list((int * (string * TYPE))) -> list((string * TYPE))
  filterLeastInheritanceAmount(_, []) = [].
  filterLeastInheritanceAmount(x, [(x, sig) | sigtl]) = [sig | filterLeastInheritanceAmount(x, sigtl)].
  filterLeastInheritanceAmount(x, [_        | sigtl]) = filterLeastInheritanceAmount(x, sigtl).

   maps dropPath(list(*)) = list(*)
   : (path * (string * TYPE)) -> (string * TYPE)
  dropPath((_, x)) = x.