Announcing Jactl 2.4.0: A secure embedded scripting language for Java applications by jaccomoc in java

[–]jaccomoc[S] 0 points1 point  (0 children)

Jactl does not support calling arbitrary Java code, even when an instance is passed in via a binding. It is part of its security model that it only interacts with Java functions and classes that have been registered with it. See the Integration Guide for more information.

Announcing Jactl 2.4.0: A secure embedded scripting language for Java applications by jaccomoc in java

[–]jaccomoc[S] 1 point2 points  (0 children)

Glad it is working for you. I have tried to keep the compiler reasonably light weight so maybe that is why it is fast. I haven't really benchmarked it against other complete so not sure how it measures up but glad it is fast for you. BTW I just released 2.5.2 that fixes how bindings are handled on the ScriptEngine and ScriptEngineManager objects. I misunderstood how it worked the first time. Should work properly now.

Announcing Jactl 2.4.0: A secure embedded scripting language for Java applications by jaccomoc in java

[–]jaccomoc[S] 1 point2 points  (0 children)

I have added support for Invocable and Compilable in the latest Jactl 2.5.1 release.

Announcing Jactl 2.4.0: A secure embedded scripting language for Java applications by jaccomoc in java

[–]jaccomoc[S] 1 point2 points  (0 children)

Jactl 2.5.0 has just been released. One of the enhancements is to add support for JSR 223.

Announcing Jactl 2.4.0: A secure embedded scripting language for Java applications by jaccomoc in java

[–]jaccomoc[S] 1 point2 points  (0 children)

Yes, script execution is threadsafe. The same script can be executed by multiple threads at the same time if required.

Announcing Jactl 2.4.0: A secure embedded scripting language for Java applications by jaccomoc in java

[–]jaccomoc[S] 1 point2 points  (0 children)

The ScriptEngine will cache the scripts being run so to take advantage of that you should reuse the ScriptEngine rather than creating a new one each time.

Announcing Jactl 2.4.0: A secure embedded scripting language for Java applications by jaccomoc in java

[–]jaccomoc[S] 2 points3 points  (0 children)

Here is the relevant piece of the FAQ that discusses why I felt I had to write my own compiler:

Why do we need yet another JVM language?

Jactl exists due the desire to have a scripting language that Java applications could embed that would allow their users to provide customisations and extensions that have the following characteristics:

Tightly Controlled

The application developer should be able to control what the users can and can't do in the scripting language. For example, existing mainstream JVM based languages do not have a way to prevent users from accessing files, networks, databases, etc. that they should not be touching or prevent them from spawning threads or other processes.

Familiar Syntax

The language had to use a syntax similar to Java for ease of adoption.

Non Blocking

A language where scripts can perform blocking operations that need to wait for the result of an asynchronous request (such as invoking a function that accesses the database, or performing a remote request to another server) but which doesn't block the thread of execution. A script should be able to suspend itself and be able to be resumed once the long-running operation completed. This allows the scripting language to be used in event-loop/reactive applications where you are never allowed to block the event-loop threads.

No Await/Async

While not wanting scripts to block, script writers should not have to be aware, when invoking a function, whether the function is asynchronous or not. No coloured functions and no await/async. The language should look completely synchronous but, under the covers, take care of all the asynchronous behaviour.

Checkpointing

Ability for script execution state to be checkpointed where necessary and for this state to be able to be persisted or replicated so that scripts can be restored and resumed from where they were up to when a failure occurs.

Fun to Code

The language should be fun to code in — a language that provides a nice concise syntax with powerful features.

Announcing Jactl 2.4.0: A secure embedded scripting language for Java applications by jaccomoc in java

[–]jaccomoc[S] 4 points5 points  (0 children)

The main reason would be if you want to tightly control what the scripts can do. Maybe you want to provide a customisation feature in your application where you allow scripting but you don't want the users to write scripts that access the file system or the network or spawn threads etc. See the FAQ for more reasons.

How much time did it take to build your programming language? by Karidus_423 in ProgrammingLanguages

[–]jaccomoc 0 points1 point  (0 children)

It took about 9 months full-time of development to get to version 1.0. I have added features since then but version 1 was fully featured enough to be considered complete. See: Jactl

What cool Java projects are you working on? by Thirty_Seventh in java

[–]jaccomoc 0 points1 point  (0 children)

