GithubHelp home page GithubHelp logo

max-leuthaeuser / scroll Goto Github PK

View Code? Open in Web Editor NEW
26.0 4.0 12.0 5.49 MB

SCROLL - SCala ROLes Language (A DSL based on Scala for role-based programming and dispatch)

License: GNU Lesser General Public License v3.0

Scala 100.00%
scala dispatch dsl meta-programming role-oriented roles

scroll's Introduction

SCROLL

SCala ROLes Language

GH Action Scala Steward badge

Introduction

SCROLL is an embedded method-call interception domain-specific language (DSL) tailored to the features needed to implement roles and resolve the ambiguities arising with regard to dynamic dispatch. The library approach together with an implementation with Scala was chosen for mainly the following reasons: it allows focusing on role semantics, supports a customizable, dynamic dispatch at runtime, and allows for a terse, flexible representation. No additional tooling (like a custom lexer, parser or compiler) is needed to execute the SCROLL meta-object protocol (MOP). It is purely embedded in the host language, thus uses the standard Scala compiler to generate Java Virtual Machine bytecode. With that, the implementation is reasonable small (โˆผ1400 lines of code) and maintainable. The programming interface with Scala's exible syntax holds the property of being easily readable, even to inexperienced users.

See the wiki for further information.

Basic Implementation Concepts

Basics

To provide a DSL for the pure embedding of roles in structured contexts, SCROLL requires the basic implementation concepts from the host language shown in the image above:

  • Compiler rewrites: A concept for compiler rewrites for method calls, functions calls, and attribute access is required. It hands over calls to the library for nding behavior and structure that is not natively available at the player. This can be seen as a compiler-supported variant of method-call interception.

  • Implicit conversions: For aggregating the compound object from the core and its roles, and for exposing the SCROLL MOP API, implicit conversions are needed. An implicit conversion from type S to type T is defined by an implicit value which has the function type S => T, or by an implicit method convertible to a value of that type. Implicit conversions are applied in two situations: i) If an expression e is of type S, and S does not conform to the expression's expected type T, and ii) in a selection e.m with e of type S, if the selector m does not denote a member of S. In the first case, a conversion c is searched for which is applicable to e and whose result type conforms to T. In the second case, a conversion c is searched for which is applicable to e and whose result contains a member named m.

  • Definition table for the plays relationship: The relationships between each individual player and its roles need to be stored. A definition table holds all kinds of program components, whose attributes are created by declaration: types, variables, methods, functions, and parameters. In SCROLL, a definition table for roles is implemented with a graph-based data structure, but it may be implemented with tables, maps, or lists as well.

Example

// A Natural type, the player:
class Person(val firstName: String)

val peter = new Person("Peter")

// A new context, a Compartment:
new Compartment {
  // A Role type, here used as dynamic extension:
  class PersonExtension(val lastName: String) {
    def fullName(): String = {
      // the +-operator used as base call:
      val first: String = +this firstName
      val last: String = lastName
      first + " " + last
    }
  }

  val name: String = peter play new PersonExtension("Meier") fullName()
  println(name)
}

A more elaborated example can be found here and here.

You can find even more examples here.

You also might want to check the tests.

Edit and develop

See the developer wiki for further information.

Use the library

See the user wiki for further information.

Publications

Dissertation

scroll's People

Contributors

chrissi007 avatar lschuetze avatar martinmo avatar max-leuthaeuser avatar scala-steward avatar tklgetud avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

scroll's Issues

Update Wiki

- [ ] Add pointer to other SCROLL related repositories (see comment below)

  • Add known issues for project maintenance (dependencies, etc.)
  • Enhance user guide (relationships, role-constraints, role-groups, etc.)

Enhance the documentation

The ReadMe file only describes how to run the project, it doesn't describe the structure/ideas of the project.

Bind clause in Context does not accept non Unit returning code blocks

the Bind clause in Context does not accept non Unit returning code blocks.
Fails like this:

