emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: John C <john.ciolfi.32@gmail.com>
To: Ihor Radchenko <yantar92@posteo.net>
Cc: emacs-orgmode@gnu.org
Subject: Re: ob-octave: improve MATLAB support
Date: Sat, 28 Dec 2024 22:04:11 -0500	[thread overview]
Message-ID: <CACb3vdQ7GufyWXdMMTJKJ_wgz4Cf0-XNF4fnmq1x=-w9He-g-Q@mail.gmail.com> (raw)
In-Reply-To: <87o712lv3t.fsf@localhost>


[-- Attachment #1.1: Type: text/plain, Size: 9163 bytes --]

I accidentally did "reply" instead of "reply all". Re-adding the
e-mail between Ihor and I.

Regarding the suggestion to filter line-by-line. I think I understand that,
but it will require that we match input lines to echo'd input. However,
it's possible that output lines match input and are not from echo'd input.
In addition, there's other stripping (filtering) that is needed. For
example, removal of the error indicators (<ERRORTXT>). I like the idea of
enhancing org-babel-comint-with-output to strip content based on regular
expressions and have implemented that.

See attached org-matlab.patch which addresses all feedback. Here's the
commit info.

ob-matlab.el: improve MATLAB support

* lisp/ob-matlab.el (header): Update URL for MATLAB

* lisp/ob-octave.el (org-babel-octave-evaluate): Fixed MATLAB support
  - Deprecate variables related to MATLAB Emacs Link and removed the code.
    Emacs Link capability was removed from MATLAB release R2009a, 15 years
ago.
  - Fixed the following type of org block evaluation:
    1) #+begin_src matlab :results verbatim
    2) #+begin_src matlab :results output
    3) #+begin_src matlab :results output latex
    4) #+begin_src matlab :results file graphics
    which aid in writing scientific papers.
  - Minor point, the correct spelling of MATLAB when referencing the
product is
    all upper case.

* ob-comint.el: enhanced org-babel-comint-with-output
  - The META argument of org-babel-comint-with-output now supports an
optional
    STRIP-REGEXPS which can be used to remove content from the returned
output.

* etc/ORG-NEWS (New functions and changes in function arguments):
  Added entry "ob-octave: improved MATLAB support"

Thanks
John

-----
*Ihor Radchenko *<yantar92@posteo.net> Sun, Dec 22, 2024 at 11:24 PM

To: John C <john.ciolfi.32@gmail.com>
Cc: emacs-orgmode@gnu.org
Hi John,

It has been a month since the last message in this thread.
Do you need any help working on the patch?

--
Ihor Radchenko // yantar92,
Org mode maintainer,
[Quoted text hidden]
------------------------------
*John C *<john.ciolfi.32@gmail.com> Wed, Dec 25, 2024 at 8:26 AM

To: Ihor Radchenko <yantar92@posteo.net>
My apologies - I’ve been too busy. I'll update the NEWS entry per your
suggestion once we resolve the "%-<org-eval>" comment issue you flagged.

Consider this MATLAB code block and expected results.

    #+begin_src matlab :exports both :results output latex
      m = [4*pi, 3*pi; 2*pi, pi];
      result = latex(sym(m));
      disp(result)
    #+end_src

    #+RESULTS:
    #+begin_export latex
    \left(\begin{array}{cc} 4\,\pi  & 3\,\pi \\ 2\,\pi  & \pi
 \end{array}\right)
    #+end_export

If we remove the proposed "%-<org-eval>" handling from ob-octave.el, we get
the incorrect result:

    #+RESULTS:
    #+begin_export latex
    m = [4*pi, 3*pi; 2*pi, pi];
    result = latex(sym(m));
    disp(result)
    \left(\begin{array}{cc} 4\,\pi  & 3\,\pi \\ 2\,\pi  & \pi
 \end{array}\right)
    #+end_export

