1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! 構文木の型
//!
//! 分子は原子を節とする木である。1 つの原子から結合が何本も出ていて、その先にまた原子がある。
//! 枝も本鎖も同じ形で持つ。枝は括弧で書かれるが、木の上では出ていく結合の 1 本にすぎない。
//!
//! 環だけは木にならない。最後の結合が最初の原子へ戻るためである。
//! 環は、その最初の原子となる節が [`Ring`] として抱える。
/// 結合の種別
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
/// 単結合 -
Single,
/// 二重結合 =
Double,
/// 三重結合 ~
Triple,
/// くさび >。太い側が入る側
Wedge,
/// くさび <。太い側が出る側
WedgeBack,
/// 破線のくさび >:。太い側が入る側
Hash,
/// 破線のくさび <:。太い側が出る側
HashBack,
/// 中を塗らないくさび >|。太い側が入る側
Hollow,
/// 中を塗らないくさび <|。太い側が出る側
HollowBack,
}
impl Kind {
/// くさびのたぐいかどうか
pub fn is_cram(&self) -> bool {
!matches!(self, Kind::Single | Kind::Double | Kind::Triple)
}
/// 太い側が出る側かどうか
pub fn is_reversed(&self) -> bool {
matches!(self, Kind::WedgeBack | Kind::HashBack | Kind::HollowBack)
}
}
/// 結合の向きの決め方
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Angle {
/// 書かれていない。鎖の中では真横(東)へ、環の頂点からは環の外へ向く
Default,
/// 東を 0 度、反時計回りを正とする角度
Absolute(f64),
/// 直前の結合の向きからの差
Relative(f64),
}
/// 向きを書かなかった結合の向き
pub const DEFAULT_ANGLE: Angle = Angle::Default;
/// 1 本の結合
#[derive(Debug, Clone, PartialEq)]
pub struct Bond {
pub kind: Kind,
pub angle: Angle,
/// 結合長に掛ける係数
pub length: f64,
/// 出ていく側の部分の番号(1 始まり)。書かなければラベル全体
pub from: Option<usize>,
/// 入る側の部分の番号(1 始まり)。書かなければラベル全体
pub to: Option<usize>,
/// 元の文字列でのこの結合の位置
pub position: usize,
}
impl Bond {
/// 向きも長さも書かれていない結合
pub fn new(kind: Kind, position: usize) -> Bond {
Bond {kind, angle: DEFAULT_ANGLE, length: 1.0, from: None, to: None, position}
}
/// 向きか長さが書かれているかどうか。環の中では書けない
pub fn is_placed(&self) -> bool {
self.angle != DEFAULT_ANGLE || self.length != 1.0
}
}
/// 環を閉じる印
///
/// 同じ名前の印が 2 回出てきたら、その 2 つの原子のあいだに結合を引く。
/// 縮合環や大きな環は、これで閉じる。
#[derive(Debug, Clone, PartialEq)]
pub struct Hook {
/// 印の名前。書かなければ a
pub name: String,
/// 閉じるときに引く結合の種別。2 つの印のうち、書いたほうが使われる
pub kind: Option<Kind>,
/// 元の文字列でのこの印の位置
pub position: usize,
}
/// 印に名前を書かなかったときの名前
pub const DEFAULT_HOOK: &str = "a";
/// 原子。ラベルの元の文字列をそのまま持つ
#[derive(Debug, Clone, PartialEq)]
pub struct Atom {
/// 書かれたままのラベル。空の文字列なら、印を置かない結び目である
pub source: String,
/// この原子に付いた、環を閉じる印
pub hook: Vec<Hook>,
/// 元の文字列でのこの原子の位置
pub position: usize,
}
/// 環
///
/// 最初の原子は、この環を抱えている節そのものである。member はその次から数えた原子で、
/// size より 1 つ少ない。bond は size 本あり、最後の 1 本が最初の原子へ戻る。
#[derive(Debug, Clone, PartialEq)]
pub struct Ring {
/// 環の大きさ。頂点の数
pub size: usize,
/// 内側に丸を描くか。芳香環の ** がこれにあたる
pub circle: bool,
pub bond: Vec<Bond>,
pub member: Vec<Node>,
/// 元の文字列でのこの環の位置
pub position: usize,
}
/// 構文木の節
#[derive(Debug, Clone, PartialEq)]
pub struct Node {
pub atom: Atom,
/// この原子から出ていく結合と、その先の節
pub branch: Vec<(Bond, Node)>,
/// この原子を最初の頂点とする環
pub ring: Option<Ring>,
}
impl Node {
/// ラベルだけを持つ節
pub fn new(source: impl Into<String>, position: usize) -> Node {
Node {
atom: Atom {source: source.into(), hook: Vec::new(), position},
branch: Vec::new(),
ring: None,
}
}
}
/// 木に含まれる原子の数
pub fn count(node: &Node) -> usize {
let branch: usize = node.branch.iter().map(|(_, child)| count(child)).sum();
let ring: usize = match &node.ring {
Some(found) => found.member.iter().map(count).sum(),
None => 0,
};
1 + branch + ring
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_the_atoms_of_a_tree() {
let mut root = Node::new("C", 0);
root.branch.push((Bond::new(Kind::Single, 1), Node::new("H", 2)));
root.branch.push((Bond::new(Kind::Double, 3), Node::new("O", 4)));
assert_eq!(count(&root), 3);
}
#[test]
fn counts_the_atoms_of_a_ring() {
let mut root = Node::new("", 0);
root.ring = Some(Ring {
size: 6,
circle: false,
bond: (0..6).map(|at| Bond::new(Kind::Single, at)).collect(),
member: (1..6).map(|at| Node::new("", at)).collect(),
position: 0,
});
assert_eq!(count(&root), 6);
}
#[test]
fn a_bond_without_brackets_goes_east() {
let bond = Bond::new(Kind::Single, 0);
assert_eq!(bond.angle, Angle::Default);
assert_eq!(bond.length, 1.0);
assert_eq!(bond.from, None);
assert!(!bond.is_placed());
}
#[test]
fn the_cram_bonds_know_their_wide_side() {
assert!(Kind::Wedge.is_cram());
assert!(!Kind::Single.is_cram());
assert!(!Kind::Wedge.is_reversed());
assert!(Kind::WedgeBack.is_reversed());
assert!(Kind::HashBack.is_reversed());
assert!(!Kind::Hollow.is_reversed());
}
}