Author Topic: Clojure in Cad - a taste of things to come  (Read 7292 times)

0 Members and 1 Guest are viewing this topic.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Clojure in Cad - a taste of things to come
« on: November 02, 2014, 10:18:42 PM »
I've been tinkering with a few ideas today and got a little Clojure interpreter happening within Bricscad, very rough at this point and proof of concept. Pretty simple though.
You will need to download clojureclr from: https://github.com/clojure/clojure-clr/wiki/Getting-binaries
then ref the clojure.dll into the project.

To use it create a C# class library project and add the following, change your lib's as required:
Code - C#: [Select]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.IO;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8.  
  9. using Teigha.Runtime;
  10. using Teigha.DatabaseServices;
  11. using Bricscad.ApplicationServices;
  12. using Bricscad.EditorInput;
  13.  
  14. using _AcRx = Teigha.Runtime;
  15. using _AcAp = Bricscad.ApplicationServices;
  16. using _AcDb = Teigha.DatabaseServices;
  17. using _AcEd = Bricscad.EditorInput;
  18.  
  19. using clojure;
  20.  
  21. [assembly: CommandClass(typeof(ClojureCad.Commands))]
  22. namespace ClojureCad
  23. {
  24.     class Commands
  25.     {
  26.         [CommandMethod("CLJCAD")]
  27.         public static void CljCad()
  28.         {
  29.             Editor ed = _AcAp.Application.DocumentManager.MdiActiveDocument.Editor;
  30.             clojure.lang.Compiler.loadFile("C:\\testcad.clj");
  31.             PromptResult res = ed.GetString("Enter a clojure expression within \"\": ");
  32.             char[] trimchars = {'"'};
  33.             string cljstring = res.StringResult.Trim(trimchars);
  34.             object o = clojure.lang.RT.readString(cljstring);
  35.             object result = clojure.lang.Compiler.eval(o);
  36.             ed.WriteMessage("Result: " + result.ToString());
  37.         }
  38.  
  39.         [CommandMethod("CLJCAD1")]
  40.         public static void CljCad1()
  41.         {
  42.             Editor ed = _AcAp.Application.DocumentManager.MdiActiveDocument.Editor;
  43.             clojure.lang.Compiler.loadFile("C:\\testcad.clj");
  44.             PromptResult res = ed.GetString("Enter a clojure command: ");
  45.             var foo = clojure.lang.RT.var("cljcad", res.StringResult);
  46.             object o = foo.invoke();
  47.             ed.WriteMessage("Result: " + o.ToString());
  48.         }
  49.     }
  50. }
  51.  

And here's a very simple Clojure script file:
Code - Auto/Visual Lisp: [Select]
  1. (ns cljcad) ;; namespace for this file
  2.  
  3. (defn command1 []
  4.   (str "Simple test to see clojure is connected!"))
  5.  
  6. (defn command2 []
  7.   (str "Another command, getting boring now :)"))
  8.  

here's how it works:
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #1 on: November 02, 2014, 10:23:48 PM »
Ok, nothing very suprising there...yet.

The real benefit is that I can edit my script and run it again without having to Netload all the time, I can make a quick edit and run it again.
The other bonus is I can use any .net framework as well as the ARX .net wrappers, if you are happy with your script you can then compile it to a dll proper and netload it like any other class lib.

Very early stages yet, I will be trying to add a few things to the db next and try to hook up a proper repl so you can code on the fly and test things out.

There's very little help out there for clojure-clr so it will be uphill for a while if you're interested.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #2 on: November 02, 2014, 11:34:45 PM »
a bit more testing the limits of using the command line:

Quote
: CLJCAD
Enter a clojure expression within "": "(defn tester [] (str "tester
: CLJCAD
Enter a clojure expression within "": "(defn tester [] (+ 1 2))"
Result: #'clojure.core/tester
: CLJCAD
Enter a clojure expression within "": "(tester)"
Result: 3
: CLJCAD
Enter a clojure expression within "": "(defn add2 [n1 n2] (+ n1 n2))"
Result: #'clojure.core/add2
: CLJCAD
Enter a clojure expression within "": "(add2 4 5)"
Result: 9

The limit is when using parens or quoted strings in Editor.GetString() Bricscad wants to exit and interpret it as a lisp expression right away, does anyone have a work around for this?
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8698
  • AKA Daniel
Re: Clojure in Cad - a taste of things to come
« Reply #3 on: November 03, 2014, 01:57:46 AM »
Hi Mick,
Cool stuff!  Instead of get string, maybe you could use LispFunction