Exception in thread "main" java.lang.NullPointerException
    at examples.BankExample$$anon$1$$anonfun$1.apply(BankExample.scala:123)
    at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at internal.Context$class.Bind(Context.scala:30)
    at examples.BankExample$Bank.Bind(BankExample.scala:47)
    at examples.BankExample$$anon$1.<init>(BankExample.scala:123)
    at examples.BankExample$.delayedEndpoint$examples$BankExample$1(BankExample.scala:119)
    at examples.BankExample$delayedInit$body.apply(BankExample.scala:9)
    at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.collection.immutable.List.foreach(List.scala:381)
    at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
    at scala.App$class.main(App.scala:76)
    at examples.BankExample$.main(BankExample.scala:9)
    at examples.BankExample.main(BankExample.scala)
``

isPlaying-function can not be called on a non-role playing natural object

Calling the function "isPlaying" on a newly created object that does not play any roles causes an IllegalArgumentException. A workaround would be to bind an arbitrary role immediately after creating the object.

Example:
var p1 = new Person("Ferdinand")
(+p1).isPlaying[PseudoRole]
-->IllegalArgumentException: Node simplePseudonymity.Person@1c6c3b2 is not an element of this graph.

Exceptions in role methods are silently discarded

Consider the following example (also available at https://github.com/martinmo/SCROLL/tree/scroll-problems):

object SwallowedException extends App {
  class CoreType

  class ExceptionShowcase extends Compartment {
    class Exceptional {
      def roleMethod: Unit = {
        println("Exceptional::roleMethod()")
        throw new Exception("catch me if you can")
      }
    }
  }

  new ExceptionShowcase() {
    val core = new CoreType()
    core play new Exceptional()
    (+core).roleMethod()
    throw new AssertionError("should not be reached")
  }
}

Expected output:

Exceptional::roleMethod()
java.lang.Exception: catch me if you can
	at <...>
	at scroll.examples.SwallowedException.main(SwallowedException.scala)

Actual output:

Exceptional::roleMethod()
java.lang.AssertionError: should not be reached
	at scroll.examples.SwallowedException$$anon$1.<init>(SwallowedException.scala:21)
	at scroll.examples.SwallowedException$.<init>(SwallowedException.scala:17)
	at scroll.examples.SwallowedException$.<clinit>(SwallowedException.scala)
	at scroll.examples.SwallowedException.main(SwallowedException.scala)

Another example is the CheckingsAccount role in the Banking example, which throws an IllegalArgumentException that gets also silently swallowed.

Remove erasure warning (2)

Remove the following erasure warning:

[warn] RoleDispatch/src/main/scala/internal/ReflectiveHelper.scala:25: abstract type T is unchecked since it is eliminated by erasure
[warn]     def is[T]: Boolean = cur.isInstanceOf[T]
[warn]                                          ^

How can I dispatch base calls with multiple roles without infinite recursion?

I have the following example:

object MultipleRoles extends App {
  class CoreType {
    def someMethod(): Unit = {
      println(s"CoreType($this)::someMethod()")
    }
  }

  class MultiRole extends Compartment {
    class RoleTypeA {
      implicit val dd = Bypassing(_.equals(this)) // ???
      def someMethod(): Unit = {
        println(s"RoleTypeA($this)::someMethod()")
        (+this).someMethod()
      }
    }
    class RoleTypeB {
      implicit val dd = Bypassing(_.equals(this)) // ???
      def someMethod(): Unit = {
        println(s"RoleTypeB($this)::someMethod()")
        (+this).someMethod()
      }
    }
  }

  new MultiRole() {
    val player = new CoreType() play new RoleTypeA()
    player.someMethod()
    println("---")

    val anotherPlayer = new CoreType() play new RoleTypeA() play new RoleTypeB()
    // implicit val dd = ???
    anotherPlayer.someMethod()
    println("---")
  }
}

What I want to achieve is that the base calls "proceed" to the next role, i.e., my desired output is:

RoleTypeA(scroll.examples.MultipleRoles$MultiRole$RoleTypeA@2eb70f3c)::someMethod()
CoreType(scroll.examples.MultipleRoles$CoreType@33f4f5bc)::someMethod()
---
RoleTypeA(scroll.examples.MultipleRoles$MultiRole$RoleTypeA@63adcfdc)::someMethod()
RoleTypeB(scroll.examples.MultipleRoles$MultiRole$RoleTypeB@6174a968)::someMethod()
CoreType(scroll.examples.MultipleRoles$CoreType@<...>)::someMethod()
---

But currently, basically all my trials defining a corresponding dispatch query lead to infinite recursion and the example produces the following output:

RoleTypeA(scroll.examples.MultipleRoles$MultiRole$RoleTypeA@2eb70f3c)::someMethod()
CoreType(scroll.examples.MultipleRoles$CoreType@33f4f5bc)::someMethod()
---
RoleTypeA(scroll.examples.MultipleRoles$MultiRole$RoleTypeA@63adcfdc)::someMethod()
RoleTypeB(scroll.examples.MultipleRoles$MultiRole$RoleTypeB@6174a968)::someMethod()
RoleTypeA(scroll.examples.MultipleRoles$MultiRole$RoleTypeA@63adcfdc)::someMethod()
RoleTypeB(scroll.examples.MultipleRoles$MultiRole$RoleTypeB@6174a968)::someMethod()
RoleTypeA(scroll.examples.MultipleRoles$MultiRole$RoleTypeA@63adcfdc)::someMethod()
RoleTypeB(scroll.examples.MultipleRoles$MultiRole$RoleTypeB@6174a968)::someMethod()
[...]
RoleTypeA(scroll.examples.MultipleRoles$MultiRole$RoleTypeA@63adcfdc)::someMethod()
RoleTypeB(scroll.examples.MultipleRoles$MultiRole$RoleTypeB@6174a968)::someMethod()
RoleTypeA(scroll.examples.MultipleRoles$MultiRole$RoleTypeA@63adcfdc)::someMethod()
---

(Btw, there seems to be a StackOverflowError that is silently discarded by the dispatcher, otherwise I cannot explain why --- is printed at the end. See also #18.)

How can I achieve the desired output? Is that even possible? So far, I have only found a way that involves hard-coding RoleTypeA in the dispatch query of RoleTypeB:

// in RoleTypeB:
implicit val dd = Bypassing((o: AnyRef) => {
  o.equals(this) || o.isInstanceOf[RoleTypeA]
})
// before the call anotherPlayer.roleMethod():
implicit val dd = From(_.isInstanceOf[RoleTypeA]).To(_.isInstanceOf[RoleTypeB])

Support for implicit and explicit contexts

Add support for implicit and explicit contexts, i.e.:

  • implicit: gets activated as soon as the control flow reaches the piece of code, maybe it can be guarded with conditions,
  • explicit: gets activated by calling a function (like run or ! or something).

Role method on IPlayer returns scala.util.Right instead unwrapped type

Describe the bug
Calling the method of a role on an IPlayer returns the value wrapped in scala.util.Right instead the wrapped value.

To Reproduce

import scroll.internal._
import scroll.internal.support.DispatchQuery
import DispatchQuery._

class Bank(name : String) extends Compartment {
  class Customer(name : String, id : String) {
    def name() : String = name
  }

  def addCustomer(p : Person) {
    p play(new Customer("C1234", "1234"))
  }
}

class Person(name: String, age : Int) {
  def name() : String = name
}

object Test1 extends App {
  val p = new Person("Max", 18)
  println("Name = " + p.name())

  val b = new Bank("TestBank")
  b.addCustomer(p)

  val playerP = b.newPlayer(p)

  println((+playerP).name().getClass.getName)

  println("Name = " + (+playerP).name())
}

Output:

Name = Max
scala.util.Right
Name = Right(C1234)

Expected behavior
Output:

Name = Max
java.util.String
Name = C1234

Console output / Test output
Nothing to see here.

Environment (please complete the following information):
Used the bootstrap build.sbt file and sbt run.

Additional context
Nothing to see here.

Using role inheritance fails

Using role inheritance fails currently with, e.g. message like this:

Exception in thread "main" java.lang.ClassCastException: Cannot cast examples.BankExample$Account to examples.BankExample$Bank$Decreasable
    at java.lang.Class.cast(Class.java:3094)
    at internal.Compartment$DispatchType$$anonfun$2.apply(Compartment.scala:101)
    at internal.Compartment$DispatchType$$anonfun$2.apply(Compartment.scala:93)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:245)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:245)
    at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:59)
    at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:48)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:245)
    at scala.collection.AbstractTraversable.map(Traversable.scala:104)
    at internal.Compartment$DispatchType$class.dispatch(Compartment.scala:93)
    at internal.Compartment$PlayerType.dispatch(Compartment.scala:160)
    at internal.Compartment$PlayerType$$anonfun$applyDynamic$2$$anonfun$apply$4.apply(Compartment.scala:192)
    at internal.Compartment$PlayerType$$anonfun$applyDynamic$2$$anonfun$apply$4.apply(Compartment.scala:189)
    at scala.Option.foreach(Option.scala:256)
    at internal.Compartment$PlayerType$$anonfun$applyDynamic$2.apply(Compartment.scala:189)
    at internal.Compartment$PlayerType$$anonfun$applyDynamic$2.apply(Compartment.scala:188)
    at scala.collection.Iterator$class.foreach(Iterator.scala:743)
    at scala.collection.AbstractIterator.foreach(Iterator.scala:1195)
    at scala.collection.IterableLike$class.foreach(IterableLike.scala:72)
    at scala.collection.AbstractIterable.foreach(Iterable.scala:54)
    at internal.Compartment$PlayerType.applyDynamic(Compartment.scala:188)
    at examples.BankExample$$anon$1$$anonfun$1.apply$mcV$sp(BankExample.scala:109)
    at internal.Context$class.Bind(Context.scala:34)
    at examples.BankExample$Bank.Bind(BankExample.scala:27)
    at examples.BankExample$$anon$1.<init>(BankExample.scala:107)
    at examples.BankExample$.delayedEndpoint$examples$BankExample$1(BankExample.scala:103)
    at examples.BankExample$delayedInit$body.apply(BankExample.scala:9)
    at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.collection.immutable.List.foreach(List.scala:381)
    at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
    at scala.App$class.main(App.scala:76)
    at examples.BankExample$.main(BankExample.scala:9)
    at examples.BankExample.main(BankExample.scala)