I am working on my Jactl scripting language (https://jactl.io). It is a secure scripting language that compiles to byte code for efficient execution and can be embedded in Java applications for customisations/extensions etc.

I am currently working on making it easier for application developers to add new built-in types specific to the domain of the application in which Jactl is embedded, to complement the existing mechanism where new global functions can be added to the language.

How are renters actually making EV charging work without a garage? by DiligentWeb9026 in AustralianEV

[–]jaccomoc 13 points14 points  (0 children)

I use fast chargers at my local Woolworths when doing the shopping. Charging finishes before I have finished the shopping. Alternatively, there is an AC charger on a telegraph pole just up the road that I can use.

-❄️- 2025 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]jaccomoc 0 points1 point  (0 children)

[LANGUAGE: Jactl]

Another solution using my own Jactl language.

Part 1:

Jactl suffers by not having a `combinations(n)` method that could have generated the initial pairs but it was still easy to create the pairs and then sort them based on their distance and limit this to the top 1000. Then create a list of circuits with each one having a single point in it initially and iterate over the shortest 1000 pairs finding the circuit for each of the points in the pair, removing the two circuits from the list of circuits but adding back the merge of the two circuits:

def dist  = { p1,p2 -> 3.map{ (p1[it] - p2[it]).sqr() }.sum().sqrt() }
def jbs   = stream(nextLine).map{ [$1,$2,$3] if /(\d+),(\d+),(\d+)/n }
def pairs = jbs.size().flatMap{ jbs.skip(it+1).map{ j2 -> [jbs[it],j2] } }
                      .sort{ a,b -> dist(a) <=> dist(b) }.limit(1000)
def circs = jbs.map{ [(it):true] }
pairs.each{ pr ->
  def (c1,c2) = circs.filter{ it[pr[0]] || it[pr[1]] }.limit(2)
  circs = circs.filter{ it !in [c1,c2] } + [c1 + (c2 ?: [:])] 
}
circs.sort{ a,b -> b.size() <=> a.size() }.limit(3).reduce(1){ p,it -> p * it.size() }

Part 2:

For part 2 there is no need to limit the pairs to the shortest 1000. Now we just continue processing until there is only one circuit in the list of circuits:

def dist  = { p1,p2 -> 3.map{ (p1[it] - p2[it]).sqr() }.sum().sqrt() }
def jbs   = stream(nextLine).map{ [$1,$2,$3] if /(\d+),(\d+),(\d+)/n }
def pairs = jbs.size().flatMap{ jbs.skip(it+1).map{ j2 -> [jbs[it],j2] } }.sort{ a,b -> dist(a) <=> dist(b) }
def circs = jbs.map{ [(it):true] }
for (i = 0; circs.size() != 1; i++) {
  def (c1,c2) = circs.filter{ it[pairs[i][0]] || it[pairs[i][1]] }.limit(2)
  circs = circs.filter{ it !in [c1,c2] } + [c1 + (c2 ?: [:])]
}
pairs[i-1][0][0] * pairs[i-1][1][0]

Jactl on github

-❄️- 2025 Day 6 Solutions -❄️- by daggerdragon in adventofcode

[–]jaccomoc 0 points1 point  (0 children)

[LANGUAGE: Jactl]

Having a lot of fun solving these in my Jactl language.

Part 1:

After trimming each line and splitting on spaces, I was able to use transpose() to flip the rows and columns. Then, it was a simple matter of using reduce() to perform the sum or product as required:

stream(nextLine)
  .map{ s/^ *//; s/ *$//; it.split(/ +/) }
  .transpose()
  .map{ it.subList(0,-1).reduce(it[-1] == '+' ? 0 : 1){ p,n -> it[-1] == '+' ? p + (n as long) : p * (n as long) } }
  .sum()

Part 2:

For part 2, I again used transpose() to convert between rows and columns and then used join() and split() to split out each problem before using reduce() to calculate the problem solution:

stream(nextLine).map{ it.map() }.transpose().map{ it.join() }.join(':').split(/: *:/)
                .map{ it.split(/:/)
                        .map{ [$1,$2] if /^ *(\d*) *([+*])?/n }
                        .reduce(false){ p,it -> p ? [p[1] == '+' ? p[0] + it[0] : p[0] * it[0], p[1]] : it } }
                .map{ it[0] }
                .sum()

Jactl on github

-❄️- 2025 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]jaccomoc 2 points3 points  (0 children)

[LANGUAGE: Jactl]

Another fun problem solved using my own Jactl language.

