Added Elisp code, updated README

This commit is contained in:
Phuntsok Drak-pa 2019-08-14 02:16:40 +02:00
parent 9eccaba263
commit a1a7799523
3 changed files with 277 additions and 154 deletions

View File

@ -1,11 +1,27 @@
[[file:https://cdn.rawgit.com/syl20bnr/spacemacs/442d025779da2f62fc86c2082703697714db6514/assets/spacemacs-badge.svg]] [[file:https://cdn.rawgit.com/syl20bnr/spacemacs/442d025779da2f62fc86c2082703697714db6514/assets/spacemacs-badge.svg]]
* Features Tree * Table of content :TOC_5_gh:noexport:
- [[#tree-to-dot][Tree to dot]]
- [[#presentation][Presentation]]
- [[#usage][Usage]]
- [[#tldr][TL;DR]]
- [[#elisp][Elisp]]
- [[#scheme][Scheme]]
- [[#more-details][More details]]
- [[#elisp-1][Elisp]]
- [[#scheme-1][Scheme]]
- [[#my-elements-are-not-alignedcentered-what-do][My elements are not aligned/centered, what do?]]
- [[#how-can-i-do-that-on-windows][How can I do that on Windows?]]
- [[#scheme-2][Scheme]]
- [[#elisp-2][Elisp]]
- [[#license][License]]
* Tree to dot
** Presentation ** Presentation
*Features Tree* is a small utility for linguists and especially conlangers *Tree to dot* is a small utility for linguists and especially conlangers that
that allows them to declare trees with any number of children per node. I allows them to declare trees with any number of children per node. I
especially made it with the option in mind to make feature contrastive trees especially made it with the option in mind to make feature contrastive trees
as presentented by Joseph Windsor in his talk during the /Language Creation as presentented by Joseph Windsor in his talk during the /Language Creation
Conference 8/, with examples below. Conference 8/, with examples below.
@ -18,163 +34,213 @@
*** TL;DR *** TL;DR
In your *NIX terminal, clone the project, edit the example trees or create a **** Elisp
new one, and execute this:
#+BEGIN_SRC sh Load this source code within Emacs, either in a ~emacs-lisp-mode~ buffer or
cd features-tree in a source block in your ~org-mode~ buffer. Create trees, pass them as
chicken-csc features-tree.scm # Compile the .scm file arguments to ~tree-to-dot~, execute, and voilà.
./features-tree | dot -Tpng -o output.png # for PNG output
./features-tree | dot -Tsvg -o output.svg # for SVG output **** Scheme
#+END_SRC
In your *NIX terminal, clone the project, edit the example trees or create
a new one, and execute this:
#+BEGIN_SRC sh
cd features-tree
chicken-csc features-tree.scm # Compile the .scm file
./features-tree | dot -Tpng -o output.png # for PNG output
./features-tree | dot -Tsvg -o output.svg # for SVG output
#+END_SRC
*** More details *** More details
For now, the workflow is not the best, as you have to edit yourself the **** Elisp
source code and re-compile it each time you edit your own tree.
You will have to declare a Scheme list containing your tree, and your The Elisp code was written with the intent of being used from org-mode in
typical node should be declared like so: order to create inline images out of code. Put that source code in a code
#+BEGIN_SRC scheme block in org-mode, and declare it as a noweb block.
("text" (child1) (child2) ...) #+BEGIN_SRC org
#+END_SRC ,#+NAME: process-tree
Each child is itself a tree that should follow the same type of declaration, ,#+BEGIN_SRC emacs-lisp :exports none :noweb yes
with as many child as you like per node I discourage you to have more than Code here!
nine children though, otherwise it might break the output. If a node does ,#+END_SRC
not have any child, it should be declared like so: #+END_SRC
#+BEGIN_SRC scheme Dont forget to name your source blocks, it will be important for the noweb
("text") part later.
#+END_SRC
As an example, here is the tree that was used to declare the first example Then, you can declare later a beautiful tree in another code block, like
image: so, and call your ~tree-to-dot~ function on it:
#+BEGIN_SRC scheme #+BEGIN_SRC org
(define vowels ,#+NAME: my-tree
'("[vowel]" ,#+BEGIN_SRC emacs-lisp :noweb yes :exports none
("[back]" (defvar mytree
("[tense]" '("Tree"
("[high]" ("ü")) ("First child"
("{high}" ("ö"))) ("First childs child"))
("{tense}" ("Second child"
("[high]" ("u")) ("Second childs first child")
("{high}" ("o")))) ("Second childs second child"))))
("{back}" ,#+END_SRC
("[tense]" #+END_SRC
("[high]" ("y"))
("{high}" ("ë"))) You can now finally create one last code block in order to get your image:
("{tense}" #+BEGIN_SRC org
("[high]" ("i")) ,#+BEGIN_SRC dot :file whatever.png :var input=my-tree :exports results
("{high}" ("e")))))) $input
#+END_SRC ,#+END_SRC
And here is the source code of the second example image: #+END_SRC
#+BEGIN_SRC scheme You can now export this last code block only to get your image. It will be
(define syntax-tree '("S" automatically evaluated when you export your org buffer, but you can also
("Obl") manually trigger the evaluation by typing ~C-c C-c~ with your cursor on
("S'" this last code block. Be sure to have enabled the dot language in babel and
("NPerg" to have configured it properly ([[https://www.orgmode.org/worg/org-contrib/babel/languages/ob-doc-dot.html][this]] might help).
("NP"))
("VP" **** Scheme
("NPdat"
For now, the workflow is not the best, as you have to edit yourself the
source code and re-compile it each time you edit your own tree.
You will have to declare a Scheme list containing your tree, and your
typical node should be declared like so:
#+BEGIN_SRC scheme
("text" (child1) (child2) ...)
#+END_SRC
Each child is itself a tree that should follow the same type of
declaration, with as many child as you like per node I discourage you to
have more than nine children though, otherwise it might break the output.
If a node does not have any child, it should be declared like so:
#+BEGIN_SRC scheme
("text")
#+END_SRC
As an example, here is the tree that was used to declare the first example
image:
#+BEGIN_SRC scheme
(define vowels
'("[vowel]"
("[back]"
("[tense]"
("[high]" ("ü"))
("{high}" ("ö")))
("{tense}"
("[high]" ("u"))
("{high}" ("o"))))
("{back}"
("[tense]"
("[high]" ("y"))
("{high}" ("ë")))
("{tense}"
("[high]" ("i"))
("{high}" ("e"))))))
#+END_SRC
And here is the source code of the second example image:
#+BEGIN_SRC scheme
(define syntax-tree '("S"
("Obl")
("S'"
("NPerg"
("NP")) ("NP"))
("VP'" ("VP"
("NPabs" ("NPdat"
("NP" ("NP"))
("S") ("VP'"
("NP'" ("NPabs"
("Adj") ("NP"
("N")))) ("S")
("V'" ("NP'"
("Mood") ("Adj")
("Tense") ("N"))))
("V") ("V'"
("Neg"))))))) ("Mood")
#+END_SRC ("Tense")
("V")
("Neg")))))))
#+END_SRC
Once youve declared the tree you want to get, modify the last line of the Once youve declared the tree you want to get, modify the last line of the
source code =(tree-to-dot ...)= by replacing the default argument with the source code =(tree-to-dot ...)= by replacing the default argument with the
name of your tree. For the first example, we would call name of your tree. For the first example, we would call =(tree-to-dot
=(tree-to-dot vowels)=, while for the second we would call vowels)=, while for the second we would call =(tree-to-dot syntax-tree)=.
=(tree-to-dot syntax-tree)=.
*Only one =(tree-to-dot)= call can be done at once, else what follows might *Only one =(tree-to-dot)= call can be done at once, else what follows might
break!* break!*
Once youve done that, compile your file! I personally use [[https://call-cc.org/][Chicken]] as my Once youve done that, compile your file! I personally use [[https://call-cc.org/][Chicken]] as my
Scheme compiler, but if you already have another, you can use your own. Just Scheme compiler, but if you already have another, you can use your own.
replace my calls to =chicken-csc= by your own compilers command. Also, be Just replace my calls to =chicken-csc= by your own compilers command.
aware that I use =chicken-csc= as the command for che Chicken compiler, but Also, be aware that I use =chicken-csc= as the command for che Chicken
if you also use Chicken, you might have to call =csc= instead (this might compiler, but if you also use Chicken, you might have to call =csc= instead
mean you have an older version than the one I use). (this might mean you have an older version than the one I use).
Now that youve compiled your file, you will have to execute it. If your Now that youve compiled your file, you will have to execute it. If your
edits were alright, you should have some text output that looks like this, edits were alright, you should have some text output that looks like this,
except that it will be way more compact. except that it will be way more compact.
#+BEGIN_SRC dot #+BEGIN_SRC dot
graph{ graph{
node[shape=plaintext]; node[shape=plaintext];
graph[bgcolor="transparent"]; graph[bgcolor="transparent"];
0[label="[vowel]"]; 0[label="[vowel]"];
1[label="[back]"]; 1[label="[back]"];
0 -- 1; 0 -- 1;
11[label="[tense]"]; 11[label="[tense]"];
1 -- 11; 1 -- 11;
111[label="[high]"]; 111[label="[high]"];
11 -- 111; 11 -- 111;
1111[label="ü"]; 1111[label="ü"];
111 -- 1111; 111 -- 1111;
112[label="{high}"]; 112[label="{high}"];
11 -- 112; 11 -- 112;
1121[label="ö"]; 1121[label="ö"];
112 -- 1121; 112 -- 1121;
12[label="{tense}"]; 12[label="{tense}"];
1 -- 12; 1 -- 12;
121[label="[high]"]; 121[label="[high]"];
12 -- 121; 12 -- 121;
1211[label="u"]; 1211[label="u"];
121 -- 1211; 121 -- 1211;
122[label="{high}"]; 122[label="{high}"];
12 -- 122; 12 -- 122;
1221[label="o"]; 1221[label="o"];
122 -- 1221; 122 -- 1221;
2[label="{back}"]; 2[label="{back}"];
0 -- 2; 0 -- 2;
21[label="[tense]"]; 21[label="[tense]"];
2 -- 21; 2 -- 21;
211[label="[high]"]; 211[label="[high]"];
21 -- 211; 21 -- 211;
2111[label="y"]; 2111[label="y"];
211 -- 2111; 211 -- 2111;
212[label="{high}"]; 212[label="{high}"];
21 -- 212; 21 -- 212;
2121[label="ë"]; 2121[label="ë"];
212 -- 2121; 212 -- 2121;
22[label="{tense}"]; 22[label="{tense}"];
2 -- 22; 2 -- 22;
221[label="[high]"]; 221[label="[high]"];
22 -- 221; 22 -- 221;
2211[label="i"]; 2211[label="i"];
221 -- 2211; 221 -- 2211;
222[label="{high}"]; 222[label="{high}"];
22 -- 222; 22 -- 222;
2221[label="e"]; 2221[label="e"];
222 -- 2221; 222 -- 2221;
} }
#+END_SRC #+END_SRC
If you get some errors, then you fucked up somewhere in your tree, probably If you get some errors, then you fucked up somewhere in your tree, probably
missing some parenthesis or you forgot to add the ='= before the first missing some parenthesis or you forgot to add the ='= before the first
parenthesis after the name of your tree. Go back to your source file and fix parenthesis after the name of your tree. Go back to your source file and
that. Also, it might be easier to edit the file if you have a decent text fix that. Also, it might be easier to edit the file if you have a decent
editor, Id recommend using something along the lines of VS Code, Atom or text editor, Id recommend using something along the lines of VS Code, Atom
Brackets, or even Emacs if you are not afraid by steep but extremely or Brackets, or even Emacs if you are not afraid by steep but extremely
rewarding learning curves. rewarding learning curves.
Now, you need to have [[https://graphviz.org/][Graphviz]]s dot tool installed to generate images. In Now, you need to have [[https://graphviz.org/][Graphviz]]s dot tool installed to generate images. In
your terminal, either redirect the output of your newly compiled program your terminal, either redirect the output of your newly compiled program
like so: like so:
#+BEGIN_SRC sh #+BEGIN_SRC sh
./features-tree | dot -Tpng -o output.png ./features-tree | dot -Tpng -o output.png
#+END_SRC #+END_SRC
Or simply copy and paste the output in a separate file, then only run the Or simply copy and paste the output in a separate file, then only run the
dot part of the above command. Youve got an ~output.png~ file containing dot part of the above command. Youve got an ~output.png~ file containing
your tree now! your tree now!
*** My elements are not aligned/centered, what do? *** My elements are not aligned/centered, what do?
@ -184,10 +250,17 @@
*** How can I do that on Windows? *** How can I do that on Windows?
IDK. Get a UNIX terminal (like the Linux subsystem, Putty(?) or Cygwin) and **** Scheme
apply what has been said before, maybe. If you have a better explanation,
you are more than welcome to either send it with a new issue or a pull IDK. Get a UNIX terminal (like the Linux subsystem, Putty(?) or Cygwin) and
request. apply what has been said before, maybe. If you have a better explanation,
you are more than welcome to either send it with a new issue or a pull
request.
**** Elisp
This should work properly with Emacs on Windows, provided youve configured
properly org-babel. I havent tested it though.
* License * License

50
tree-to-dot.el Normal file
View File

@ -0,0 +1,50 @@
(defun declare-node (node-text node-generation)
"Declares a node in the graphviz source code. The nodes identifier will be
~node-generation~, and it will bear the label ~node-text~."
(concat (number-to-string node-generation)
"[label=\""
node-text
"\"];"))
(defun make-link (previous-node current-node)
"This creates a link in the graphviz source code between the two nodes
bearing ~previous-node~ and ~current-node~ respectively as their node
identifier."
(concat (number-to-string previous-node) " -- "
(number-to-string current-node) ";"))
(defun tree-to-dot-helper (tree current-generation previous-generation)
"Helper to ~tree-to-dot~ that translates an Elisp tree with any number of
children per node to a corresponding graphviz file that can be executed from
dot.
Arguments:
- tree :: tree-to-convert
- current-generation :: Generation number, incremented when changing from a node
to another node from the same generation, multiplied by 10 when going from
a node to one of its children.
- previous-generation :: generation number from previous named node"
(cond
((null tree) "")
((atom (car tree)) ;; '("text" () () ())
(concat (declare-node (car tree) current-generation)
(make-link previous-generation current-generation)
(tree-to-dot-helper (cdr tree)
(+ 1 (* 10 current-generation))
current-generation)))
((listp (car tree)) ;; '(() () ())
(concat (tree-to-dot-helper (car tree) ;; child of current node
current-generation
previous-generation)
(tree-to-dot-helper (cdr tree)
(+ 1 current-generation)
previous-generation)))))
(defun tree-to-dot (tree)
"Returns a graphvizs dot compatible string representing an Elisp tree"
(interactive)
(if (null tree) ""
(concat
"graph{node[shape=plaintext];graph[bgcolor=\"transparent\"];"
(declare-node (car tree) 0)
(tree-to-dot-helper (cdr tree) 1 0)
"}")))