Here, both CheckingsAccount and SavingsAccount inherit from the abstract base classDecreasable```, which is itself not a role.

Remove erasure warning (1)

Remove the following erasure warning:

[warn] RoleDispatch/src/main/scala/internal/Compartment.scala:114: abstract type pattern A is unchecked since it is eliminated by erasure
[warn]         case (arg: A, tpe: Class[_]) => tpe.cast(arg)
[warn]                    ^

Replace reflective access of role-specific behavior / structure with MethodHandles

Replace all reflective access of role-specific behavior / structure in ReflectiveHelper.scala with MethodHandels.

Hence, we should do:

  • replace caches using java.lang.reflect.{Field, Method} with ones storing MethodHandles

  • use MethodHandle findVirtual(Class<?> refc, String name, MethodType type), MethodHandle findGetter(Class<?> refc, String name, Class<?> type), and MethodHandle findSetter(Class<?> refc, String name, Class<?> type) instead of getDeclaredMethods, getDeclaredFields, getAccessibleMethods, and getAccessibleFields

  • remove unnecessary code, e.g., def matchMethod[A](m: Method, name: String, args: Seq[A]): Boolean or def hasMember(on: AnyRef, name: String): Boolean since this could be handled by the MethodHandles API directly.

  • finally, call role-specific functionally with MethodHandle.invoke(Object... args)

Remove comparison warnings

Remove the following comparison warning from the test suite:

[warn] RoleDispatch/src/test/scala/EqualityRoleSpec.scala:78: this.RoleA and this.RoleB are unrelated: they will most likely always compare unequal
[warn]         assert(someRole != someOtherRole)
[warn]               ^

and

[warn] RoleDispatch/src/test/scala/EqualityRoleSpec.scala:79: this.RoleB and this.RoleA are unrelated: they will most likely always compare unequal
[warn]         assert(someOtherRole != someRole)
[warn]               ^

Update gen-idea info for IntelliJ > 13

Like in your repository SCROLLdemo you can update the info for this repository:

"Execute SBT and run gen-idea if you are using Intellij IDE <= 13 (to config see here). This is not required anymore since Intellij 14. Just use the built-in import SBT project functionality." - from SCROLLdemo

NullPointerException when passing null to role-function

When argument null is passed to a function which is implemented in a role-object, the dispatch-process can not be completed and throws a NullPointerException.

Example:
var p1 = new Person("Ferdinand")
var r1 = new PseudoRole("F1")
p1 play r1
(+p1).setMyName(null)
--> NullPointerException

Exception in thread "main" java.lang.NullPointerException
at scroll.internal.util.ReflectiveHelper$.$anonfun$matchMethod$1(ReflectiveHelper.scala:130)
at scroll.internal.util.ReflectiveHelper$.$anonfun$matchMethod$1$adapted(ReflectiveHelper.scala:120)
at scala.collection.IndexedSeqOptimized.prefixLengthImpl(IndexedSeqOptimized.scala:37)
at scala.collection.IndexedSeqOptimized.forall(IndexedSeqOptimized.scala:42)
at scala.collection.IndexedSeqOptimized.forall$(IndexedSeqOptimized.scala:42)
at scala.collection.mutable.ArrayBuffer.forall(ArrayBuffer.scala:48)
at scroll.internal.util.ReflectiveHelper$.matchArgTypes$lzycompute$1(ReflectiveHelper.scala:120)
at scroll.internal.util.ReflectiveHelper$.matchArgTypes$1(ReflectiveHelper.scala:120)
at scroll.internal.util.ReflectiveHelper$.matchMethod(ReflectiveHelper.scala:134)
at scroll.internal.util.ReflectiveHelper$.$anonfun$findMethod$1(ReflectiveHelper.scala:156)
at scroll.internal.util.ReflectiveHelper$.$anonfun$findMethod$1$adapted(ReflectiveHelper.scala:156)
at scala.collection.Iterator.find(Iterator.scala:981)
at scala.collection.Iterator.find$(Iterator.scala:978)
at scala.collection.AbstractIterator.find(Iterator.scala:1417)
at scala.collection.IterableLike.find(IterableLike.scala:78)
at scala.collection.IterableLike.find$(IterableLike.scala:77)
at scala.collection.AbstractIterable.find(Iterable.scala:54)
at scroll.internal.util.ReflectiveHelper$.findMethod(ReflectiveHelper.scala:156)
at scroll.internal.Compartment$Player.$anonfun$applyDynamic$1(Compartment.scala:435)
at scroll.internal.Compartment$Player.$anonfun$applyDynamic$1$adapted(Compartment.scala:434)
at scala.collection.immutable.List.foreach(List.scala:389)
at scroll.internal.Compartment$Player.applyDynamic(Compartment.scala:434)
at simplePseudonymity.PseudoApp$$anon$1.(PseudoApp.scala:39)
at simplePseudonymity.PseudoApp$.delayedEndpoint$simplePseudonymity$PseudoApp$1(PseudoApp.scala:29)
at simplePseudonymity.PseudoApp$delayedInit$body.apply(PseudoApp.scala:23)
at scala.Function0.apply$mcV$sp(Function0.scala:34)
at scala.Function0.apply$mcV$sp$(Function0.scala:34)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App.$anonfun$main$1$adapted(App.scala:76)
at scala.collection.immutable.List.foreach(List.scala:389)
at scala.App.main(App.scala:76)
at scala.App.main$(App.scala:74)
at simplePseudonymity.PseudoApp$.main(PseudoApp.scala:23)
at simplePseudonymity.PseudoApp.main(PseudoApp.scala)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.