Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Support :var Input to org babel code blocks #4

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 59 additions & 137 deletions ob-dart.el
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,41 @@
;;; Code:

(require 'ob)
;(use-package 'f)

(defvar org-babel-tangle-lang-exts)
(add-to-list 'org-babel-tangle-lang-exts '("dart" . "dart"))
(defvar org-babel-default-header-args:dart '())
(defvar ob-dart-default-header-args '())

(defvar ob-dart-command "dart"
"Name of the command to use for executing Dart code.
Windows support is pending.")


(defun ob-dart-variable-assignments (params)
"Return a list of Dart statements assigning the block's variables.
The assignments are defined in PARAMS."
(mapcar
(lambda (pair)
(format "var %s = %s;"
(car pair)
(ob-dart-var-to-dart (cdr pair))))
(org-babel--get-vars params)))

(defun ob-dart-var-to-dart (var)
"Convert an elisp value to a Dart variable.
Convert an elisp value, VAR, into a string of Dart source code
specifying a variable of the same value."
(cond
((listp var)
(concat "[" (mapconcat #'ob-dart-var-to-dart var ", ") "]"))
((stringp var)
(format "\"%s\"" (substring-no-properties var)))
((numberp var)
(number-to-string var))
((booleanp var)
(if var "true" "false"))
(t (format "%s" var))))

(defun org-babel-execute:dart (body params)
"Execute a block of Dart code with org-babel.
This function is called by `org-babel-execute-src-block'.
Expand All @@ -62,34 +88,24 @@ Args:
or for the key `:results', (value raw)
from Babel args `:results value raw'."
(message "executing Dart source code block")
;; (processed-params = org-babel-process-params RETURNS assoc list with slightly
;; reformatted list of :param values from all elements after #+begin_src
;; For example, from ':results output raw', the function creates two assoc elements,
;; one is (:result-type . output), second is (:result-params . raw)
;; Variables are converted to PROPERTY LIST in the result.
;; Other less frequent parameters like (:colname-names . (list names)) are cons cells.
(let* ((processed-params (org-babel-process-params params))
(session (ob-dart-initiate-session (nth 0 processed-params)))
(vars (nth 1 processed-params))
(result-params (nth 2 processed-params))
(let* ((ob-dart-command
(or (cdr (assq :dart params))
ob-dart-command))
(vars (ob-dart-variable-assignments params))
(result-params (nth 2 (org-babel-process-params params)))
(result-type (cdr (assoc :result-type params)))
(full-body (org-babel-expand-body:generic
body params))
;; ob-dart-evaluate is the core function, which
;; - generates dart code from `body' wrapped in `ob-dart-wrapper'
;; - calls the generated dart code in shell
;; - processes the shell's stdout and stderr
body params
vars))
(result (ob-dart-evaluate
session full-body result-type result-params)))

nil full-body result-type result-params)))
(org-babel-reassemble-table
result
(org-babel-pick-name
(cdr (assoc :colname-names params)) (cdr (assoc :colnames params)))
(org-babel-pick-name
(cdr (assoc :rowname-names params)) (cdr (assoc :rownames params))))))


(defun ob-dart-evaluate (session body &optional result-type result-params)
"Evaluate BODY in external Dart process.
If RESULT-TYPE equals `output' then return standard output as a string.
Expand All @@ -111,96 +127,12 @@ Args:

;; Set the name generated-filename='generated-filename=/tmp/dart-RAND.dart'
(let* ((generated-filename (org-babel-temp-file "dart-")))

;; Create 'temp-file' named 'generated-filename',
;; and insert into it the Dart code generated from body.

(ob-dart-write-dart-file-from body generated-filename)

;; In this section below, we call 'org-babel-eval'.
;; The 'org-babel-eval' runs the first arg 'command' by delegating
;; the run to 'org-babel--shell-command-on-region'.
;; The region is in the "current buffer", which is the 'temp-buffer'
;; created as the "current buffer". The 'temp-buffer' is the buffer where
;; everything in the 'org-babel--shell-command-on-region' runs, see below.
;; The 'temp-buffer' is populated with the
;; second arg QUERY (we set QUERY to "", so only 'command' runs)
;; We do build the 'command' below as a string
;; concat of 'org-babel-dart-command' => dart and the 'generated-filename';
;; this creates a string which, if executed,
;; runs OS process as "dart generated-filename".
;; The 'generated-filename=/tmp/dart-RAND.dart' was named above using
;; 'org-babel-temp-file'. The 'generated-filename' contains the BEGIN_SRC..END_SRC.
;;
;; In detail, the 'org-babel-eval', works like this:
;; 1. Lets - 'error-buffer' = " *Org-Babel Error*", creates or erases it.
;; 2. Creates a 'temp-buffer' ('with-temp-buffer' rest of work done here)
;; where it inserts the QUERY (empty)
;; 3. As if cursor was put in the temp-buffer, it delegates to
;; 'org-babel--shell-command-on-region' 'command' 'error-buffer'
;; where
;; 'command'='dart /tmp/dart-RAND.dart "output|value"'
;; 4. On success of the
;; 'org-babel--shell-command-on-region' 'command' 'error-buffer '
;; its stdout is placed in the 'temp-buffer'
;; in which context steps 3-6 run
;; (temp-buffer is 'scratch' in testing).
;; On error, error text is placed in the 'error-buffer'.
;; 5. Where are we: After step 4,
;; the 'temp-buffer' contains the stdout from Dart code 'println',
;; which may be:
;; - either output from internal 'print' (if results: 'output')
;; - or toString() on the 'return lastStatement' (results: 'value')
;; See the 'org-babel-dart-wrapper-method' for Dart code that runs the .
;; 6. The contents of the temp-buffer is returned to caller as
;; 'buffer-string' ; string of the temp-buffer, stdout from Dart.
;;
;; Note: Test this step 3. in 'scratch' as if the temp-buffer was scratch:
;; - Place pointer to 'scratch' and do
;; - Alt-: (org-babel--shell-command-on-region "echo Hello" "")
;; this runs "echo Hello" is shell, and places
;; the string Hello in 'scratch' - the 'temp-buffer'!
;;
;;
;;
;; Details of 'org-babel--shell-command-on-region' 'command' 'error-buffer'
;; 1. Assumes it is run when pointer is in the 'temp-buffer' created
;; by caller 'org-babel-eval' - 'temp-buffer' is 'scratch' in our testing.
;; 2. Lets variables
;; - input-file named 'ob-input-TEMP-RAND'
;; - error-file named 'ob-error--TEMP-RAND'
;; - shell-file-name /bin/sh
;; 3. Copies the temp-buffer where the pointer is, to 'input-file';
;; at the time this is called, the 'temp-buffer' contains the QUERY,
;; which is a zero-lenght string "" in our case.
;; 3. Calls 'process-file' (this is like 'call-process' with args:
;; (process-file
;; program = shell-file-name ; has /bin/sh
;; infile = input-file ; has QUERY
;; buffer = has cons (t . error-file) ; see call-process
;; display = nil ; has nil
;; args1 = sh-switch ; has probably empty
;; args2 = command ; 'dart /tmp/dart-RAND.dart "output|value"'
;; which calls apply on:
;; call-process ; runs program synchronously in separate process
;; program = program ; has /bin/sh
;; infile = input-file ; has QUERY - would be stdin of command
;; destination= ; has cons(t . error-file)
;; ; how to handle program output
;; ; t = "use current buffer for stdout"
;; ; error-file is where strerr goes
;; display ; has nil, means no action
;; args1 = sh-switch ; has probably empty
;; args2 = command ; 'dart /tmp/dart-RAND.dart "output|value"'
;;
;; 4. The above step 3 means:
;; - Exec 'dart /tmp/dart-RAND.dart "output|value"'
;; - Place stdout to current buffer (t), which is the with-temp-buff
;; created by 'org-babel-eval'
;; - Places stderr in the error-file viewed by the error-buffer
;;

;; Run org-babel-eval, a shell process that calls dart on the generated-filename.
;; Run org-babel-eval
(let ((raw (org-babel-eval
(concat ob-dart-command " " generated-filename " " (symbol-name result-type)) "")))
;; result-type: both 'value and 'output formats results as table, unless raw is specified
Expand All @@ -220,11 +152,6 @@ Args:
body-part-top
wrapper
body-part-main)))
;;(f-write-text
;; generated-dart-str
;; 'utf-8
;; generated-filename)

(with-temp-file
generated-filename
(insert generated-dart-str))))
Expand Down Expand Up @@ -254,7 +181,7 @@ Comments:
can be split into 2 parts, then processed with a common wrapper
to generate the Dart source that is executed.

3. The WRAPPER contains format symbols as %s, where block parts are inserted.
3. The WRAPPER contains format symbols as %s, %w, %a.
Currently only BODY-PART-MAIN is inserted.

4. If main function exists in BODY:
Expand All @@ -266,15 +193,15 @@ Currently only BODY-PART-MAIN is inserted.
- `:results output' and `:results value' are supported"

(let* (
(main-function-line-index (string-match "^ *\\([A-z0-9_<>]*\\) *main *(.*" body)))
(main-function-line-index (string-match "^ *void *main *\\(\\([A-Za-z0-9_<>]*\\)\\)? *\\(.*\\)" body)))

(if main-function-line-index
;; If main function exists in body,
;; ob-dart uses this Org Babel souce block unchanged,
;; ob-dart uses this Org Babel source block unchanged,
;; with imports and all code.
;; Only :results output are supported in this branch.
;; Dart code is "import 'dart:async';\n", appended with body.
;; The wrapper becomes "%s", effectively becomes the body.
;; Dart code is \"import 'dart:async';\n\", appended with body.
;; The wrapper becomes \"%s\", effectively becomes the body.

(list "%s" "import 'dart:async';\n" body)
;; main function NOT in body, passed wrapper MUST be ob-dart-wrapper
Expand All @@ -288,7 +215,7 @@ appended with WRAPPER string which contains format symbols as %s, %w, %a.
The WRAPPER is inserted with contents of format-specs at appropriate symbols,
and BODY-PART-MAIN at WRAPPER's symbol %s.

The wrapper can be just `%s' then the BODY-PART-MAIN
The wrapper can be just \"%s\" then the BODY-PART-MAIN
becomes the WRAPPER, and is appended after BODY-PART-TOP.

Comments:
Expand All @@ -308,12 +235,9 @@ is not part of this method."
(format-spec
wrapper
`((?a . "async ") (?w . "await ") (?s . ,body-part-main))
; ignore missing symbols %a in wrapper
nil
; split to list
nil)))