In this case, the *MATLAB* buffer contains:

    >> m = [4*pi, 3*pi; 2*pi, pi];
    result = latex(sym(m));
    disp(result)
    'org_babel_eoe'
    m = [4*pi, 3*pi; 2*pi, pi];
    >> result = latex(sym(m));
    >> disp(result)
    \left(\begin{array}{cc} 4\,\pi  & 3\,\pi \\ 2\,\pi  & \pi
 \end{array}\right)
    >> 'org_babel_eoe'

    ans =

        'org_babel_eoe'

    >>

I suspect that the issue is that the MATLAB echo's all commands and that
there's noting wrong with comint. For example, matlab-shell buffer,
*MATLAB*, the current buffer and at the MATLAB promot, ">>",

    M-: RET (comint-send-string (get-buffer-process (current-buffer))
"pwd\n") RET

Results in:

    #+begin_example
    >> pwd

    ans =

        '/home/ciolfi/tmp'
    #+end_example

Therefore, the adding "%-<org-eval>" MATLAB comments to the code being
evaluated, then stripping the echo'd lines containing the "%-<org-eval>" is
probably the best option.

Thanks
John
[Quoted text hidden]
------------------------------
*Ihor Radchenko *<yantar92@posteo.net> Thu, Dec 26, 2024 at 1:02 AM

To: John C <john.ciolfi.32@gmail.com>
John C <john.ciolfi.32@gmail.com> writes:

> My apologies - I’ve been too busy. I'll update the NEWS entry per your
> suggestion once we resolve the "%-<org-eval>" comment issue you flagged.
>
> Consider this MATLAB code block and expected results.
>
>     #+begin_src matlab :exports both :results output latex
>       m = [4*pi, 3*pi; 2*pi, pi];
>       result = latex(sym(m));
>       disp(result)
>     #+end_src
> ...
> In this case, the *MATLAB* buffer contains:
>
>     >> m = [4*pi, 3*pi; 2*pi, pi];
>     result = latex(sym(m));
>     disp(result)
>     'org_babel_eoe'
>     m = [4*pi, 3*pi; 2*pi, pi];
>     >> result = latex(sym(m));
>     >> disp(result)
>     \left(\begin{array}{cc} 4\,\pi  & 3\,\pi \\ 2\,\pi  & \pi
>  \end{array}\right)
>     >> 'org_babel_eoe'
>
>     ans =
>
>         'org_babel_eoe'
>
>     >>

So, MATLAB does not echo the full code, but rather does it line-by-line.
This is not what `org-babel-comint--echo-filter' and
`org-babel-comint-with-output' expect - the usual behavior with REPLs
echoing the input is displaying the whole (multiline) input together.

> ...
> Therefore, the adding "%-<org-eval>" MATLAB comments to the code being
> evaluated, then stripping the echo'd lines containing the "%-<org-eval>"
is
> probably the best option.

What I am thinking as an alternative is modifying
`org-babel-comint-with-output' to filter FULL-BODY line by line.
For example, when REMOVE-ECHO is set to 'line.

`org-babel-comint--echo-filter' can then be passed an extra argument
that will make it filter body lines rather than the whole body only.

WDYT?
[Quoted text hidden]
------------------------------
*John C *<john.ciolfi.32@gmail.com> Fri, Dec 27, 2024 at 8:24 AM

To: Ihor Radchenko <yantar92@posteo.net>
I'm not clear on how I'd update org-babel-comint-with-output to filter
line-by-line. Could you give me a start at it? Also, I'm not sure if
line-by-line filtering would work. I suspect MATLAB echos when it sees a
new line and a full statement. For example,

    m1 = [4*pi, 3*pi; ...
          2*pi, pi];

is a multi-line single statement. The "..." are MATLAB's line continuation
syntax.

Another thought is we could update org-babel-comint-with-output to have an
option like:

     (BUFFER EOE-INDICATOR REMOVE-ECHO FULL-BODY EOL-FILTER-INDICATOR)

where optional EOL-FILTER-INDICATOR is text, typically a comment, that is
appended to each line in FULL-BODY before it is evaluated. After
evaluation, each line in the result containing EOL-FILTER-INDICATOR is
removed from the result.

