Skip to content

mzimmerm/ob-dart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This document is a (relatively) short description of using Dart as one of the languages in Emacs org-mode Babel.

Version 2.0.0

New in Version 2.0.0

Added support to put in source blocks correct Dart code including main() method.

If the Org Babel Dart code block has the top level main() method, it is assumed that user does not want ob-dart to mangle in any way, and run the code as-is. But such source blocks with main() currently only support :results output (anything after output still supported, e.g. :results output raw

Examples of new 2.0.0 feature

The added feature in version 2.0.0 is support for pure Dart code that contains classes, top level functions, and main() in source blocks.

Only :results output work. :results value behaves as output.

import 'dart:math';

/// Complete Dart code snippet can be wrapped.
main(List args) {
  var listMax = [10,20,30].reduce(max);
  print ('WE HAVE MAIN');
  print ('In output mode, all printed lines show in result');
  print ('List max printed = ' + listMax.toString());
  return ('Results: value!! List from MAIN');
  }

The next example has some Async code, so it runs 4 seconds.

import 'dart:async';
import 'dart:math';
import 'dart:io' as io; // sleep is here

/// Complete Dart code snippet can be wrapped.

class Classroom {
  int size = 3;
  List<String> pupils = ['Andrea', 'Monica', 'Andrew'];
}

Future<Classroom> classroomWithDelay() async {
  // In Async Code
  // If NO await below, flow would continue immediately after being called,
  //   Classroom inside returned Future would be null,
  //   which is OK in this case, but if the Classroom was obtained
  //   remotely, Classroom inside Future would be null,
  //   and classroom.size would fail on null when called almost immediately.

  // The "then" WILL NOT await unless asked.
  // Prints after this call happen before Classroom ready, but otherwise
  // no null issues.
  return await Future.delayed(Duration(seconds: 4)).then((value) => Classroom());
  // In Sync Code
  // io.sleep(Duration(seconds:1));
}

main(List args) async {
  // create the future here. It can be created anywhere
  // With await inside classroomWithDelay(), the code awaits inside
  // BUT FLOW CONTINUES BELOW, BECAUSE classroomWithDelay() IS ASYNC,
  // SO THE WORLD FORKS BELOW
  print('1. body7: START');
  print('2. before futureClassroom');
  Future<Classroom> futureClassroom = classroomWithDelay();
  print('3. after futureClassroom');
  // var listMax = [10, 20, 30].reduce(max);
  // print('List max printed = ' + listMax.toString());

  print('4. before futureClassroom.then. Will wait 4s if await called below.');

  // "then((classroom) => processClassroom)" is necessary.
  // No other way to pull Classroom from Future
  //
  // WITHOUT await below, print('7. body7: END'); would be printed before classroom print (!!)
  //   other than that, classroom info is printed OK, no null issues.
  // The reason for the switch (without await below) is that futureClassroom
  //   is async (rather it's a future), and FLOW FORKS on discovering any ASYNC
  //   code or Future variable.
  await futureClassroom.then((Classroom classroom) {
    print('5. inside futureClassroom.then: Class size = ${classroom.size}, pupils=${classroom.pupils}');
    print('6. end of futureClassroom.then');
    return ('NO SHOW - returns to OS: Results: value!! List from MAIN');
  });
  print('7. body7: END');
}

TODOS todos

  1. See todo in ob-dart.el
  2. Currently we have:
    • If body has main
      • ob-dart uses block unchanged, with imports and everything.
      • Only :results output supported
    • Else
      • the full contents of block is wrapped in the Gen code wrapper in ob-dart-wrapper
  3. Change it to
    • If body has main
      • todo-v3 : Allow blocks with “main” also be wrapped in Zone so “results: value” is supported
      • Implementation
        • e.g. “Future<type> main(List args) async \{”
        • e.g. “type main(List args) async \{”
        • parse out args name and put to args-name
        • parse out async and put to async-str
        • if eq async-str async, set await-str await
        • Future<type> there
          • put to return-type
        • else if type there
          • put to return-type
        • else return-type=void
        • replace the body main line with line $return-type originalMain(List $args-name) $async-str
        • setq call-original-main “return originalMain($args-name);”
        • appending and formatting full-body:
          • concat
            • “import ‘dart:async’;”
            • body
            • format-spec ob-dart-wrapper `((?a . async-str) (?w . await-str) (?s . ,call-original-main))
    • Else body does not match main
      • todo-v2 also allow imports, classes, top level functions from top in that order, followed by non-main code.
      • Implementation:
        • Require that source block author adds some markers at the end of certain block:
          • after imports // ob-dart-block-imports-end, after classes // ob-dart-block-classes-end, after top funcions // ob-dart-block-functions-end
        • pull sections out of body, create ‘body-top-extracted’ from ‘ob-dart-wrapper-imports’, and sections above in that order. These will not be formatted, and placed where ‘ob-dart-wrapper-imports’ are now
        • create ‘body-main-extracted’ formatted and wrapped as current body
        • so the full-body will be created as body-top-extracted wrapped body-main-extracted))

About this Package: Org Babel Dart (ob-dart).

This emacs package (ob-dart.el) supports running Dart code in Emacs org-mode (Babel source blocks).

A Simple Example of running Dart code in Emacs org-mode (Babel source blocks)

After installation (see the Installation from MELPA section), try this:

  • In this README.org file, viewed in emacs
  • Put cursor anywhere inside the #+begin_src ... #+end_src block below (this block is hidden if you are reading this on Github)
  • Hit C-c C-c (holding down the Ctrl button, hit c twice). Thi will run the code snippet in Dart, and show result below in the #+results section.
return [1,2, 11,15].reduce(max);
15

There are more examples in the Examples section.

Installation of ob-dart from MELPA

ob-dart prerequisite - Dart language is installed on your system, for example in $HOME/software/dart-sdk

If you do not have Dart installed, follow steps below to install Dart first

  • Download and extract Dart from https://www.dartlang.org/downloads/
  • For the rest of this installation, let us assume Dart was extracted to $HOME/software/dart-sdk
  • Add the Dart installation bin directory to your PATH. Add to your profile (such as .bashrc or .bash_profile if you use bash) :
    export PATH=${PATH}:$HOME/software/dart-sdk/bin
        

Installing ob-dart and adding Dart to list of Org Babel supported languages

Installation from MELPA

The ob-dart package is now published on MELPA, so, assuming you have MELPA in the list of your package repositories, you can install ob-dart directly from Emacs. Follow the steps below:

  • In emacs, use the M-x package-list-packages command.
    • Search for the string ob-dart, and put your cursor on the ob-dart line.
    • Press the i key (select line for Install).
    • Press the x key (Execute installs).

Post installation: Adding Dart to list of Org Babel supported languages

Current post-installation

Currently, even though you now have ob-dart installed, you need to add Dart to the list of Org Babel supported languages.

  • Add the following code to your emacs init file (such as $HOME/.emacs.d/init.el)
    ;; For Dart to appear as one of the values of 
    ;;         customize-variable org-babel-load-languages, add this code:
    
    (require 'ob-dart)
    (add-to-list 'org-babel-load-languages  '(dart . t))
    
        

Future post-installation

After we add Dart to Babel list of supported languages, we will be able to just add Dart support using customize (as opposed to org-babel-do-load-languages)

  • In emacs, use the M-x customize-variables command
  • On the prompt, respond org-babel-load-languages
  • In the Org Babel Load Languages section, INS (insert) a new item, named Dart, and click State->Save for future session

Last step: Test a Dart code block in a org file. To do this, you can edit this file README.org in emacs, and follow the description of the simple example above.

Alternative to installation from MELPA (probably no reason to use)

It you are not using MELPA, following the steps below will ensure everything needed is installed.

  • Make sure org-mode is installed in your emacs. Recent versions of emacs do include org mode.
  • Download and install ob-dart.el in emacs from this Github repository:
    • Download ob-dart.el from Github https://github.com/mzimmerm/ob-dart and save it to a directory. For the rest of this installation, let us assume you have saved ob-dart.el in $HOME/.emacs.d/ob-dart.el
    • Add the following code to your emacs init file (such as $HOME/.emacs.d/init.el)
      ;; Step 1: Add ob-dart to /path/to/ob-dart.el, for example:
      
      (load-file "~/.emacs.d/ob-dart.el") 
      (require 'ob-dart)
      
      ;; Step 2: For Dart to appear as one of the values of 
      ;;         customize-variable org-babel-load-languages, add this code:
      
      (org-babel-do-load-languages
       'org-babel-load-languages
       '(
         (dart . t)
         ;; other languages may be added here
         ;; (python . t)
         ;; etc
         )
       )
              
  • Once you have ob-dart installed, test a Dart code block in a org file. To do this, you can edit this file README.org in emacs, and follow the description of the simple example above.

About Emacs Org Mode, and Babel

Org Mode (org-mode) is a mode for editing files in text, in a “wysiwyggy” way.

(org-mode) Babel is used in literal programming, reproducible research, for documentation and more.

You can read about org-mode and org-mode babel on these links:

How Org Babel Dart works

Before executing Dart code between the #+begin_src and #+end_src, a temporary file is generated with several standard Dart library imports (core, async, collection etc) on top. Below, the code is wrapped in a main() method. This temporary file is then run as command line dart. Org mode inserts it’s output back in the document in the #+RESULTS section, just below the code.

This is the similar as code between the #+begin_src and #+end_src in main(), executed from Dart, with all security implications.

Current Limitations

  1. Major: The :var Input to org babel code blocks is not supported in this ob-dart version (neither scalar variables nor tables).
  2. Medium: The section of code between #+begin_src and #end_src can only run Dart code that would normally be placed inside a top-level Dart function (top-level functions: see https://www.dartlang.org/dart-tips/dart-tips-ep-6.html ). Ob-dart wraps this code as main() { begin_src to end_src }. This is to support the main intended use of Babel to write functions in a mix of languages in a simple way. As a result of this implementation, the ability to run “any” Dart code that would normally be placed in a file and run as if we ran dart my-app.dart is missing. See Resolving Current Limitations for detail discussion.
  3. Medium: Ability to pass a flag specifying to run in checked / production mode
  4. Medium: Need to figure out how to support packages. Should support packages.yaml somehow. How is this done in dartpad?.
  5. Medium: Asserts failures cause org mode result formatting error. Likely an org-mode issue
  6. Minor(?): Missing support for Org Babel “session mode” which allows to run Dart in and “incremental” mode (as in iPython/Jupiter): This may not be resonably doable at this time, as Dart does not have a REPL yet - although it looks like the vm_service_client may allow to write a Dart REPL. So perhaps one day.
  7. Minor(?): Strings outputted by Dart to stdio by methods other than print() (e.g. loggers?) would still show up in the :results value mode. Need to look more into loggers, not sure how to resolve this yet. Maybe this is not so important due to the audience size.

A Brief Presentation of Dart using this package (ob-dart)

Dart already has excellent tools for learning and quickly running Dart code and code snippets, such as https://dartpad.dartlang.org/. The usefulness of this package (Dart in org mode) is thus to be seen.

Perhaps it can be useful to make use of the easy editing in org mode, and then use the amazing org-mode tools to convert org documents to other formats, ODT, html, PDF and others. So having Dart working in org mode babel can be used for documenting, generating pdf, or html for blogs or pages that need include Dart code and results.

The following paragraph is a simple example of how Org Babel Dart might be used.

Using Org Babel Dart - quick summary.

Dart Language basics

This table shows Dart basics.

SyntaxDesc
// This is a comment in DartComment
var length = 10;Variable declaration, untyped
print("Hello");print to stdout
etc

Dart sample code in org babel.

As an example of a piece of Dart code in an Org document is below. If we place the cursor in the source code block between #+begin_src and #+end_src and enter C-c C-c (Control down, enter the c key twice), the Dart code will be eveluated. The evaluation result will be inserted after the code block in a new block with header #+RESULTS:

var str = "hello" + " there";
print (str == "hello there");
print (str == "not hello there");
true
false

The text placed in #+RESULTS: block is determined by the arguments of the source code block. In the example above, we wanted to show the standard output in the #+RESULTS: block, so we used:

:results output

If we were to export the Org documents, say to PDF, both source code and the results would appear in the PDF. This is because we specify:

:exports both

Conditionals (flow control)

We can use any valid Dart code, including functions, except class definitions.

Here we use if..else for flow control.

var status = false;
if (status) {
  print ('Status was true');
} else {
  print('Status was false');
}
Status was false

Presenting :results value and :results output and :results output(or value) raw

Examples show the rather boring differences between various collection types (:results output/value with potential format raw). See http://orgmode.org/manual/results.html

First, source block which asks for :results value should result in the string representation of the last statement in the source block which must be marked with the ~return~ keyword.

var listMax = [1,2,3].reduce(max);
print  ("In output mode, all printed lines show in result");
print  ("List max printed = " + listMax.toString());
return  "List max returned = " + listMax.toString(); // Note: bug in Org export (C-c C-e h o) prevents a syntactically correct:   return  "List max returned = ${listMax}"; 
List max returned = 3

The same source block which asks for :results value table should result in the string representation of the last statement, converted to a Org-table on pipe characters if the resulting object is a collection. As the result is not a collection, the whole string representation is surrounded with pipe characters as one table cell.

var listMax = [1,2,3].reduce(max);
print  ("In output mode, all printed lines show in result");
print  ("List max printed = " + listMax.toString());
return  "List max returned = " + listMax.toString();
List max returned = 3

To output an actual table, return a list. Like this:

return [1,2];

todo fix this regression

[1 (, 2)]
12

Or if you want to return a table with headers, like this:

return [ 
  ["col_1", "col_2"], // no spaces in headers; default impl breaks on them
  [1,       2],
  [3,       4]
];

todo fix this regression

[[col_1 (, col_2)] (, [1 (, 2)]) (, [3 (, 4)])]
col_1col_2
12
34

Next, evaluation of a source block which asks for :results output results in showing every string in the code which was directed to stdout (all print statements are directed).

var listMax = [1,2,3].reduce(max);
print  ("In output mode, all printed lines show in result");
print  ("List max printed = " + listMax.toString());
return  "List max returned = " + listMax.toString();
In output mode, all printed lines show in result
List max printed = 3

In this example, a table is correctly ignored with :results output, showing quoted results, as shown below:

var listMax = [1,2,3].reduce(max);
print  ("In output mode, all printed lines show in result");
print  ("List max printed = " + listMax.toString());
return  "List max returned = " + listMax.toString();
In output mode, all printed lines show in result
List max printed = 3

:results value raw and :results output raw do not add any formatting to the result, and results appear as regular text, as shown below. Also note that because org mode joins lines of regular text, multiple printed lines of results are joined.

Result of :results value raw:

var listMax = [1,2,3].reduce(max);
print  ("In output mode, all printed lines show in result");
print  ("List max printed = " + listMax.toString());
return  "List max returned = " + listMax.toString();

List max returned = 3

Result of :results output raw

var listMax = [1,2,3].reduce(max);
print  ("In output mode, all printed lines show in result");
print  ("List max printed = " + listMax.toString());
return  "List max returned = " + listMax.toString();

In output mode, all printed lines show in result List max printed = 3

Resolving Current Limitations

Below, a discussion for each numbered item in the Limitations section.

  1. :var not passed to Dart. Should deal with this first, for Dart code blocks to play nice in org context, and accept, rather than just return, information.
  2. Code that will work (and not work) inside the #+begin_src and #end_src.
    • Issues with solving this limitation: I want to add support for “any” Dart code soon, so functions, classes, and methods can be defined, then used in Org Babel Dart. Ideally, any valid Dart code that would run from the Dart command line can be pasted in the Org code sections and support the basic results modes. But this would make it impossible to support the :results value, because the Dart main() function does not return a value. Currently, ob-dart works around the :results value problem by wrapping the code and a combination pf running Zoned to ignore print(), and relying on return present in the org code, wraping it as print(). But to solve this in a general case, would require a deeper level of code manipulation either with emacs Semantic or Dart Analyser (https://github.com/dart-lang/sdk/tree/master/pkg/analyzer) (to wrap a return as print or similar).
    • Suggested solutions: I think for now I arrived at supporting the following “Styles” - When Org Babel Dart code uses any of the styles below, it will work without adding further org mode special flags, headers, or markers.
      • Dart Style Top Level Functional: This is the currently supported style.The #+begin_src and #end_src section can contain any code that can be inside a top-level Dart function without any class context from “above” the top level method. Some basic imports are added before the conde runs. Both “:results value” and “:results output” do work as expected.
        • Valid examples (this works becaue functions can be nested, so this works wrapped in main):
          square(x) {
            return x * x;
          }
          return square(2);
                          
          4
                          
          var x = 1.5;
          var y = 2;
          return max(pow(x, 4), pow(y, 2));
                          
          5.0625
                          
  • Invalid Example (does not work because class cannot be nested in a function, and we are wrapping all code in main())
    /* nesting class in a top-level function fails
    class C {
      square(x) {
        return x * x;
      }
    }
    var c = new C();
    return c.square(2);
    */ 
        
    null
        
  • Dart Style Aided Functional: This will be extension of the mode above. It will allow to define classes above code, and use them in code. It will require user to enter a special marker in code; code above the marker will be evaluated on top level, and so classes and functions defined above the marker can be used below it. This will make the example from above valid. Both “:results value” and “:results output” will work as expected.
    • Valid Example (does work because we split code on the marker, and only wrap the code below the “separator” string)
      /* todo - uncomment once support added
      class C {
        square(x) {
          return x * x;
        }
      }
      // Org-Dart-Functional
      var c = new C();
      return c.square(2);
      */
              
    • todo: provide an invalid example
  • Dart Style Dart Program: This will be different from either styles above. Any fully valid Dart program can be entered; it must include the main() method. Only “:results output” will be a valid option, “:results value” will cause an error..
    • Valid Example:
      /* todo - uncomment once support added
      class C {
        square(x) {
          return x * x;
        }
      }
      main() {
        var c = new C();
        print( c.square(2) );
      }
      */ 
              
      null
              

Security

Do not execute randomly downloaded code in Org Babel. Do not execute code you do not understand. There is no guarantee using insecure code such as “delete all” will not harm your data.. The issues would be similar to running the code as dart some-file.dart.

As a result, use at own risk. There are no guarantees running a random code safely - please read the org-mode babel documentation regarding security.

Todos (apart from resolving the limitations above)

  1. Check language of ob-dart.el comments:
  2. Add a babel directive :import if specified, the wrapper will not add any import packages. Imported packages must be in code (later, we may allow to specify and list in the :import directive)

Bugs

  1. :results value table does not allow space in the header name.
    return [ 
      ["col 1", "col 2"],
      [1,       2],
      [3,       4]
    ];
        

    This works e.g. in python, but in Dart it adds columns on spaces:

    col1col2
    12
    34

Notes

  1. Code for inclusion of ob-dart on Melpa (likely of no interest to anyone, just a note to the author). This recipy was submitted to https://github.com/melpa/melpa/tree/master/recipes/ob-dart using following steps
    • Using the Github Gui, created a recipe for Melpa ob-dart, and added a Github Pull Request for it’s inclusion:
      • Forked https://github.com/melpa from the Github Gui
      • Added and comitted to the fork a file melpa/recipes/ob-dart with contents here
        (ob-dart
          :fetcher github
          :repo "mzimmerm/ob-dart")
                    
        • Initiated the pull request (=asking someone to review and merge my new code from my Melpa fork to Melpa master)
          • Navigated the forked mzimmerm/melpa repository with the changes I want someone else to pull and merge
          • Pressed the “Pull Requests” button.
          • Pressed the “Create pull request” button.
          • There is some dialog, the requires to push another “Create pull request” button at the bottom.
      • An owner of Melpa will review the request and merge to Melpa master or follow with comments.

About

ob-dart allows for Dart code snippets to be used in Emacs org-babel source code.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published