001/* 002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others 003 * 004 * This program and the accompanying materials are made available under the 005 * terms of the Eclipse Public License 2.0 which is available at 006 * http://www.eclipse.org/legal/epl-2.0. 007 * 008 * SPDX-License-Identifier: EPL-2.0 009 */ 010 011package gololang.ir; 012 013import java.lang.invoke.MethodType; 014import java.util.LinkedList; 015import java.util.List; 016import java.util.HashSet; 017import java.util.Set; 018import java.util.Collection; 019import java.util.Objects; 020 021import org.eclipse.golo.compiler.SymbolGenerator; 022 023import static java.util.Collections.unmodifiableList; 024import static java.util.Arrays.asList; 025 026/** 027 * Represents a function declaration. 028 * 029 * <p>Such as 030 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 031 * function foo = |x| -> x + 1 032 * </code></pre> 033 */ 034public final class GoloFunction extends ExpressionStatement<GoloFunction> implements BlockContainer<GoloFunction>, ToplevelGoloElement, NamedElement { 035 036 private static final SymbolGenerator SYMBOLS = new SymbolGenerator("golo.ir.function"); 037 038 private String name; 039 private boolean isLocal = false; 040 private Scope scope = Scope.MODULE; 041 042 private final List<String> parameterNames = new LinkedList<>(); 043 private final List<String> syntheticParameterNames = new LinkedList<>(); 044 private boolean varargs = false; 045 private Block block; 046 private boolean synthetic = false; 047 private boolean decorator = false; 048 private boolean macro = false; 049 private boolean special = false; 050 private boolean contextual = false; 051 private String syntheticSelfName = null; 052 private String decoratorRef = null; 053 private final LinkedList<Decorator> decorators = new LinkedList<>(); 054 055 public enum Scope { 056 MODULE, AUGMENT, CLOSURE 057 } 058 059 protected GoloFunction self() { return this; } 060 061 private GoloFunction() { 062 super(); 063 this.block(Block.empty()); 064 } 065 066 /** 067 * Copy constructor. 068 * <p> 069 * make a partial copy of the given function: the properties are copied, but children node are not 070 */ 071 private GoloFunction(GoloFunction function) { 072 this.name = function.name; 073 this.isLocal = function.isLocal; 074 this.scope = function.scope; 075 this.varargs = function.varargs; 076 this.synthetic = function.synthetic; 077 this.decorator = function.decorator; 078 this.macro = function.macro; 079 this.special = function.special; 080 this.contextual = function.contextual; 081 this.syntheticSelfName = function.syntheticSelfName; 082 this.decoratorRef = function.decoratorRef; 083 this.parameterNames.addAll(function.parameterNames); 084 this.syntheticParameterNames.addAll(function.syntheticParameterNames); 085 this.documentation(function.documentation()); 086 this.positionInSourceCode(function.positionInSourceCode()); 087 } 088 089 /** 090 * Creates a function declaration. 091 * 092 * <p>Typical usage: 093 * <pre class="listing"><code class="lang-java" data-lang="java"> 094 * function("foo").withParameters("x").returns(plus(ReferenceLookup.of("x"), constant(1))) 095 * </code></pre> 096 * creates 097 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 098 * function foo = |x| -> x + 1 099 * </code></pre> 100 */ 101 public static GoloFunction function(Object name) { 102 if (name instanceof GoloFunction) { 103 return new GoloFunction((GoloFunction) name); 104 } 105 if (name == null) { 106 return new GoloFunction(); 107 } 108 return new GoloFunction().name(name.toString()); 109 } 110 111 // name ----------------------------------------------------------------------------------------- 112 public GoloFunction name(String n) { 113 this.name = n; 114 return this; 115 } 116 117 @Override 118 public String getName() { 119 return name; 120 } 121 122 public boolean isMain() { 123 return "main".equals(name) && getArity() == 1; 124 } 125 126 public boolean isModuleInit() { 127 return GoloModule.MODULE_INITIALIZER_FUNCTION.equals(this.name); 128 } 129 130 public boolean isAnonymous() { 131 return this.name == null; 132 } 133 134 // synthetic ------------------------------------------------------------------------------------ 135 public GoloFunction synthetic(boolean s) { 136 this.synthetic = s; 137 return this; 138 } 139 140 public GoloFunction synthetic() { 141 return synthetic(true); 142 } 143 144 public boolean isSynthetic() { 145 return synthetic; 146 } 147 148 // decorator ------------------------------------------------------------------------------------ 149 public GoloFunction decorator() { 150 return decorator(true); 151 } 152 153 public GoloFunction decorator(boolean d) { 154 this.decorator = d; 155 return this; 156 } 157 158 public boolean isDecorator() { 159 return decorator; 160 } 161 162 // visibility ----------------------------------------------------------------------------------- 163 public GoloFunction local() { 164 return local(true); 165 } 166 167 public GoloFunction local(boolean isLocal) { 168 this.isLocal = isLocal; 169 return this; 170 } 171 172 public boolean isLocal() { 173 return this.isLocal; 174 } 175 176 // scope ---------------------------------------------------------------------------------------- 177 public GoloFunction inScope(Scope s) { 178 this.scope = s; 179 return this; 180 } 181 182 public GoloFunction inAugment() { 183 return inAugment(true); 184 } 185 186 public GoloFunction inAugment(boolean isInAugment) { 187 if (isInAugment) { 188 this.scope = Scope.AUGMENT; 189 } 190 return this; 191 } 192 193 public boolean isInAugment() { 194 return Scope.AUGMENT.equals(scope); 195 } 196 197 public boolean isInModule() { 198 return Scope.MODULE.equals(scope); 199 } 200 201 public GoloFunction asClosure() { 202 this.scope = Scope.CLOSURE; 203 return this; 204 } 205 206 // block ---------------------------------------------------------------------------------------- 207 /** 208 * {@inheritDoc} 209 */ 210 @Override 211 public GoloFunction block(Object block) { 212 this.block = makeParentOf(Block.of(block)); 213 for (String param : parameterNames) { 214 addParameterToBlockReferences(param); 215 } 216 return this; 217 } 218 219 /** 220 * {@inheritDoc} 221 */ 222 @Override 223 public Block getBlock() { 224 return block; 225 } 226 227 public GoloFunction returns(Object expression) { 228 this.block.add(ReturnStatement.of(expression)); 229 return this; 230 } 231 232 public void insertMissingReturnStatement() { 233 if (!this.block.hasReturn() && !this.isModuleInit()) { 234 if (this.isMain()) { 235 this.block.add(ReturnStatement.empty().synthetic()); 236 } else { 237 this.block.add(ReturnStatement.of(null).synthetic()); 238 } 239 } 240 } 241 242 // parameters and varargs ----------------------------------------------------------------------- 243 public GoloFunction varargs(boolean isVarargs) { 244 this.varargs = isVarargs; 245 return this; 246 } 247 248 public GoloFunction varargs() { 249 return this.varargs(true); 250 } 251 252 public boolean isVarargs() { 253 return varargs; 254 } 255 256 public int getArity() { 257 return parameterNames.size() + syntheticParameterNames.size(); 258 } 259 260 public GoloFunction withParameters(Object... names) { 261 return withParameters(asList(names)); 262 } 263 264 public GoloFunction withParameters(Collection<?> names) { 265 for (Object name : names) { 266 addParameterToBlockReferences(name.toString()); 267 this.parameterNames.add(name.toString()); 268 } 269 return this; 270 } 271 272 private void addParameterToBlockReferences(String name) { 273 this.block.getReferenceTable().add(LocalReference.of(name)); 274 } 275 276 public int getSyntheticParameterCount() { 277 return syntheticParameterNames.size(); 278 } 279 280 public List<String> getParameterNames() { 281 LinkedList<String> list = new LinkedList<>(syntheticParameterNames); 282 list.addAll(parameterNames); 283 return list; 284 } 285 286 public Collection<String> getSyntheticParameterNames() { 287 return unmodifiableList(syntheticParameterNames); 288 } 289 290 public void addSyntheticParameters(Set<String> names) { 291 Set<String> existing = new HashSet<>(getParameterNames()); 292 for (String name : names) { 293 if (!existing.contains(name) && !name.equals(syntheticSelfName)) { 294 LocalReference ref = block.getReferenceTable().get(name); 295 if (ref == null || !ref.isModuleState()) { 296 this.syntheticParameterNames.add(name); 297 } 298 } 299 } 300 } 301 302 public String getSyntheticSelfName() { 303 return syntheticSelfName; 304 } 305 306 public LocalReference getSyntheticSelfReference() { 307 return block.getReferenceTable().get(syntheticSelfName); 308 } 309 310 public void setSyntheticSelfName(String name) { 311 if (syntheticParameterNames.contains(name)) { 312 this.syntheticParameterNames.remove(name); 313 this.syntheticSelfName = name; 314 } 315 } 316 317 public void captureClosedReference() { 318 if (synthetic && syntheticSelfName != null) { 319 block.prepend(AssignmentStatement.create(getSyntheticSelfReference(), asClosureReference(), true)); 320 } 321 } 322 323 // macro ---------------------------------------------------------------------------------------- 324 public GoloFunction asMacro(boolean value) { 325 this.macro = value; 326 return this; 327 } 328 329 public GoloFunction asMacro() { 330 return asMacro(true); 331 } 332 333 public boolean isMacro() { 334 return this.macro; 335 } 336 337 public boolean isSpecialMacro() { 338 return this.special; 339 } 340 341 public GoloFunction special(boolean value) { 342 this.special = value; 343 return this; 344 } 345 346 public boolean isContextualMacro() { 347 return this.contextual; 348 } 349 350 public GoloFunction contextual(boolean value) { 351 this.contextual = value; 352 return this; 353 } 354 355 // decorators ----------------------------------------------------------------------------------- 356 /** 357 * Adds decorators to this function. 358 * 359 * <p>The objects are converted into a decorator if needed. 360 */ 361 public GoloFunction decoratedWith(Object... decorators) { 362 for (Object deco : decorators) { 363 this.addDecorator(Decorator.of(deco)); 364 } 365 return this; 366 } 367 368 public boolean isDecorated() { 369 return !decorators.isEmpty(); 370 } 371 372 public String getDecoratorRef() { 373 return decoratorRef; 374 } 375 376 public void addDecorator(Decorator decorator) { 377 this.decorators.add(makeParentOf(decorator)); 378 } 379 380 public void removeDecorator(Decorator decorator) { 381 this.decorators.remove(decorator); 382 } 383 384 public List<Decorator> getDecorators() { 385 return unmodifiableList(decorators); 386 } 387 388 public GoloFunction createDecorator() { 389 ExpressionStatement<?> expr = ReferenceLookup.of("__$$_original"); 390 for (Decorator decorator : this.getDecorators()) { 391 expr = decorator.wrapExpression(expr); 392 } 393 this.decoratorRef = SYMBOLS.next(name + "_decorator"); 394 return function(this.decoratorRef) 395 .decorator() 396 .inScope(this.scope) 397 .withParameters("__$$_original") 398 .returns(expr); 399 } 400 401 //----------------------------------------------------------------------------------------------- 402 public ClosureReference asClosureReference() { 403 if (scope != Scope.CLOSURE) { 404 throw new IllegalStateException("Can't get a closure reference of a non-closure function"); 405 } 406 return new ClosureReference(this); 407 } 408 409 410 /** 411 * Return a new function with the same signature as this one. 412 * <p> 413 * Can be used to generate a wrapper for a given function for instance. 414 */ 415 public GoloFunction sameSignature() { 416 return function(name) 417 .local(isLocal) 418 .inScope(scope) 419 .withParameters(parameterNames) 420 .varargs(varargs) 421 .synthetic(true); 422 } 423 424 /** 425 * Return the parameters of this function wrapped in a {@code ReferenceLookup}. 426 */ 427 public Object[] parametersAsRefs() { 428 return parameterNames.stream().map(ReferenceLookup::of).toArray(); 429 } 430 431 @Override 432 public String toString() { 433 return String.format("Function{name=%s, arity=%d, vararg=%s, synthetic=%s, self=%s, macro=%s}", 434 getName(), 435 getArity(), 436 isVarargs(), 437 synthetic, 438 syntheticSelfName, 439 isMacro()); 440 } 441 442 @Override 443 public void accept(GoloIrVisitor visitor) { 444 visitor.visitFunction(this); 445 } 446 447 @Override 448 public List<GoloElement<?>> children() { 449 LinkedList<GoloElement<?>> children = new LinkedList<>(decorators); 450 children.add(block); 451 return children; 452 } 453 454 @Override 455 protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) { 456 throw cantReplace(); 457 } 458 459 @Override 460 public int hashCode() { 461 return Objects.hash(this.name, this.getArity(), this.varargs); 462 } 463 464 @Override 465 public boolean equals(Object obj) { 466 if (obj == this) { 467 return true; 468 } 469 if (obj == null) { 470 return false; 471 } 472 if (getClass() != obj.getClass()) { 473 return false; 474 } 475 final GoloFunction other = (GoloFunction) obj; 476 if (!Objects.equals(this.name, other.name)) { 477 return false; 478 } 479 if (this.varargs != other.varargs) { 480 return false; 481 } 482 return this.getArity() == other.getArity(); 483 } 484 485 public MethodType getMethodType() { 486 if (this.isMain()) { 487 return MethodType.methodType(void.class, String[].class); 488 } 489 if (this.isModuleInit()) { 490 return MethodType.methodType(void.class); 491 } 492 if (this.isMacro()) { 493 MethodType signature = MethodType.methodType(GoloElement.class); 494 int arity = this.getArity() - (this.isVarargs() ? 1 : 0); 495 int i = 0; 496 if (this.isContextualMacro()) { 497 signature = signature.appendParameterTypes(gololang.ir.AbstractInvocation.class); 498 i++; 499 } 500 if (this.isSpecialMacro()) { 501 signature = signature.appendParameterTypes(org.eclipse.golo.compiler.macro.MacroExpansionIrVisitor.class); 502 i++; 503 } 504 while (i < arity) { 505 signature = signature.appendParameterTypes(GoloElement.class); 506 i++; 507 } 508 if (this.isVarargs()) { 509 signature = signature.appendParameterTypes(GoloElement[].class); 510 } 511 return signature; 512 } 513 if (this.isVarargs()) { 514 return MethodType.genericMethodType(this.getArity() - 1, true); 515 } 516 return MethodType.genericMethodType(this.getArity()); 517 } 518}