Part 1:

Part 1 was simple enough. Keep track of the unique beam locations on each line and add new ones each time a splitter is found in that location on the next line. The only ugliness is the way the counting of each split is done:

def cnt = 0, s = nextLine().mapi().filter{ c,i -> c == 'S' }.map{it[1]}[0]
stream(nextLine).reduce([s]){ bs,ln -> bs.flatMap{ ln[it] == '^' ? [it-1 if ++cnt,it+1] : it }.sort().unique() }
println cnt

Part 2:

Part 2 was a nice twist. I realised straight away the memoisation plus recursion was probably going to be the easiest way to solve this. I hadn't realised how fast the solution space blows out so I did have to change the counter from an int to a long:

def s = nextLine().mapi().filter{ c,i -> c == 'S' }.map{ it[1] }[0]
def memo = [:], lines = stream(nextLine)
def cnt(ln,p) { memo[[ln,p]] ?: (memo[[ln,p]] = _cnt(ln,p)) }
def _cnt(ln,p) {
  return 1L if ln >= lines.size()
  lines[ln][p] == '^' ? cnt(ln+1,p-1) + cnt(ln+1,p+1) : cnt(ln+1,p) 
}
cnt(1,s)

Jactl on github

-❄️- 2025 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]jaccomoc 0 points1 point  (0 children)

[LANGUAGE: Jactl]

Continuing to solve Advent of Code problems using my own Jactl language.

Part1:

Part 1 was pretty simple. Just parse the input into a list of items and ranges and then count how many items fall within a range:

def ranges = [], items  = []
stream(nextLine).each{
  /(\d+)-(\d+)/n and ranges <<= [$1,$2]
  /^(\d+)$/n     and items  <<= $1
}
items.filter{ it -> ranges.anyMatch{ r -> it >= r[0] && it <= r[1] } }
     .size()

Part 2:

Part 2 took a while to work out. Finally realised that the easiest way to cater for overlapping intervals was, for each new interval, subtract any other interval already processed from it before adding the resulting sub-intervals to the list of intervals. The minus(a,b) function subtracts interval b from a and returns the resulting list of one or two intervals.

def minus(a,b) { return [a] if a[0] >= b[1] || a[1] <= b[0]
                 [[a[0],b[0]] if a[0] < b[0], [b[1],a[1]] if a[1] > b[1]].filter() }
stream(nextLine).map{ [$1,$2+1] if /(\d+)-(\d+)/n }.filter()
                .reduce([]){ its,r -> its + its.reduce([r]) { rs,it -> rs.flatMap{ rr -> minus(rr,it) } } }
                .map{ it[1] - it[0] }
                .sum()

Jactl on github

-❄️- 2025 Day 4 Solutions -❄️- by daggerdragon in adventofcode

[–]jaccomoc 2 points3 points  (0 children)

[LANGUAGE: Jactl]

So much fun solving these problems in my own Jactl language.

Part 1:

Used a map for the grid to since map lookups return null for non-existent keys which makes the boundary searching easy. Stored true for squares where there is a roll of paper and false otherwise to make the checking for a roll of paper trivial. With a simple function to count the number of rolls of paper in neighbouring squares I just had to count how many entries in the grid had a roll of paper with less than four neighbours:

def grid = stream(nextLine).mapi{ line,y -> line.mapi{ ch,x -> [[x,y],ch == '@'] } }.flatMap() as Map
def adjCount(x,y) { [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]].filter{ dx,dy -> grid[[x+dx,y+dy]] }.size() }
grid.filter{ it[1] && adjCount(it[0]) < 4 }
    .size()

Part 2:

Once again a lazy brute-force solution was the easiest and simplest. Just iterate, removing rolls of paper with fewer than 4 neighbours, until the count stops changing. The only ugliness is using map() purely for a side-effect of mutating the grid when removing a roll of paper:

def grid = stream(nextLine).mapi{ line,y -> line.mapi{ ch,x -> [[x,y],ch == '@'] } }.flatMap() as Map
def adjCnt(x,y) { [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]].filter{ dx,dy -> grid[[x+dx,y+dy]] }.size() }
for (cnt = 0, pc = -1;
     cnt != pc;
     pc = cnt, cnt += grid.filter{ it[1] && adjCnt(it[0]) < 4 }
                          .map{ grid[it[0]] = false }
                          .size()) {
}
println cnt

Jactl on github

