syntree
is a package for GNU Emacs that draws plain text constituency
trees. It is meant to be for plain text what packages like qtree or
forest are for LaTeX, providing only those features that make sense
for the plain text format.
While it is written with the linguist’s usage in mind, syntree
can
obviously be used for any sort of tree-like diagram. Here’s an
example of what you can get from syntree
:
("Sentence" "_NP:the man" ("VP" "Verb:hit" ("NP" "T:the" "N:ball"))) Sentence ._____|_____. | | NP VP .__|__. .___|___. |_____| | | the man Verb NP hit .__|__. | | T N the ball
The drawing is done interactively, with the output displayed as you type:
It does not provide functionalities such as drawing arrows between nodes. Once the tree is drawn, however, arrows are rather easily added manually with picture-mode (built-in in GNU Emacs).
You can install syntree
through melpa.
Upon calling the command syntree-new
a new frame is created that is
split in two windows: the upper one visits the input buffer, the lower
one visits the output buffer. Both buffers are in syntree-mode
.
At this point, syntree-mode-map
is activated in the two buffers (more
on this here). The output buffer will redisplay the tree every time
the input buffer has changed and a valid syntree input is detected in
it (or whenever the value of syntree--current-style
is changed by
calling some interactive command—more on that below).
The input is a substring of the buffer made up by parentheses and
objects formatted as strings. This input is read
as a list. Thus,
these are valid inputs:
("abc" ("def" "ghi"))
("abc" "def" "ghi")
Every list must contain more than one string. These are not valid inputs:
"abc" "def" ; this is not a list
("abc" ("def" ghi)) ; not everything here is a string
("abc" ("def")) ; the embedded list contains only one string
The first string in a list is the label of the branching node. Here, the embedded constituent has an empty label:
("XP" "abc" ("" "def" "ghi" "X:xyz")) XP ____|____ / \ | | abc ____|____ / | \ | | X def ghi xyz
Terminal nodes can themselves be labeled: the label and the text are
separated by a :
(i.e., a colon). Here some examples of terminals:
("" "XP:abcd") | | | XP | abcd | -----------------+------- | ("" "abcd") | | | abcd | -----------------+------- | ("YP" ":abcd") | YP | | | abcd | -----------------+------- | ("YP" ":ab:cd") | YP | | | ab:cd
If the label on the terminal node starts with, or is _
, the leaf
will be under some sort of roof. For linguists, this usually
indicates that the internal syntax of that node is ignored.
("DP" "D:a" "_NP:beautiful tree") ("DP" "D:a" "_:beautiful tree") DP DP .____|____. .____|____. | | | | D NP D .______|_____. a .______|_____. a |____________| |____________| beautiful tree beautiful tree
Whether you are under a roof or not, if you want to force the text of
a terminal node or of a label to be on multiple lines, use \n
in the
input.
("Modified \n sentence" "NP:the man" "_VP:vigorously\nhit the ball") Modified sentence ._____|_____. | | NP VP the man ._____|____. |__________| vigorously hit the ball
Whether the input string in the buffer is split in different lines or not, and whether it is indented in any way or not is irrelevant.
These are all the commands that are available in syntree-mode
buffers,
each with the default keybinding. You can define your own keybindings
in syntree-mode-map
.
Read the documentation for syntree
in the info system.
Change the value of any properties, with completion, and redisplay the tree according to the new value.
Note that changing values this way does not change the value of the property in the current style, neither permanently nor for the duration of the Emacs session. The value is simply temporarily overwritten. If, after changing a value you reset the style, the change will be discarded.
Increase and reduce (if possible) the value of :hspace
(see
properties).
Note that changing values this way does not change the value of the property in the current style, neither permanently nor for the duration of the Emacs session. The value is simply temporarily overwritten. If, after changing a value you reset the style, the change will be discarded.
Increase and reduce (if possible) the value of :height
(see
properties).
Note that changing values this way does not change the value of the property in the current style, neither permanently nor for the duration of the Emacs session. The value is simply temporarily overwritten. If, after changing a value you reset the style, the change will be discarded.
Select a different style defined in syntree-styles-list
, with
completion, as the current one, and redisplay the tree.
What the current style is right after syntree-new
is called is determined
by the value of syntree-default-style
(more on this
here).
Once you are satisfied with how the tree looks like, you can call
syntree-done
: the input and the output (the source for the tree and
the tree itself) are added to the kill ring (in that order), the two
syntree buffers and their dedicated frame are killed, and point is
back in the position it was when syntree-new
was called. You can now
yank the tree.
syntree
produces a tree according to a specific style. A style is a
plist that associates properties to values. Interactively, one can
switch between predefined styles.
The properties defining how a tree looks like can be divided in two: the first group has booleans, integers or symbols as value, the second has strings or characters. We first examine the first group.
This property accepts one of these four symbols as value:down
up
right
left
It determines the direction of growth of the tree: for example, if the
value is right
, the root of the tree will be to the left and the tree
will horizontally grow to the right.
This property has a boolean value.
If it is t
, it means that all terminal nodes will be aligned
(horizontally or vertically depending on the value of :growing).
This property accepts an integer as value.
It determines the amount of space between two sister nodes. It can be
increased or reduced interactively with syntree-increase-padding
and
syntree-reduce-padding
.
Values smaller than 1 are interpreted as the smallest graphically viable value.
Note that if the tree grows horizontally, :hspace
ends up determining
vertical space between nodes.
This property accepts an integer as value.
It determines the length of the stem connecting mother and daughter
nodes. It can be increased or reduced interactively with
syntree-increase-height
and syntree-reduce-height
.
Values smaller than 1 are interpreted as the smallest graphically viable value.
Note that if the tree grows horizontally, :height
ends up determining
a horizontal dimension despite its name.
A positive value means that the roof will be larger than the text under it, a negative one, shorter.
:roofwidth 0 | .___|___. |_______| long leaf :roofwidth 2 | ._____|_____. |___________| long leaf :roofwidth -1 | .__|__. |_____| long leaf
This property accepts an integer as value.
It determines what the minimal width of a roof is. If its value is,
say, 5
, no roof will be drawn (despite what the input is) if the width
of the text under it and the value of :roofwidth determine that it
would have to be less than 5
characters wide.
Values smaller than 1 are interpreted as the smallest graphically viable value.
This property accepts an integer as value.A value smaller than 1 means that strings in the input are never broken in different lines except at new line characters. Any other value causes the string to be word wrapped at that given length: for instance, a value of 4 will cause the string “young boy” to be split in two.
:word-wrap 0 | young boy :word-wrap 4 | young boy
Note that depending on the value of :growing, the wrapped text will have different apperance. If the trees grows vertically, the text is centered. If it grows horizontally, it is flushed left or right:
:word-wrap 4 :growing right ---young boy :word-wrap 4 :growing left young boy--
This property has a boolean value.
If it is t
, it means that the width or height of the tree (depending
on whether the value of :growing
is down
/ up
or right
/ left
respectively) is reduced as much as possible. Nodes can end up being
“stacked” on each other.
:compress t .______|_____. | | NP ._____|____. the man | | ADV VP vigorously hit the ball :compress nil .________|_______. | | NP ._____|____. the man | | ADV VP vigorously hit the ball
These are all properties that determine which character is used to draw each part of the tree. The values can be strings (containing one character) or single characters.
Below is an example tree (vertical, growing down) that illustrates which component of the tree is associated with which property.
:hbranch "1" :branchcenter "2" :intersection "3" :l-intersection "4" :r-intersection "5" :stem "6" :l-stem "7" :r-stem "8" :roofstem "9" :rooftop "A" :rooftopangle "B" :roofbottom "C" :roofbottomangle "D" ("Sentence" "_NP:the young women" "Aux:may" ("VP" "Verb:hit" ("NP" "T:the" "N:ball"))) Sentence 6 6 411111111111111211111111111115 7 6 8 6 6 VP NP Aux 6 BAAAAAAAA9AAAAAAAAB may 6 DCCCCCCCCCCCCCCCCCD 411111211115 the young women 7 8 6 NP Verb 6 hit 6 411121115 7 8 6 6 T N the ball
syntree
comes with four built-styles to provide you with a basis to
define your own (see here). That is, the predefined value of
syntree-styles-list
is:
'((:name basic
:der nil
:growing down
:hspace 0
:one-line nil
:height 0
:hbranch "_"
:branchcenter "|"
:intersection "."
:l-intersection "."
:r-intersection "."
:stem "|"
:l-stem "|"
:r-stem "|"
:roofstem "|"
:rooftop "_"
:rooftopangle "."
:roofbottom "_"
:roofbottomangle "|"
:roofwidth 0
:roofminwidth 3
:word-wrap 0
:compress t)
(:name horizontal
:der nil
:growing right
:hspace 0
:one-line nil
:height 2
:hbranch "|"
:branchcenter "+"
:intersection "+"
:l-intersection "+"
:r-intersection "+"
:stem "-"
:l-stem "-"
:r-stem "-"
:roofstem "|"
:rooftop "|"
:rooftopangle "|"
:roofbottom ""
:roofbottomangle ""
:roofwidth 0
:roofminwidth 1
:word-wrap 5
:compress t)
(:name basic-upwards
:der basic
:growing up
:hbranch "-"
:branchcenter "+"
:intersection "+"
:l-intersection "+"
:r-intersection "+"
:rooftop "-"
:roofbottom " "
:roofstem "+"
:roofbottomangle " "
:rooftopangle "+")
(:name basic-one-line
:der basic
:one-line t))
Defining a new style is a matter of adding a new properly constructed
plist to the list syntree-styles-list
.
There are two special properties that must be defined in every style:
:name
:der
Both accept a symbol as value. :name
specifies the name of the style,
:der
specifies the name of the style from which the present one
derives.
For example, say that I want to define a new style, new-horizontal,
that is identical to the built in one named horizontal
except for two
things: I want trees in new-horizontal
style to grow from right to
left and to have all terminals of the tree be aligned.
The briefest way to define new-horizontal
is thus:
(add-to-list
'syntree-styles-list
'(:name new-horizontal
:der horizontal
:one-line t
:growing left))
All properties that are not defined in new-horizontal
are looked up in
horizontal
.
If the value of :der
is nil
, then all of the properties described in
properties must be defined.
basic
is the style that trees are designed according to
when syntree-new
is called. If you want new-horizontal
(the one defined
here) to be the default, set the value of
syntree-default-style
accordingly:
(setq syntree-default-style 'new-horizontal)
syntree
provides you with a syntree src block
that you can
use like any other source block in you org-mode buffer. By writing an
input string in such a block, you can evaluate it like any other
source block and the output tree will be returned as the result of the
evaluation, under the block.
.#+begin_src syntree ("Sentence" "_NP:the young women" "Aux:may" ("VP" "Verb:hit" ("NP" "T:the" "N:ball"))) .#+end_src
You can control the design of the tree, deviating from whatever
syntree-default-style
specifies, by passing the specifications for
:style
and all the other properties as parameters of the source block.
For example, the block below when evaluated will return a tree in
my-style
with value 8 and “l” for :height
and :stem
respectively.
.#+begin_src syntree :style 'my-style :height 8 :stem "l" ("Sentence" "_NP:the young women" "Aux:may" ("VP" "Verb:hit" ("NP" "T:the" "N:ball"))) .#+end_src