This way, if another language encounters this issue, they can leverage this
service.

Thanks
John
[Quoted text hidden]
------------------------------
*Ihor Radchenko *<yantar92@posteo.net> Fri, Dec 27, 2024 at 10:26 AM

To: John C <john.ciolfi.32@gmail.com>
John C <john.ciolfi.32@gmail.com> writes:

> I'm not clear on how I'd update org-babel-comint-with-output to filter
> line-by-line. Could you give me a start at it?

For example, you can add an optional argument to
`org-babel-comint--echo-filter'. When it is passed, BODY argument is
split via `split-string' and then individual lines are removed in order
from STRING.

`org-babel-comint-with-output' is the caller of
`org-babel-comint--echo-filter'.

> ... Also, I'm not sure if
> line-by-line filtering would work. I suspect MATLAB echos when it sees a
> new line and a full statement. For example,
>
>     m1 = [4*pi, 3*pi; ...
>           2*pi, pi];
>
> is a multi-line single statement. The "..." are MATLAB's line continuation
> syntax.

AFAIU, removing line-by-line should still work in such case.

> Another thought is we could update org-babel-comint-with-output to have an
> option like:
>
>      (BUFFER EOE-INDICATOR REMOVE-ECHO FULL-BODY EOL-FILTER-INDICATOR)
>
> where optional EOL-FILTER-INDICATOR is text, typically a comment, that is
> appended to each line in FULL-BODY before it is evaluated. After
> evaluation, each line in the result containing EOL-FILTER-INDICATOR is
> removed from the result.

It may also work. Maybe even better.
Rather than just EOL-FILTER-INDICATOR, we may allow a regular expression
to be provided. Then, everything matching that regular expression will
be removed.

P.S. Is there any reason you are writing off-list? We generally try to
keep the discussion public to keep record of the decision-making.



On Mon, Dec 23, 2024 at 2:23 AM Ihor Radchenko <yantar92@posteo.net> wrote:

> Hi John,
>
> It has been a month since the last message in this thread.
> Do you need any help working on the patch?
>
> --
> Ihor Radchenko // yantar92,
> Org mode maintainer,
> Learn more about Org mode at <https://orgmode.org/>.
> Support Org development at <https://liberapay.com/org-mode>,
> or support my work at <https://liberapay.com/yantar92>
>