Code: [Select]
       [LispFunction("Clojure")]
        public static ResultBuffer MyClojureLispFunc(ResultBuffer args)
        {
            ResultBuffer buf = new ResultBuffer();
            foreach (var item in args)
            {
                if ((LispDataType)item.TypeCode == LispDataType.Text)
                {
                    object o = clojure.lang.RT.readString(item.Value.ToString());
                    object result = clojure.lang.Compiler.eval(o).ToString();
                    buf.Add(new TypedValue((int)LispDataType.Text, result));
                }
            }
            return buf;
        }

Code: [Select]
(Clojure "(+ 1 3)" "(+ 3 9)")

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8698
  • AKA Daniel
Re: Clojure in Cad - a taste of things to come
« Reply #4 on: November 03, 2014, 02:36:33 AM »
Code: [Select]
    public static class MyClojureFuncs
    {
        static List<object> loaded = new List<object>();


        //(LoadClojure "C:\\Temp\\testcad.clj")
        [LispFunction("LoadClojure")]
        public static object LoadClojure(ResultBuffer args)
        {
            foreach (var item in args)
            {
                if ((LispDataType)item.TypeCode == LispDataType.Text)
                {
                    loaded.Add(clojure.lang.Compiler.loadFile(item.Value.ToString()));
                }
            }
            return true;
        }

        //(CL "cljcad" "command1")
        [LispFunction("CL")]
        public static object CL(ResultBuffer args)
        {
            String func = String.Empty;
            String space = String.Empty;
            String result = String.Empty;
            List<TypedValue> _args = new List<TypedValue>(args.AsArray());

            if ((LispDataType)_args[0].TypeCode == LispDataType.Text)
            {
                space = _args[0].Value.ToString();
            }
            if ((LispDataType)_args[1].TypeCode == LispDataType.Text)
            {
                func = _args[1].Value.ToString();
            }
            var foo = clojure.lang.RT.var(space, func);
            object o = foo.invoke();
            return o.ToString();
        }
    }

: (LoadClojure "C:\\Temp\\testcad.clj")
T
: (CL "cljcad" "command1")
"Simple test to see clojure is connected!"

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #5 on: November 03, 2014, 02:42:29 AM »
Cool Daniel! Thanks for that, I know little of the LispFunction attribute, works well:

: (clj "(+ 3 4)")
("7")
: (clj "(defn my-add [n1 n2] (+ n1 n2))")
("#'clojure.core/my-add")
: (clj "(my-add 3 4)")
("7")
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #6 on: November 03, 2014, 04:30:58 AM »
Here's an updated script file contents so far, it now has an add-line command, loving the conciseness of this code, the imports are a bit of a drag though :)

Code - Auto/Visual Lisp: [Select]
  1. (ns cljcad
  2.         (:import (Teigha.DatabaseServices Database SymbolUtilityServices Transaction BlockTable BlockTableRecord Line OpenMode))
  3.         (:import (Teigha.Geometry Point3d))
  4.         (:import (Bricscad.ApplicationServices Application)))
  5.  
  6. (defn command1 []
  7.   (str "Simple test to see clojure is connected!"))
  8.  
  9. (defn command2 []
  10.   (str "Another command, getting boring now :)"))
  11.  
  12. (defn- add-to-db ;; private function -> defn-
  13.         "Adds an AcDbEntity to ModelSpace of the current database"
  14.         [entity]
  15.         (let [ db (.. Application DocumentManager MdiActiveDocument Database)
  16.                         tr (.. db TransactionManager StartTransaction)
  17.                         bt (.GetObject tr (. db BlockTableId) OpenMode/ForWrite)
  18.                         btr(.GetObject tr (. SymbolUtilityServices GetBlockModelSpaceId db) OpenMode/ForWrite)
  19.                   ]
  20.         (let [id (.AppendEntity btr entity)]
  21.         (doto tr
  22.                 (.AddNewlyCreatedDBObject entity true)
  23.                 (.Commit)
  24.                 (.Dispose))
  25.                 id)))
  26.        
  27. (defn add-line
  28.         []
  29.         (let [ line (Line. (Point3d. 20.0 20.0 0.0) (Point3d. 200.0 50.0 0.0))]
  30.                  (add-to-db line)))
  31.  
« Last Edit: November 03, 2014, 04:45:54 AM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Clojure in Cad - a taste of things to come
« Reply #7 on: November 03, 2014, 04:35:29 AM »

Skipping along the bleeding edge again I see, Mick :-)

Looks interesting.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #8 on: November 03, 2014, 04:54:48 AM »

Skipping along the bleeding edge again I see, Mick :-)

Looks interesting.

It is Kerry!