(defvar ob-dart-wrapper-imports
"
import 'dart:async';
Expand All @@ -327,7 +251,7 @@ import 'dart:isolate';
import 'dart:math';
import 'dart:mirrors';
"
"Documentation: Variable which returns Dart wrapper imports as String.
"Documentation: Variable which returns Dart wrapper imports as String.

Created from wrapper-imports.drap.

Expand All @@ -345,8 +269,8 @@ class Gen {
/// if org file has NO MAIN, code is from between #+begin_src and #+end_src.
/// if org file has MAIN, code is copied from main.
/// Either way, async is added if the code block contains await.
/// That is theory: for files with MAIN, we add async here if the main is async.
runBlock(List args) %a {
/// That is theory: for files with MAIN, we add async here if the main is async.
runBlock(Map<String, dynamic> vars) %a {
// - Org code block from begin_src .. end_src inserted here by elisp format.
// - See `ob-dart-wrapper` and `format-spec` in wrap-body.esh and ob-dart.el
%s
Expand All @@ -358,10 +282,9 @@ class Gen {
///
/// See the [runBlockResultsValue] for description how the async propagation
/// and await-ing result just before print.
runBlockResultsOutput(List args) %a {
runBlock(args);
runBlockResultsOutput(Map<String, dynamic> vars) %a {
runBlock(vars);
}

/// Runs the BEGIN_SRC .. END_SRC source block.
///
/// Uses [ZoneSpecification] to skip the print to stdout,
Expand All @@ -376,11 +299,11 @@ class Gen {
///
/// The [returnedValue.toString] result pops up in elisp code
/// in the 'ob-dart.el' function
/// 'org-babel-dart-evaluate'.
/// 'ob-dart-evaluate'.
/// This function parses and manipulates the [returnedValue.toString] returned from here.
///
/// The elisp parsing and manipulation of the [returnedValue.toString] in
/// the 'org-babel-dart-evaluate' depends on the parameters passed in
/// the 'ob-dart-evaluate' depends on the parameters passed in
/// `:results [output|value] [raw]`
/// for example, for:
/// - [value raw] there is no parsing or manipulation of the result.
Expand Down Expand Up @@ -422,7 +345,7 @@ class Gen {
/// The [runBlock] runs async,
/// but BEFORE WE PRINT IN CALLER, this thread WAITs, making async to resolve
/// the future [runBlock] returnedValue BACK INTO this FLOW (THREAD) before print.
runBlockResultsValue(List args) %a {
runBlockResultsValue(List<String> args) %a {
var returnedValue;
/// Runs it's [body], the function in the first argument,
/// in a new [Zone], based on [ZoneSpecification].
Expand Down Expand Up @@ -453,10 +376,10 @@ class Gen {
}
}

main(List args) %a {
void main(List<String> args) %a {
var results_collection_type = null;
if (args.length > 0) {
results_collection_type = args.elementAt(0);
results_collection_type = args[0];
}

if (results_collection_type == 'output') {
Expand All @@ -475,8 +398,7 @@ class Gen {
}
}
"

"Documentation: Variable which returns Dart wrapper code as String.
"Documentation: Variable which returns Dart wrapper code as String.

Created from wrapper.drap

Expand All @@ -491,12 +413,12 @@ If the passed argument to
is:
- `output':
- The stdout from Dart print() in the snippet is send
to the standart output, and becomes the #+RESULT.
to the standard output, and becomes the #+RESULT.
- `value':
- The stdout from Dart print() is blocked by the `runZoned'
method, and the `toString()' of the last `return lasExpression'
becomes the #+RESULT."
)
method, and the `toString()' of the last `return lastExpression'
becomes the #+RESULT.")


(defun ob-dart-table-or-string (results)
"Convert RESULTS into an appropriate elisp value.
Expand Down