[-- Attachment #1.2: Type: text/html, Size: 15793 bytes --]

[-- Attachment #2: org-matlab.patch --]
[-- Type: text/x-patch, Size: 18403 bytes --]

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 5d421172f..4eedba0ae 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -162,6 +162,23 @@ bibliography format requires them to be written in title-case.
 
 # This also includes changes in function behavior from Elisp perspective.
 
+*** ob-matlab: fixed MATLAB support
+
+Fixed MATLAB babel code blocks processing. MATLAB code blocks, ~#+begin_src matlab~, with ~:results
+verbatim~, ~:results output~, ~:results output latex~, or ~:results file graphics~ now work.  Fixes
+include (1) waiting for matlab-shell to start before evaluating MATLAB code, (2) correctly showing
+the results using writematrix, (3) removing the code block lines from the result, (4) correctly
+handling graphics results by invoking print correctly. To use MATLAB with org, you need
+https://github.com/MathWorks/Emacs-MATLAB-Mode.
+
+*** ob-matlab: MATLAB behavior change
+
+MATLAB code blocks now reuse the ~MATLAB*~ buffer created by ~M-x matlab-shell~, whereas the
+prior version started a new shell for each evaluation.  The benefit of this is that
+evaluations are very fast after the first evaluation and that state is maintained between
+evaluations, which you can clear using the MATLAB ~clear~ command.  Another benefit of this
+behavior is that it is consistent with how MATLAB works.
+
 *** ob-sqlite: Added ability to open a database in readonly mode
 
 Added option :readonly to ob-sqlite.
diff --git a/lisp/ob-comint.el b/lisp/ob-comint.el
index b88ac445a..830e2245b 100644
--- a/lisp/ob-comint.el
+++ b/lisp/ob-comint.el
@@ -101,15 +101,28 @@ PROMPT-REGEXP defaults to `comint-prompt-regexp'."
        (setq string (substring string (match-end 0))))
   string)
 
+(defun org-babel-comint--strip-regexps (result strip-regexps)
+  "STRIP-REGEXPS from RESULT list of strings."
+  (dolist (strip-regexp strip-regexps)
+    (let ((new-result '()))
+      (dolist (line result)
+        (setq line (replace-regexp-in-string strip-regexp "" line))
+        (when (not (string= line ""))
+          (setq new-result (append new-result `(,line)))))
+      (setq result new-result)))
+  result)
+
 (defmacro org-babel-comint-with-output (meta &rest body)
   "Evaluate BODY in BUFFER and return process output.
 Will wait until EOE-INDICATOR appears in the output, then return
 all process output.  If REMOVE-ECHO and FULL-BODY are present and
-non-nil, then strip echo'd body from the returned output.  META
-should be a list containing the following where the last two
-elements are optional.
+non-nil, then strip echo'd body from the returned output.  If
+optional STRIP-REGEXPS, a list of regular expressions, is
+present, then all matches will be removed from the returned
+output.  META should be a list containing the following where the
+last three elements are optional.
 
- (BUFFER EOE-INDICATOR REMOVE-ECHO FULL-BODY)
+ (BUFFER EOE-INDICATOR REMOVE-ECHO FULL-BODY STRIP-REGEXPS)
 
 This macro ensures that the filter is removed in case of an error
 or user `keyboard-quit' during execution of body."
@@ -117,7 +130,8 @@ or user `keyboard-quit' during execution of body."
   (let ((buffer (nth 0 meta))
 	(eoe-indicator (nth 1 meta))
 	(remove-echo (nth 2 meta))
-	(full-body (nth 3 meta)))
+	(full-body (nth 3 meta))
+        (strip-regexps (nth 4 meta)))
     `(org-babel-comint-in-buffer ,buffer
        (let* ((string-buffer "")
 	      (comint-output-filter-functions
@@ -165,8 +179,12 @@ or user `keyboard-quit' during execution of body."
          (and ,remove-echo ,full-body
               (setq string-buffer (org-babel-comint--echo-filter string-buffer ,full-body)))
 
-         ;; Filter out prompts.
-         (org-babel-comint--prompt-filter string-buffer)))))
+         ;; Filter out prompts from returned output result.
+         (let ((result (org-babel-comint--prompt-filter string-buffer)))
+           ;; Remove all matches of STRIP-REGEXPS in returned output result.
+           (when ,strip-regexps
+             (setq result (org-babel-comint--strip-regexps result ,strip-regexps)))
+           result)))))
 
 (defun org-babel-comint-input-command (buffer cmd)
   "Pass CMD to BUFFER.
diff --git a/lisp/ob-matlab.el b/lisp/ob-matlab.el
index de8deadbe..083dcdec3 100644
--- a/lisp/ob-matlab.el
+++ b/lisp/ob-matlab.el
@@ -28,11 +28,10 @@
 
 ;;; Requirements:
 
-;; Matlab
-
-;; matlab.el required for interactive emacs sessions and matlab-mode
-;; major mode for source code editing buffer
-;; https://matlab-emacs.sourceforge.net/
+;; 1) MATLAB
+;; 2) https://github.com/mathworks/Emacs-MATLAB-Mode
+;;    For matlab-shell to run MATLAB within Emacs and matlab-mode
+;;    major mode for source code editing buffer
 
 ;;; Code:
 
diff --git a/lisp/ob-octave.el b/lisp/ob-octave.el
index 005990f20..024984835 100644
--- a/lisp/ob-octave.el
+++ b/lisp/ob-octave.el
@@ -1,4 +1,4 @@
-;;; ob-octave.el --- Babel Functions for Octave and Matlab -*- lexical-binding: t; -*-
+;;; ob-octave.el --- Babel Functions for Octave and MATLAB -*- lexical-binding: t; -*-
 
 ;; Copyright (C) 2010-2024 Free Software Foundation, Inc.
 