I haven't netloaded for hours, just save the script and try again.
It's taken me hours to get my head around the interop though but it's coming together. :)
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #9 on: November 04, 2014, 01:05:41 AM »
Here is the latest 'script' file contents that clean up a few things and I've commented the code to explain some of the macros and special form functions in Clojure to explain how they work.

Code - Auto/Visual Lisp: [Select]
  1. ;; Here we declare a namespace 'ns cljcad' and we 'import' the lib's we want to use
  2. ;; in our script. As we are using lib's from the host application we don't need to 'require' them.
  3. (ns cljcad
  4.         (:import (Teigha.DatabaseServices Database SymbolUtilityServices
  5.                                 Transaction BlockTable BlockTableRecord Line OpenMode))
  6.         (:import (Teigha.Geometry Point3d Vector3d))
  7.         (:import (Bricscad.ApplicationServices Application)))
  8.  
  9. (defn command1 []
  10.   (str "Simple test to see clojure is connected!"))
  11.  
  12. (defn add-to-db
  13.         ;; This is a doc string, can be read from a repl with (doc fn-name)
  14.         "Adds an AcDbEntity to ModelSpace of the current database
  15.         Returns the ObjectId of the Entity added to the db."
  16.         [entity]
  17.         ;; get the database:
  18.         (let [ db (.. Application DocumentManager MdiActiveDocument Database)]
  19.                 ;; with-open is the same as 'using' in C#.
  20.                 ;; The '..' macro allows you to dig deep into multiple levels of class hierarchy.
  21.                 (with-open [tr (.. db TransactionManager StartTransaction)]
  22.                         ;; set up the block table and block table record objects:
  23.                         ;; The '.method-name' macro lets you call a method on the object (.method instanceObj)
  24.                         ;; The '. ' macro is like the '..' but only a single level deep
  25.                         ;; The '/' is for getting enums or static methods from an object - obj/enum or Class/staticMethod
  26.                         (let [  bt (.GetObject tr (.BlockTableId db) OpenMode/ForWrite)
  27.                                         btr(.GetObject tr (. SymbolUtilityServices GetBlockModelSpaceId db) OpenMode/ForWrite)]
  28.                                 ;; here we bind the returned object id from AppendEntity to return later:
  29.                                 (let [id (.AppendEntity btr entity)]
  30.                                         ;; 'doto' is like chaining actions to the object we are using, a transaction in this case.
  31.                                         (doto tr
  32.                                                 (.AddNewlyCreatedDBObject entity true)
  33.                                                 (.Commit))
  34.                                                 id))))) ;; returns the object id
  35.                                                
  36. ;; Typical function definition:
  37. ;; 'defn' is a special form to denote function creation,
  38. ;; params passed to the function are within '[]' and you can have more than one 'arity'
  39. ;; of params (think overloading). You may then have a doc string and then the body of the function.
  40. ;; An example for the function below with multiple arity might be -
  41. ;; (defn add-line
  42. ;;              []              ;; no args
  43. ;;              [p1 p2]         ;; 2 args passed in
  44. ;;              ;;body that uses the args passed or uses defaults
  45. ;;              ( ... ))
  46. (defn add-line
  47.         []
  48.         ;; To create new instances of objects we can use (Obj. args) or (new Obj args)
  49.         (let [ line (Line. (Point3d. 20.0 20.0 0.0) (Point3d. 200.0 50.0 0.0))]
  50.                  ;; add it to the db:
  51.                  (add-to-db line)))
  52.  
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8698
  • AKA Daniel
Re: Clojure in Cad - a taste of things to come
« Reply #10 on: November 04, 2014, 05:45:11 AM »
How to namespaces work? Is it per loaded script?
Can you add functions to an existing namespace?
Maybe one add a global namespace that imports all of the references…

clojure.lang.Namespace cSpace = clojure.lang.Compiler.CurrentNamespace;
cSpace.importClass(/* import all the cad stuff to */);
clojure.lang.Compiler.loadFile(item.Value.ToString());

or

clojure.lang.Compiler.loadFile(item.Value.ToString());
clojure.lang.Namespace cSpace = clojure.lang.Compiler.CurrentNamespace;
cSpace.importClass(/* import all the cad stuff to */);