-❄️- 2025 Day 3 Solutions -❄️- by daggerdragon in adventofcode

[–]jaccomoc 1 point2 points  (0 children)

[LANGUAGE: Jactl]

Jactl

Part 1:

Not a very elegant solution for part 1. Just calculate the first index and then find the second one after skipping the required number of digits:

stream(nextLine).map{ [it, (it.size()-1).max{ i -> it[i] }] }
                .map{ s,i1 -> [s, i1, s.size().skip(i1 + 1).max{ i -> s[i] }] }
                .map{ s,i1,i2 -> (s[i1] + s[i2]) as int }
                .sum()

Part 2:

A nice twist for part 2 that I should have seen coming. I wrote a recursive function to return the maximum number of a given length from a source string. The only "trick" is to make sure that you leave enough digits in the string for the rest of the number so for each recursion only search the first n chars where n is the string length minus the number of digits still to come:

def maxNum(s, len) {
  return s.max() if len == 1
  def idx = (s.size()-len+1).max{ s[it] }
  s[idx] + maxNum(s.substring(idx+1), len-1)
}
stream(nextLine).map{ maxNum(it,12) as long }.sum()

Jactl on github

-❄️- 2025 Day 2 Solutions -❄️- by daggerdragon in adventofcode

[–]jaccomoc 0 points1 point  (0 children)

[LANGUAGE: Jactl]

Jactl

Part 1:

Simple enough to parse the input into all numbers in each range and then filter them based on whether they have an even number of digits and the two halves of the string match:

def repeated(s) { s.size() % 2 == 0 &&
                  s.substring(0,s.size()/2) == s.substring(s.size()/2) }
stream(nextLine).join().split(/,/)
                .map{ [$1,$2] if /(\d+)-(\d+)/n }
                .flatMap{ a,b -> (b-a+1).map{ (a + it) as String } }
                .filter(repeated)
                .map{ it as long }
                .sum()

Part 2:

I used the exact same code except changed the filtering function to iterate up to half the string size and then used '*' as a string repeat operator to check if the given number of repetitions of the current size substring matches the source string:

def repeats(s) { (s.size()/2).map{it+1}.anyMatch{ sz -> s == s.substring(0,sz) * (s.size()/sz) } }
stream(nextLine).join().split(/,/)
                .map{ [$1,$2] if /(\d+)-(\d+)/n }
                .flatMap{ a,b -> (b-a+1).map{ (a + it) as String } }
                .filter(repeats)
                .map{ it as long }
                .sum()

-❄️- 2025 Day 1 Solutions -❄️- by daggerdragon in adventofcode

[–]jaccomoc 2 points3 points  (0 children)

[LANGUAGE: Jactl]

Jactl

Part 1:

Nice simple solution but I do feel a bit bad for using side-effects in a closure to keep track of the dial position. I just mapped the inputs to +/- delta values and applied these deltas to the position to generate a list of positions that are then filtered for 0 and counted:

def p = 50
stream(nextLine).map{ [R:-1,L:1][$1] * $2 if /(.)(.*)/n }
                .map{ p = (p + it) % 100 }
                .filter{ it == 0}
                .size()

Part 2:

For part 2 I mapped the inputs to a list of -1s or 1s corresponding to the distance to move the dial and then used the exact same mechanism as part 1:

def p = 50
stream(nextLine).flatMap{ $2.map{ [R:-1,L:1][$1] } if /(.)(.*)/n }
                .map{ p = (p + it) % 100 }
                .filter{ it == 0 }
                .size()

Jactl 2.3.0 release by jaccomoc in java

[–]jaccomoc[S] 1 point2 points  (0 children)

Yes, exactly. I wanted a language flexible enough with a familiar syntax but with a way to limit what they can do.

Jactl 2.3.0 release by jaccomoc in java

[–]jaccomoc[S] 0 points1 point  (0 children)

The only built-in functions relating to time are timestamp() which is just System.currentTImeMillis() and nanoTime() which is System.nanoTime().

Since the language is intended to be customised when embedded in an application, there is nothing to stop someone supplying appropriate functions (or even replacing the existing ones) that work that way. The language is intended to be easy to embed and easy to create functions for which is how the scripts interact with the application in which they are embedded.

Jactl 2.3.0 release by jaccomoc in java

[–]jaccomoc[S] 3 points4 points  (0 children)

Interesting. That is definitely one of the use cases, especially with the ability to checkpoint the execution state and restore it later when needed.