@@ -30,6 +30,8 @@
 
 ;;; Code:
 
+(require 'cl-seq)
+
 (require 'org-macs)
 (org-assert-version)
 
@@ -39,7 +41,52 @@
 (declare-function matlab-shell "ext:matlab-mode")
 (declare-function matlab-shell-run-region "ext:matlab-mode")
 
-(defvar org-babel-default-header-args:matlab '())
+;; With `org-babel-default-header-args:matlab' set to
+;;  '((:session . "*MATLAB*")))
+;; each matlab code block evaluation will reuse the "*MATLAB*" buffer
+;; created by `matlab-shell' for code evaluation.  The benefit of this
+;; is that evaluation is very fast for evaluations after the first
+;; evaluation. The first evaluation may be slower because it can take
+;; a long time to start MATLAB.  Reusing the session buffer means
+;; state is maintained between evaluations.  To avoid reuse of state,
+;; you can clear the MATLAB workspace.  This setting aligns with
+;; typical MATLAB use, where MATLAB is started and used for a long
+;; period of period of time.  Another benefit of this setting is that
+;; you can see the history of the evaluations in the "*MATLAB*"
+;; command window buffer.  For example:
+;;
+;;  #+begin_src matlab :results output
+;;    a = 123
+;;  #+end_src
+;;
+;;  #+RESULTS:
+;;  : a =
+;;  :
+;;  :    123
+;;
+;;  #+begin_src matlab :results output
+;;    b = a * 2
+;;  #+end_src
+;;
+;;  #+RESULTS:
+;;  : b =
+;;  :
+;;  :    246
+;;
+;;  #+begin_src matlab :results output
+;;    clear
+;;    c = b * 2
+;;  #+end_src
+;;
+;;  #+RESULTS:
+;;  : Unrecognized function or variable 'b'.
+;;
+;; If you want a new session each time you evaluate a MATLAB code block,
+;;   (setq 'org-babel-default-header-args:matlab '())
+;; However, this will make evaluations slower and is not consistent with how
+;; MATLAB works.  MATLAB is designed for many evaluations.
+(defvar org-babel-default-header-args:matlab '((:session . "*MATLAB*")))
+
 (defvar org-babel-default-header-args:octave '())
 
 (defvar org-babel-matlab-shell-command "matlab -nosplash"
@@ -47,18 +94,42 @@
 (defvar org-babel-octave-shell-command "octave -q"
   "Shell command to run octave as an external process.")
 
-(defvar org-babel-matlab-with-emacs-link nil
-  "If non-nil use matlab-shell-run-region for session evaluation.
-This will use EmacsLink if (matlab-with-emacs-link) evaluates
-to a non-nil value.")
-
-(defvar org-babel-matlab-emacs-link-wrapper-method
-  "%s
-if ischar(ans), fid = fopen('%s', 'w'); fprintf(fid, '%%s\\n', ans); fclose(fid);
-else, save -ascii %s ans
-end
-delete('%s')
+(make-obsolete-variable 'org-babel-matlab-with-emacs-link
+                        "MATLAB removed EmacsLink in R2009a." "2009")
+
+(make-obsolete-variable 'org-babel-matlab-emacs-link-wrapper-method
+                        "MATLAB removed EmacsLink in R2009a." "2009")
+
+(defvar org-babel-matlab-print "print(\"-dpng\", %S);\nans=%S;"
+  ;; MATLAB command-function duality requires that the file name be specified
+  ;; without quotes. Using: print -dpng "file.png", would produce a file with
+  ;; the quotes in the file name on disk. Therefore, use the functional form
+  ;; to handle files with spaces, print("-dpng", "file.png").
+  ;; Example:
+  ;;    #+begin_src matlab :file "sine wave.png" :results file graphics
+  ;;      t = [0 : 0.1 : 2*pi];
+  ;;      y = sin(t);
+  ;;      plot(t, y);
+  ;;      set(gcf, 'PaperUnits', 'inches', 'PaperPosition', [0 0 4 3]) % Set the size to 4" x 3"
+  ;;    #+end_src
+  ;;
+  ;;    #+RESULTS:
+  ;;    [[file:sine wave.png]]
+  "MATLAB format specifier to print current figure to a file.")
+
+(defvar org-babel-octave-print "print -dpng %S\nans=%S"
+  "Octave format specifier to print current figure to a file.")
+
+(defvar org-babel-matlab-wrapper-method
+  (concat "\
+cd('%s');
+%s
+if ~exist('ans', 'var') ans = ''; end; \
+writematrix(ans, '%s', 'Delimiter', 'tab');
 ")
+  "Format specifier used when evaluating MATLAB code blocks.
+Arguments are the `default-directory', the MATLAB code, and a result file.txt.")
+
 (defvar org-babel-octave-wrapper-method
   "%s
 if ischar(ans), fid = fopen('%s', 'w'); fdisp(fid, ans); fclose(fid);
@@ -92,7 +163,10 @@ When MATLABP is non-nil, execute Matlab.  Otherwise, execute Octave."
 				 (list
 				  "set (0, \"defaultfigurevisible\", \"off\");"
 				  full-body
-				  (format "print -dpng %S\nans=%S" gfx-file gfx-file))
+				  (format (if matlabp
+                                              org-babel-matlab-print
+                                            org-babel-octave-print)
+                                          gfx-file gfx-file))
 				 "\n")
 		    full-body)
 		  result-type matlabp)))
@@ -153,6 +227,23 @@ If there is not a current inferior-process-buffer in SESSION then
 create.  Return the initialized session.  PARAMS are src block parameters."
   (org-babel-octave-initiate-session session params 'matlab))
 
+(defun org-babel-matlab-shell ()
+  "Start and/or wait for MATLAB shell."
+  (require 'matlab-shell) ;; make `matlab-shell-busy-checker' available
+  (cond
+   ((fboundp 'matlab-shell-busy-checker)
+    ;; Start the shell if needed.  `matlab-shell' will reuse existing if already running.
+    (matlab-shell)
+    ;; If we just started the matlab-shell, wait for the prompt.  If we do not
+    ;; wait, then the startup messages will show up in the evaluation results.
+    (matlab-shell-busy-checker 'wait-for-prompt))
+   (t
+    (message (concat "You version of matlab-mode is old.\n"
+                     "Please update, see https://github.com/mathworks/Emacs-MATLAB-Mode\n"
+                     "Updating will eliminate unexpected output in your results\n"))
+    (sit-for 3)
+    (matlab-shell))))
+
 (defun org-babel-octave-initiate-session (&optional session _params matlabp)
   "Create an octave inferior process buffer.
 If there is not a current inferior-process-buffer in SESSION then
@@ -165,9 +256,15 @@ Octave session, unless MATLABP is non-nil."
   (unless (string= session "none")
     (let ((session (or session
 		       (if matlabp "*Inferior Matlab*" "*Inferior Octave*"))))
-      (if (org-babel-comint-buffer-livep session) session
+      (if (org-babel-comint-buffer-livep session)
+          (progn
+            (when (and matlabp (fboundp 'matlab-shell-busy-checker))
+              ;; Can't evaluate if the matlab-shell is currently running code
+              (matlab-shell-busy-checker 'error-if-busy))
+            session)
 	(save-window-excursion
-	  (if matlabp (unless org-babel-matlab-with-emacs-link (matlab-shell))
+	  (if matlabp
+              (org-babel-matlab-shell)
 	    (run-octave))
 	  (rename-buffer (if (bufferp session) (buffer-name session)
 			   (if (stringp session) session (buffer-name))))
@@ -183,66 +280,104 @@ value of the last statement in BODY, as elisp."
       (org-babel-octave-evaluate-session session body result-type matlabp)
     (org-babel-octave-evaluate-external-process body result-type matlabp)))
 
+(defun org-babel-octave-wrapper-tmp-file (matlabp)
+  "Return a local tmp file with name adjusted for MATLABP."
+  (if matlabp
+      ;; writematrix requires a file ending with '.txt'
+      (org-babel-temp-file "matlab-" ".txt")
+    (org-babel-temp-file "octave-")))
+
+(defun org-babel-octave-get-code-to-eval (body tmp-file matlabp)
+  "Format BODY of the code block for evaluation saving results to TMP-FILE.
+If MATLABP, format for MATLAB, else format for Octave."
+    (if matlabp
+        (format org-babel-matlab-wrapper-method default-directory body tmp-file)
+      (format org-babel-octave-wrapper-method body tmp-file tmp-file)))
+
 (defun org-babel-octave-evaluate-external-process (body result-type matlabp)
-  "Evaluate BODY in an external Octave or Matalab process.
+  "Evaluate BODY in an external Octave or MATLAB process.
 Process the result as RESULT-TYPE.  Use Octave, unless MATLABP is non-nil."
   (let ((cmd (if matlabp
 		 org-babel-matlab-shell-command
 	       org-babel-octave-shell-command)))
     (pcase result-type
       (`output (org-babel-eval cmd body))
-      (`value (let ((tmp-file (org-babel-temp-file "octave-")))
+      (`value (let ((tmp-file (org-babel-process-file-name
+                               (org-babel-octave-wrapper-tmp-file matlabp)
+                               'noquote)))
 	        (org-babel-eval
 		 cmd
-		 (format org-babel-octave-wrapper-method body
-			 (org-babel-process-file-name tmp-file 'noquote)
-			 (org-babel-process-file-name tmp-file 'noquote)))
+                 (org-babel-octave-get-code-to-eval body tmp-file matlabp))
 	        (org-babel-octave-import-elisp-from-file tmp-file))))))
 
+(defun org-babel-body-for-output (body matlabp)
+  "If MATLABP, fixup BODY for MATLAB output result-type."
+  (when matlabp
+    ;; When we send multi-line input to `matlab-shell', we'll see the "body"
+    ;; code lines echoed in the output which is not what one would expect.  To
+    ;; remove these unwanted lines, we append a comment "%-<org-eval>" to each
+    ;; line in the body MATLAB code.  After we collect the results from
+    ;; evaluation, we leverage the "%-<org-eval>" to remove the unwanted lines.
+    ;; Example of desired behavior:
+    ;;     #+begin_src matlab :results output
+    ;;       disp('The results are:')
+    ;;       a = [1, 2; 3, 4]
+    ;;       b = a * 2
+    ;;     #+end_src
+    ;;
+    ;;     #+RESULTS:
+    ;;     #+begin_example
+    ;;     The results are:
+    ;;
+    ;;     a =
+    ;;
+    ;;          1     2
+    ;;          3     4
+    ;;
+    ;;     b =
+    ;;
+    ;;          2     4
+    ;;          6     8
+    ;;     #+end_example
+    (setq body (replace-regexp-in-string "\n" " %-<org-eval>-\n" body))
+    (when (not (string-match "\n\\'" body))
+      (setq body (concat body " %-<org-eval>-"))))
+  body)
+
 (defun org-babel-octave-evaluate-session
     (session body result-type &optional matlabp)
   "Evaluate BODY in SESSION."
-  (let* ((tmp-file (org-babel-temp-file (if matlabp "matlab-" "octave-")))
-	 (wait-file (org-babel-temp-file "matlab-emacs-link-wait-signal-"))
+  (let* ((tmp-file (org-babel-octave-wrapper-tmp-file matlabp))
 	 (full-body
 	  (pcase result-type
 	    (`output
 	     (mapconcat
 	      #'org-babel-chomp
-	      (list body org-babel-octave-eoe-indicator) "\n"))
+	      (list (org-babel-body-for-output body matlabp)
+                    org-babel-octave-eoe-indicator)
+              "\n"))
 	    (`value
-	     (if (and matlabp org-babel-matlab-with-emacs-link)
-		 (concat
-		  (format org-babel-matlab-emacs-link-wrapper-method
-			  body
-			  (org-babel-process-file-name tmp-file 'noquote)
-			  (org-babel-process-file-name tmp-file 'noquote) wait-file) "\n")
-	       (mapconcat
-		#'org-babel-chomp
-		(list (format org-babel-octave-wrapper-method
-			      body
-			      (org-babel-process-file-name tmp-file 'noquote)
-			      (org-babel-process-file-name tmp-file 'noquote))
-		      org-babel-octave-eoe-indicator) "\n")))))
-	 (raw (if (and matlabp org-babel-matlab-with-emacs-link)
-		  (save-window-excursion
-		    (with-temp-buffer
-		      (insert full-body)
-		      (write-region "" 'ignored wait-file nil nil nil 'excl)
-		      (matlab-shell-run-region (point-min) (point-max))
-		      (message "Waiting for Matlab Emacs Link")
-		      (while (file-exists-p wait-file) (sit-for 0.01))
-		      "")) ;; matlab-shell-run-region doesn't seem to
-		;; make *matlab* buffer contents easily
-		;; available, so :results output currently
-		;; won't work
-		(org-babel-comint-with-output
-		    (session
-		     (if matlabp
-			 org-babel-octave-eoe-indicator
-		       org-babel-octave-eoe-output)
-		     t full-body)
-		  (insert full-body) (comint-send-input nil t))))
+	     (mapconcat
+	      #'org-babel-chomp
+	      (list (org-babel-octave-get-code-to-eval body tmp-file matlabp)
+		    org-babel-octave-eoe-indicator)
+              "\n"))))
+	 (raw-results
+	  (org-babel-comint-with-output
+	      (session
+	       (if matlabp
+		   org-babel-octave-eoe-indicator
+		 org-babel-octave-eoe-output)
+	       t full-body ;; Remove echo'd full-body from result
+               (when matlabp
+                 '(;; MATLAB echo's all input lines, so use the %-<org-eval> comments to strip
+                   ;; them from the output
+                   "^[^\n]*%-<org-eval>-\n"
+                   ;; Remove starting blank line caused by stripping %-<org-eval>
+                   "\\`[[:space:]\r\n]+"
+                   ;; Strip <ERRORTXT> and </ERRORTXT> matlab-shell error indicators
+                   "</?ERRORTXT>\n")))
+	    (insert full-body) (comint-send-input nil t)))
 	 results)
     (pcase result-type
       (`value
@@ -250,11 +385,11 @@ Process the result as RESULT-TYPE.  Use Octave, unless MATLABP is non-nil."
       (`output
        (setq results
 	     (if matlabp
-		 (cdr (reverse (delete "" (mapcar #'org-strip-quotes
-					          (mapcar #'org-trim raw)))))
+                 (cdr (reverse (delete "" (mapcar #'org-strip-quotes
+					          (mapcar #'org-trim raw-results)))))
 	       (cdr (member org-babel-octave-eoe-output
 			    (reverse (mapcar #'org-strip-quotes
-					     (mapcar #'org-trim raw)))))))
+					     (mapcar #'org-trim raw-results)))))))
        (mapconcat #'identity (reverse results) "\n")))))
 
 (defun org-babel-octave-import-elisp-from-file (file-name)

  parent reply	other threads:[~2024-12-29  3:44 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-11-08 19:30 ob-octave: improve MATLAB support John C
2024-11-09  9:32 ` Ihor Radchenko
2024-11-13 19:10   ` John C
2024-11-15 13:29     ` John C
2024-11-23 15:57     ` Ihor Radchenko
     [not found]       ` <87o712lv3t.fsf@localhost>
2024-12-29  3:04         ` John C [this message]
2024-12-29  7:42           ` Ihor Radchenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CACb3vdQ7GufyWXdMMTJKJ_wgz4Cf0-XNF4fnmq1x=-w9He-g-Q@mail.gmail.com' \
    --to=john.ciolfi.32@gmail.com \
    --cc=emacs-orgmode@gnu.org \
    --cc=yantar92@posteo.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).