Paul Hammant's Blog: That Ruby and Groovy Language Feature
“Groovy in builder-style DSL syntax that leverages closures” .. is where I was last month with GPT writing code in a particular style. And “Ruby in a builder-style DSL syntax that leverages blocks” is what it thought was the best way to describe the same for Ruby. Read on for the rabbit hole I went down with this.
Getting AI’s (GPT4 and Gemini) to describe this form of programming, was fun but won’t pay the bills. Here is a prexisting Ruby example using the library green_shoes
for some context: https://raw.githubusercontent.com/Alexanderlol/GS-Calc/master/calc.rb. The code for that:
require 'green_shoes'
Shoes.app(title: "My calculator", width: 200, height: 240) do
number_field = nil
@number = 0
@op = nil
@previous = 0
flow width: 200, height: 240 do
flow width: 0.7, height: 0.2 do
background rgb(0, 157, 228)
number_field = para @number, margin: 10
end
flow width: 0.3, height: 0.2 do
button 'Clr', width: 1.0, height: 1.0 do
@number = 0
number_field.replace(@number)
end
end
flow width: 1.0, height: 0.8 do
background rgb(139, 206, 236)
%w(7 8 9 + 4 5 6 - 1 2 3 / 0 . = *).each do |btn|
button btn, width: 50, height: 50 do
case btn
when /[0-9]/
@number = @number.to_i * 10 + btn.to_i
when '='
@number = @previous.send(@op, @number)
else
@previous, @number = @number, nil
@op = btn
end
number_field.replace(@number)
end
end
end
end
end
It would work with { and } instead of do and end. Credits: Calculator code by Alex Afeltra, Green_shoes gem by ashbb but unmaintained today. Shoes originally by _why. Even if it was maintained, you could not reasonably deploy to Android or iOS.
GPT thinks that is “Ruby in a builder-style DSL syntax that leverages blocks” and that would work for the people in the Ruby community.” Groovy would call those closures, while Ruby ‘blocks’. So there’s no easy canonical way of describing it without a language prefix.
I’ve always said pseudo-declarative DSL, but that would itself need qualification. HTML is declarative. Gemini agrees:
Reasons for classifying HTML as declarative:
1. Focus on describing desired state: HTML focuses on describing the content
and structure you want on a web page, not how to achieve it step-by-step. You
say what you want, and the browser handles the rendering details.
2. No control flow statements: Unlike languages with if/else or for loops,
HTML avoids control flow mechanisms. It simply defines the elements and their
relationships, allowing the browser to interpret and handle the presentation
and interactions dynamically.
2. Data-driven and reusable: HTML elements hold data and properties that define
their appearance and behavior. This creates reusability and modularity, allowing
the same element types to be used in different contexts with varying attributes.
While HTML is predominantly declarative, there are nuances to consider:
* Scripting languages like JavaScript can be embedded within HTML: This adds
imperative elements and control flow possibilities, though the overall
structure remains declarative.
* CSS stylesheets apply presentation rules: CSS, while separate from HTML,
defines how elements are presented. This introduces non-declarative aspects
as you specify colors, layouts, and animations.
Overall, calling HTML a "declarative DSL" is accurate, while acknowledging the potential
integration with imperative scripting for more complex interactions. Compared to the
"pseudo-declarative" style with mixed imperative elements, HTML leans far more towards
describing the desired state without explicitly controlling the steps to achieve it.
Gemini goes on after I asked for consideration of “HTML (and declarative languages) are parsed and loaded into navigable structures”
HTML and Declarative Languages:
* Parsing: As you mentioned, declarative languages like HTML are typically
parsed into a structured representation (e.g., DOM tree in HTML). This
structure captures the relationships and properties of elements within the
document.
* Data-driven: The parsed structure serves as the basis for further processing
and display. Styling and behavior are often applied based on this structure
and associated data.
* No execution: Declarative languages themselves don't execute code or define
algorithms. They provide a way to describe what elements and their properties
should be, leaving the interpretation and processing to other systems (like
browsers for HTML).
I’d wanted Gemini to say “the DOM” and it did. Gemini on the pseudo declarative DSL way I’m fascinated by:
Green Shoes and Similar Styles:
* Interpretation: Instead of parsing, this style relies on interpretation.
The code is executed line by line, and elements are dynamically created
and placed as the code runs.
* No explicit structure: While structures can emerge from the code
execution, there's no upfront parsing and creation of a separate data
structure representing the UI elements.
* Executable logic: The code includes both declarations of elements and
their properties, as well as logic for handling events and user
interactions.
So longer description of the scripting possible with this language feature could be: Pseudo-declarative DSL that leverage builder-style blocks or closures, and are interpreted (or compiled) as all 3GLs. And thus, are not merely parsed into DOM-alike for a secondary actioning (or rendering if the use is for UIs).
For UI usages, Kivy’s kv templates were nice, but not it. QML looks fairly close by the blocks/closures are not open to arbitrary bits of code. I’m told Nix’s expression language is attractive, but I can’t quite coerce GPT4 into showing me what Calculator would look like in a contrived nixGuiLib:
{ lib, nixGuiLib }:
let
# Function to create number buttons
createNumberButton = number: nixGuiLib.button {
label = toString number;
width = 50;
height = 50;
onClick = ''self: { self.number = self.number * 10 + ${toString number}; }'';
};
# Function to create operator buttons
createOperatorButton = op: nixGuiLib.button {
label = op;
width = 50;
height = 50;
onClick = ''self: { ... }''; # Placeholder for operator functionality
};
# Generate number buttons
numberButtons = map createNumberButton [7 8 9 4 5 6 1 2 3 0];
# Generate operator buttons
operatorButtons = map createOperatorButton ["+", "-", "*", "/", "=", "."];
# Combine number and operator buttons
combinedButtons = lib.concatLists [numberButtons operatorButtons];
in
nixGuiLib.app {
title = "My calculator";
width = 200;
height = 240;
content = nixGuiLib.flow {
width = 200;
height = 240;
children = [
nixGuiLib.flow {
width = 0.7;
height = 0.2;
children = [
nixGuiLib.background {
color = "rgb(0, 157, 228)";
child = nixGuiLib.para {
text = "0"; # Initial number display
margin = 10;
};
}
];
}
nixGuiLib.flow {
width = 0.3;
height = 0.2;
children = [
nixGuiLib.button {
label = "Clr";
width = 1.0;
height = 1.0;
onClick = ''self: { self.number = 0; }''; # Reset the display
}
];
}
nixGuiLib.flow {
width = 1.0;
height = 0.8;
children = [
nixGuiLib.background {
color = "rgb(139, 206, 236)";
child = nixGuiLib.flow {
children = combinedButtons;
# Additional properties for layout if necessary
};
}
];
}
];
};
}
In that last, concatLists
of two sets of buttons would give buttons incorrectly placed in a flowing four by four grid. The next generation was worse, and it was clear that the for-loop possible in the Ruby and Groovy scripts wasn’t possible. Even if that was possible, there is too much prefixing of nixGuiLib.
on the functions that could infer context of what instance to operate on as Ruby/Groovy do.
Groovy + SwingBuilder port of the calculator (no good to iOS or Android deployments):
import groovy.swing.SwingBuilder
import javax.swing.JFrame
import javax.swing.WindowConstants
new SwingBuilder().edt {
JFrame frame = frame(title: 'My calculator', size: [200, 240], defaultCloseOperation: WindowConstants.EXIT_ON_CLOSE) {
def numberField
def number = 0
def op = null
def previous = 0
// Top number display
panel(layout: new BorderLayout(), preferredSize: [200, 48]) {
background = new Color(0, 157, 228)
numberField = label(text: "${number}", horizontalAlignment: SwingConstants.CENTER)
}
// Clear button
panel(layout: new FlowLayout(), preferredSize: [200, 48]) {
button('Clr', size: [94, 48]) {
action = { evt ->
number = 0
numberField.text = "${number}"
}
}
}
// Number and operation buttons
panel(layout: new GridLayout(4, 4, 5, 5), preferredSize: [200, 144]) {
background = new Color(139, 206, 236)
['7', '8', '9', '+', '4', '5', '6', '-', '1', '2', '3', '/', '0', '.', '=', '*'].each { btn ->
button(btn, size: [50, 50]) {
action = { evt ->
switch(btn) {
case ~/[0-9]/:
number = number.toInteger() * 10 + btn.toInteger()
break
case '=':
if (op != null && previous != null) {
number = previous."$op"(number)
previous = null
op = null
}
break
default:
previous = number
number = 0
op = btn
break
}
numberField.text = "${number}"
}
}
}
}
}
frame.pack()
frame.show()
}
Here’s the QML port by GPT4:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
title: "My Calculator"
width: 200
height: 240
visible: true
property int number: 0
property string op: ""
property int previous: 0
ColumnLayout {
anchors.fill: parent
Rectangle {
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height * 0.2
color: "#009de4"
Text {
id: numberField
text: number.toString()
anchors.centerIn: parent
font.pixelSize: 24
}
}
Rectangle {
Layout.preferredWidth: parent.width * 0.3
Layout.preferredHeight: parent.height * 0.2
Button {
text: "Clr"
anchors.fill: parent
onClicked: {
number = 0;
numberField.text = number.toString();
}
}
}
GridLayout {
Layout.preferredHeight: parent.height * 0.8
columns: 4
rowSpacing: 10
columnSpacing: 10
property var buttons: ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "/", "0", ".", "=", "*"]
Repeater {
model: buttons
Button {
text: modelData
width: 50
height: 50
onClicked: {
var btn = text;
if (/[0-9]/.test(btn)) {
number = number * 10 + parseInt(btn);
} else if (btn === '=') {
if (op) {
number = eval(previous + op + number);
}
} else {
previous = number;
number = 0;
op = btn;
}
numberField.text = number.toString();
}
}
}
}
}
}
Compatibility: iOS & Android (via felgo.com tech), and macOS, Linux and Windows for QML
Note that I’ve previously been critical of QML. There is a theoretical deployment possibility to iOS & Android (via felgo.com tech), and macOS, Linux and Windows for QML.
Here is the GPT4 port to Flutter with inline Dart:
import 'package:flutter/material.dart';
void main() => runApp(MyCalculator());
class MyCalculator extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My calculator',
home: Calculator(),
);
}
}
class Calculator extends StatefulWidget {
@override
_CalculatorState createState() => _CalculatorState();
}
class _CalculatorState extends State<Calculator> {
String _display = '0';
double _number = 0;
String _op = '';
double _previous = 0;
void _onPressed(String value) {
setState(() {
switch (value) {
case 'Clr':
_display = '0';
_number = 0;
_op = '';
_previous = 0;
break;
case '=':
if (_op != null && _op.isNotEmpty) {
switch (_op) {
case '+':
_number = _previous + _number;
break;
case '-':
_number = _previous - _number;
break;
case '*':
_number = _previous * _number;
break;
case '/':
_number = _previous / _number;
break;
}
_previous = 0;
_op = '';
_display = _number.toString();
_number = 0;
}
break;
case '+':
case '-':
case '*':
case '/':
_previous = _number;
_number = 0;
_op = value;
break;
default: // Number buttons
_number = _number * 10 + double.parse(value);
_display = _number.toString();
break;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('My calculator')),
body: Column(
children: <Widget>[
Expanded(
child: Container(
color: Color.fromRGBO(0, 157, 228, 1),
child: Center(
child: Text(_display, style: TextStyle(fontSize: 24, color: Colors.white)),
),
),
),
Expanded(
child: Container(
color: Colors.blueGrey,
child: FlatButton(
child: Text('Clr', style: TextStyle(fontSize: 20)),
onPressed: () => _onPressed('Clr'),
),
),
),
Expanded(
flex: 4,
child: Container(
color: Color.fromRGBO(139, 206, 236, 1),
child: GridView.count(
crossAxisCount: 4,
children: <String>['7', '8', '9', '+', '4', '5', '6', '-', '1', '2', '3', '/', '0', '.', '=', '*']
.map((String value) {
return GridTile(
child: FlatButton(
child: Text(value, style: TextStyle(fontSize: 24)),
onPressed: () => _onPressed(value),
),
);
}).toList(),
),
),
),
],
),
);
}
}
Flutter has a iOS, Android, macOS, Linux and Windows deployment possibilities.
GPT4-made Kotlin with TornadoFX (JVM language family, but now abandoned):
import tornadofx.*
import javafx.geometry.Pos
import javafx.scene.paint.Color
class MyCalculatorApp : App(MyCalculatorView::class)
class MyCalculatorView : View("My calculator") {
var numberField: Text by singleAssign()
var number: Int = 0
var op: String? = null
var previous: Int = 0
override val root = vbox {
minWidth = 200.0
minHeight = 240.0
alignment = Pos.TOP_CENTER
hbox {
minWidth = 200.0
minHeight = 48.0 // 20% of 240
rectangle {
fill = Color.rgb(0, 157, 228)
width = 140.0 // 70% of 200
height = 48.0 // 100% height of this hbox
}
numberField = text(number.toString()) {
style {
fontSize = 20.px
fill = Color.WHITE
}
paddingAll = 10.0
}
button("Clr") {
minWidth = 60.0 // 30% of 200
minHeight = 48.0 // 100% height of this hbox
action {
number = 0
numberField.text = number.toString()
}
}
}
gridpane {
minWidth = 200.0
minHeight = 192.0 // 80% of 240
hgap = 5.0
vgap = 5.0
alignment = Pos.CENTER
rectangle {
fill = Color.rgb(139, 206, 236)
width = 200.0
height = 192.0
}
listOf("7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "/", "0", ".", "=", "*").forEachIndexed { index, btn ->
val (x, y) = index % 4 to index / 4
button(btn) {
minWidth = 45.0
minHeight = 45.0
gridpaneConstraints {
columnIndex = x
rowIndex = y
}
action {
when {
btn.matches(Regex("[0-9]")) -> {
number = number * 10 + btn.toInt()
}
btn == "=" -> {
number = when (op) {
"+" -> previous + number
"-" -> previous - number
"*" -> previous * number
"/" -> previous / number
else -> number
}
}
else -> {
previous = number
number = 0
op = btn
}
}
numberField.text = number.toString()
}
}
}
}
}
}
fun main() {
launch<MyCalculatorApp>()
}
GPT4 notes that the JavaFX without Kotlin is more verbose. I’m not doing to show it here because it does not have that language feature we’re looking for it is just a bunch of instance.method(arg)
statements that superficially look nested.
Kotlin with Compose (more modern than TornadoFX, via Perplexity.AI):
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
fun main() = singleWindowApplication(title = "My Calculator", width = 200.dp, height = 240.dp) {
var number by remember { mutableStateOf(0) }
var previous by remember { mutableStateOf(0) }
var operation by remember { mutableStateOf<((Int, Int) -> Int)?>(null) }
Column(modifier = Modifier.fillMaxSize()) {
Row(
modifier = Modifier
.weight(0.2f)
.fillMaxWidth()
.background(Color(0xFF009DE4))
) {
Text(
text = number.toString(),
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(10.dp)
.weight(0.7f)
)
Button(
onClick = { number = 0 },
modifier = Modifier
.align(Alignment.CenterVertically)
.weight(0.3f)
) {
Text("Clr")
}
}
Column(
modifier = Modifier
.weight(0.8f)
.fillMaxWidth()
.background(Color(0xFF8BCEEC))
) {
val buttons = listOf("7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "/", "0", ".", "=", "*")
buttons.chunked(4).forEach { row ->
Row {
row.forEach { btn ->
Button(
onClick = {
when (btn) {
in "0".."9" -> number = number * 10 + btn.toInt()
"=" -> operation?.let { op -> number = op(previous, number) }
else -> {
previous = number
number = 0
operation = when (btn) {
"+" -> Int::plus
"-" -> Int::minus
"*" -> Int::times
"/" -> Int::div
else -> null
}
}
}
},
modifier = Modifier
.padding(2.dp)
.size(50.dp)
) {
Text(btn)
}
}
}
}
}
}
}
Apple’s SwiftUI made by GPT4 (macOS and iOS only for now)
import SwiftUI
struct ContentView: View {
@State private var number: Double = 0
@State private var previous: Double = 0
@State private var op: String? = nil
let buttons: [[String]] = [
["7", "8", "9", "+"],
["4", "5", "6", "-"],
["1", "2", "3", "/"],
["0", ".", "=", "*"]
]
var body: some View {
VStack(spacing: 0) {
Text("\(number)")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.foregroundColor(.white)
.font(.largeTitle)
Button("Clr") {
self.number = 0
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
.foregroundColor(.white)
ForEach(buttons, id: \.self) { row in
HStack(spacing: 0) {
ForEach(row, id: \.self) { button in
Button(button) {
self.buttonPressed(button)
}
.frame(width: 50, height: 50)
.background(Color.cyan)
.foregroundColor(.black)
}
}
}
}
.frame(width: 200, height: 240)
}
private func buttonPressed(_ button: String) {
switch button {
case "0"..."9":
number = number * 10 + Double(button)!
case "=":
if let op = op {
switch op {
case "+": number = previous + number
case "-": number = previous - number
case "*": number = previous * number
case "/": number = previous / number
default: break
}
}
default:
previous = number
number = 0
self.op = button
}
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
And GPT4 Common Lisp with CLIM (for Mac, Windows and Linux deployments), even though that’s a different language family than the one I’m talking about in this blog entry - it’s the epitome of terse and elegant.
(defpackage :my-calculator
(:use :common-lisp :clim-lisp :clim))
(in-package :my-calculator)
(defun start-calculator ()
(let ((number 0)
(op nil)
(previous 0))
(define-application-frame my-calculator ()
()
(:panes
(display :application
:display-function (lambda (frame pane)
(declare (ignore frame))
(format pane "~A" number))
:scroll-bars nil)
(buttons :push-button-panel
:items '(("Clr" (setf number 0))
"7" "8" "9" "+" "4" "5" "6" "-" "1" "2" "3" "/" "0" "." "=" "*")
:activate-callbacks '((:button-press (button)
(case (string button)
(("Clr") (setf number 0))
((digit when (digit-char-p (aref digit 0)))
(setf number (+ (* number 10) (parse-integer digit))))
(("=") (setf number (funcall (case op
("+" #'+)
("-" #'-)
("/" #'/)
("*" #'*)
(otherwise #'identity))
previous number)))
(otherwise
(setf previous number)
(setf number nil)
(setf op button)))
(redisplay-frame-pane *application-frame* 'display)))))
(:layouts
(default (vertically ()
(scrolling (:height 120) display)
(fixed-size (:height 120) buttons)))))
(run-frame-top-level (make-application-frame 'my-calculator))))
(start-calculator)
Lisp Calcular from Chrysalisp project:
(import "././login/env.inc")
(import "gui/lisp.inc")
(enums +event 0
(enum close max min)
(enum button))
(ui-window *window* ()
(ui-title-bar _ "Calculator" (0xea19 0xea1b 0xea1a) +event_close)
(ui-label *display* (:text "0" :color +argb_white :flow_flags +flow_flag_align_hright
:font (create-font "fonts/OpenSans-Regular.ctf" 24)))
(ui-grid _ (:grid_width 4 :grid_height 4 :color *env_toolbar_col*
:font (create-font "fonts/OpenSans-Regular.ctf" 42))
(each (lambda (text)
(. (ui-button _ (:text (if (eql text "C") "AC" text)))
:connect +event_button))
"789/456*123-0=C+")))
(defun do_lastop ()
(cond
((eql lastop "+")
(setq accum (+ accum num)))
((eql lastop "-")
(setq accum (- accum num)))
((eql lastop "*")
(setq accum (* accum num)))
((eql lastop "/")
(if (/= num 0) (setq accum (/ accum num)))))
accum)
(defun main ()
(bind '(x y w h) (apply view-locate (. *window* :pref_size)))
(gui-add-front (. *window* :change x y w h))
(defq accum 0 value 0 num 0 lastop :nil)
(while (cond
((>= (defq id (getf (defq msg (mail-read (task-netid))) +ev_msg_target_id)) +event_button)
(defq op (get :text (. *window* :find_id (getf msg +ev_msg_action_source_id))))
(cond
((eql op "AC")
(setq accum 0 value 0 num 0 lastop :nil))
((find op "=+-/*")
(if lastop
(setq value (do_lastop))
(setq value num accum num))
(setq lastop op num 0))
(:t
(cond
((= num 0)
(unless (eql op "0"))
(setq num (str-as-num op)))
(:t (setq num (str-as-num (cat (str num) op)))))
(setq value num)))
(set *display* :text (str value))
(.-> *display* :layout :dirty))
((= id +event_close)
;close button
:nil)
((= id +event_min)
;min button
(bind '(x y w h) (apply view-fit (cat (. *window* :get_pos) (. *window* :pref_size))))
(. *window* :change_dirty x y w h))
((= id +event_max)
;max button
(bind '(x y w h) (apply view-fit (cat (. *window* :get_pos) '(512 512))))
(. *window* :change_dirty x y w h))
(:t (. *window* :event msg))))
(gui-sub *window*))
That one is real.
Janet-lang version by Perplexity.AI:
(import janetui)
(def state @{:number 0 :previous 0 :op nil})
(defn create-button [label width height on-click]
(janetui/button :text label :width width :height height :on-click on-click))
(defn create-number-button [number]
(create-button (string number) 50 50 (fn []
(put state :number (+ (* (get state :number) 10) number))
(janetui/update-display))))
(defn create-operator-button [op]
(create-button op 50 50 (fn []
(put state :previous (get state :number))
(put state :number 0)
(put state :op op)
(janetui/update-display))))
(defn calculate []
(when (get state :op)
(put state :number ((get {:+ + :- - :* * :/ /} (get state :op))
(get state :previous)
(get state :number))
:previous 0
:op nil)
(janetui/update-display)))
(janetui/app :title "My calculator" :width 200 :height 240
(janetui/flow :width 200 :height 240
(janetui/flow :width 0.7 :height 0.2
(janetui/background :color "rgb(0, 157, 228)"
(janetui/para :text (string (get state :number)) :margin 10)))
(janetui/flow :width 0.3 :height 0.2
(create-button "Clr" 1.0 1.0 (fn []
(put state :number 0)
(janetui/update-display))))
(janetui/flow :width 1.0 :height 0.8
(janetui/background :color "rgb(139, 206, 236)"
(let [number-buttons (map create-number-button [7 8 9 4 5 6 1 2 3 0])
operator-buttons (map create-operator-button ["+" "-" "*" "/" "="])]
(apply janetui/flow (concat number-buttons operator-buttons))))))
Terse vs Elegance plot
Perplexity.AI gives an elegance score for each:
Language/Framework | Lines of Code | Elegance (1-10) |
---|---|---|
Ruby with Green Shoes | 21 | 9 |
Kotlin with Jetpack Compose | 37 | 9 |
SwiftUI | 40 | 8 |
Common Lisp with CLIM | 29 | 7 |
QML | 38 | 7 |
Flutter with Dart | 55 | 6 |
Kotlin with TornadoFX | 43 | 6 |
Groovy with SwingBuilder | 31 | 6 |
Nix with nixGuiLib | 53 | 5 |
Janet-lang | 31 | 8 |
From which we can make a plot:
(top left is best … Ruby and Green Shoes still)
One more, non pseudo-declarative UI markup
Back to Groovy - there’s an exciting JVM build tech called “Bld”that uses Java and fluent-interfaces as its way of configuring and describing targets: I made a Groovy skin over that. In execution it’s about one second worse than the pure Java one for me. Someone with Groovy skills could reduce that, I guess (bld.groovy):
@Grapes(
@Grab(group='com.uwyn.rife2', module='bld', version='1.9.0')
)
import static rife.bld.dependencies.Repository.*
import rife.bld.dependencies.Scope
import static rife.bld.operations.TemplateType.HTML
def proj = BuildConfig.webProject() {
pkg = "com.example"
name = 'Mywebapp'
mainClass = "com.example.MywebappSite"
uberJarMainClass = "com.example.MywebappSiteUber"
version = version(0,1,0)
downloadSources = true
repositories = List.of(MAVEN_CENTRAL, RIFE2_RELEASES)
compile() {
include() {
dependency("com.uwyn.rife2", "rife2", version(1,7,3))
}
}
test() {
include() {
dependency("org.jsoup", "jsoup", version(1,17,2))
dependency("org.junit.jupiter", "junit-jupiter", version(5,10,2))
dependency("org.junit.platform", "junit-platform-console-standalone", version(1,10,2))
}
}
standalone() {
include() {
dependency("org.eclipse.jetty.ee10", "jetty-ee10", version(12,0,6))
dependency("org.eclipse.jetty.ee10", "jetty-ee10-servlet", version(12,0,6))
dependency("org.slf4j", "slf4j-simple", version(2,0,11))
}
}
precompileOperation() {
templateTypes(HTML)
}
}
proj.start(args)
// -------------------------------------------------------------------------------------------
// All below here would be in a library no the bld.groovy file for ultimate end-user realities
class BuildConfig {
def static webProject(Closure config) {
def proj = new GldWebProject()
config.delegate = proj
config.resolveStrategy = Closure.DELEGATE_FIRST
config()
return proj
}
}
class GldWebProject extends rife.bld.WebProject {
def compile(Closure config) {
def gldScope = new GldScope(this, scope(Scope.compile))
config.delegate = gldScope
config.resolveStrategy = Closure.DELEGATE_FIRST
config()
}
def test(Closure config) {
def gldScope = new GldScope(this, scope(Scope.test))
config.delegate = gldScope
config.resolveStrategy = Closure.DELEGATE_FIRST
config()
}
def standalone(Closure config) {
def gldScope = new GldScope(this, scope(Scope.standalone))
config.delegate = gldScope
config.resolveStrategy = Closure.DELEGATE_FIRST
config()
}
def precompileOperation(Closure config) {
def gldpo = new GldPrecompileOperation(this)
config.delegate = gldpo
config.resolveStrategy = Closure.DELEGATE_FIRST
config()
}
}
class GldScope {
def proj
def sc
def GldScope(rife.bld.WebProject proj, rife.bld.dependencies.DependencySet scope) {
this.sc = scope
this.proj = proj
}
def include(Closure config) {
def gldinclude = new GldInclude(proj, sc)
config.delegate = gldinclude
config.resolveStrategy = Closure.DELEGATE_FIRST
config()
}
}
class GldInclude {
def proj
def ds
def GldInclude(rife.bld.WebProject proj, rife.bld.dependencies.DependencySet scope) {
this.proj = proj
this.ds = scope
}
def dependency(g, a, v) {
ds.include(proj.dependency(g, a, v))
}
}
class GldPrecompileOperation {
def proj
def ttype
def GldPrecompileOperation(rife.bld.WebProject proj) {
this.proj = proj
}
def templateTypes(rife.bld.operations.TemplateType ttype) {
this.ttype = ttype
proj.precompileOperation().templateTypes(ttype)
}
}
That’s not just a speculative grammar, you can try it here: https://raw.githubusercontent.com/paul-hammant/groovy-bld-prototype. I do have a prior article on Groovy infra-as-code here
Back to the quest implicit in this post
Anyway, this is a needs a short name, in my opinion. Like a “Monads” class of short name. I asked Ruby’s creator, Yukihiro “Matz” Matsumoto, and he said “DSL with scope” is what he’s always called it.
Updates:
- Feb 24th, 2024: add GPT4 ported calculator code: Nix example with a contrived UI lib, Swing example with its real SwingBuilder, QML example, Lisp example
- Feb 25th, 2024: add Kotlin with TornadoFX and SwiftUI port of that Ruby Calculator.
- Mar 1st, 2024: Add Groovy grammar for ‘Bld’ build language
- Mar 7th, 2024: Add Matz’s feedback
- May 15th, 2024: Add Kotlin+Compose, Janet and a plot
- June 21st, 2024: Add ChrysLisp version (but not to the plot)