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