just a thought

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #11 on: November 04, 2014, 02:26:52 PM »
How to namespaces work? Is it per loaded script?
Can you add functions to an existing namespace?
I'm not 100% sure but they are similar to JavaScript namespaces in that they are just a name to avoid name clashes in the symbol table.
I think they also work like Java namespaces in that the names can refer to folder and file structure for when 'require' -ing them, I'll have to experiment.
e.g. (require  '(foo/bar/baz)) would grab the baz.clj file from the bar sub folder of foo.

Quote
Maybe one add a global namespace that imports all of the references…
clojure.lang.Namespace cSpace = clojure.lang.Compiler.CurrentNamespace;
cSpace.importClass(/* import all the cad stuff to */);
clojure.lang.Compiler.loadFile(item.Value.ToString());

or

clojure.lang.Compiler.loadFile(item.Value.ToString());
clojure.lang.Namespace cSpace = clojure.lang.Compiler.CurrentNamespace;
cSpace.importClass(/* import all the cad stuff to */);

just a thought

I'll take a look at that, I think I would set it up so you just have a 'scripts' or 'plugins' folder that Clojure would search each sub folder and load all the files on start up.

My next task is to hook up a REPL so you can enter code and test it live, that will be pretty cool to edit and test arx code snippets on the fly. I would like to hook it up with a simple text editor like Notepadd++ as well but it will most likely end up with emacs at first as emacs is one of the most advanced in this area with Clojure.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #12 on: August 25, 2016, 07:36:50 AM »
Back at it again, this time with a bit more effort :)

Got a pretty sweet setup going now with a simple text editor with syntax colouring and bracket matching with the help of the ScintillaNET code editor control which is pretty cool.
I've hand built the repl and so far it gets the job done but needs a lot more work to make it less error prone.

The workflow so far is to write some code in the editor then you can select it and send it to the repl for evaluation which loads it into the system. From there on you can call your functions in the repl and see the results in the CAD editor. You can just type the same in the repl as well but I mainly use it to call functions I've loaded.
It handles exceptions and syntax errors pretty well for what it is and is quite usable.

What makes this really special though is you can develop your app using the repl for testing and refactoring, no need to restart the CAD system and netload, just select and load your revised function and test again, pretty sweet!
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Clojure in Cad - a taste of things to come
« Reply #13 on: August 26, 2016, 08:49:08 AM »
It is interesting. Can you use the breakpoints and conditional breakpoints in this code editor?

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Clojure in Cad - a taste of things to come
« Reply #14 on: August 26, 2016, 09:02:00 AM »
Sory, it is not about Clojure, but it can be interesting for C# programmers: http://avalonedit.net/

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #15 on: August 26, 2016, 07:32:02 PM »
Sory, it is not about Clojure, but it can be interesting for C# programmers: http://avalonedit.net/


Nice find, will take a look, thanks.

With all of these user controls, you need to do a fair bit of setup work to get the required functionality, especially if you are using a language that is not built into the control.
You need to handle keyboard shortcuts and things like syntax colouring and behaviour.
They do save a lot of work but I wouldn't mind building one from scratch one day just for shiggles. The repl side is hand built using a simple textbox control and has been fun so far and will need some major refactoring once the prototype is working.

It is interesting. Can you use the breakpoints and conditional breakpoints in this code editor?

No, and they are not really required. Clojure does come with some inspection tools but no debugger.

As Clojure is immutable by default a lot of the pain of setting state is removed, this is the main reason we even need a debugger in imperative languages, to check state.
Most functions are pure functions in that they only return an output given certain input and there are no side effects such as setting var's. These pure functions are very easy to test and plug together to form larger functions.

There are ways to have side effects and you need them as you will need to eventually to set/change say an entity's properties with your calculated data, but these can be isolated, tested and viewed with a simple print statement or similar inspection function with output to the repl.

The repl is key here, it allows you test and debug as you code. Enter your function in the repl and test it, if it has a problem you can fix it and retest all in the same session, you have instant feedback.

The current process in C# is:
write code -> build & deploy -> test/debug -> refactor/fix bugs -> repeat until done then final deploy

The process in Clojure will be:
write code -> test/debug -> refactor/fix bugs -> repeat until done then build & deploy

I still have to iron out a few things and finish some basic functionality like loading and saving files and make the repl remember past commands then I will post up a demo with an example of the workflow and how testing can be built in.

The goal of this tool is for 'live' development of code, this code can then be used to build a class library dll just like a C# one. I think I can even do this from the repl and at this point is the only way to have commands registered but while testing, calling from the repl is basically the same.



"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #16 on: August 26, 2016, 08:12:29 PM »
Here's a contrived example of how I would go about testing, IOW I wouldn't double up on the functions like this :)
I would also have a 'tests' file to load and run all tests as a test suite as a separate process rather than include them in the source.
If there was an error such as using a string as an input the repl would catch this as well but these tests are logic tests and they work well, particularly for pure type functions.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Clojure in Cad - a taste of things to come
« Reply #17 on: August 27, 2016, 02:20:16 AM »
Thank you for the detailed response.