1#![allow(dead_code)]
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7pub struct SourceLoc {
8 pub file: String,
9 pub offset: usize,
10 pub line: u32,
11}
12
13impl SourceLoc {
14 pub fn from_offset(file: impl Into<String>, offset: usize, source: &str) -> Self {
16 let line = source[..offset.min(source.len())]
17 .chars()
18 .filter(|&c| c == '\n')
19 .count() as u32
20 + 1;
21 SourceLoc {
22 file: file.into(),
23 offset,
24 line,
25 }
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
31#[serde(rename_all = "snake_case")]
32pub enum NodeKind {
33 Function,
34 Alias,
35 TypeDef,
36 Module,
37 Variable,
38}
39
40impl NodeKind {
41 pub fn prefix(&self) -> &'static str {
42 match self {
43 NodeKind::Function => "function",
44 NodeKind::Alias => "alias",
45 NodeKind::TypeDef => "type",
46 NodeKind::Module => "module",
47 NodeKind::Variable => "variable",
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54#[serde(rename_all = "snake_case")]
55pub enum Visibility {
56 Private,
57 Public,
58 Export,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(tag = "kind", rename_all = "snake_case")]
64pub enum NodePayload {
65 Function {
66 params: Vec<(String, String)>,
68 returns: String,
69 },
70 Record {
71 fields: Vec<(String, String)>,
73 },
74 Alias {
75 underlying: String,
76 opaque: bool,
77 },
78 Union {
79 variants: Vec<(String, Option<String>)>,
80 },
81 Module {
82 exports: Vec<String>,
83 },
84 Variable {
91 type_str: String,
92 },
93 None,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
98pub struct Node {
99 pub id: String,
101 pub kind: NodeKind,
102 pub name: String,
103 pub module_path: Vec<String>,
105 pub visibility: Visibility,
106 pub doc: Option<String>,
108 pub source: Option<SourceLoc>,
109 pub payload: NodePayload,
110}
111
112impl Node {
113 pub fn make_id(kind: &NodeKind, module_path: &[String], name: &str) -> String {
115 if module_path.is_empty() {
116 format!("{}:{}", kind.prefix(), name)
117 } else {
118 format!("{}:{}/{}", kind.prefix(), module_path.join("/"), name)
119 }
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
125#[serde(rename_all = "kebab-case")]
126pub enum EdgeKind {
127 HasField,
128 Returns,
129 ProducesType,
130 ParameterOf,
131 ConsumesType,
132 BelongsToModule,
133 Uses,
134 UsedBy,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
139pub struct Edge {
140 pub from: String,
141 pub to: String,
142 pub kind: EdgeKind,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct Spg {
148 #[serde(rename = "@context")]
150 pub context: String,
151 pub package: String,
152 pub version: String,
153 pub nodes: Vec<Node>,
154 pub edges: Vec<Edge>,
155}
156
157impl Spg {
158 pub fn new(package: impl Into<String>, version: impl Into<String>) -> Self {
159 Spg {
160 context: "https://typr-lang.dev/spg/v1/context.jsonld".into(),
161 package: package.into(),
162 version: version.into(),
163 nodes: Vec::new(),
164 edges: Vec::new(),
165 }
166 }
167
168 pub fn add_node(&mut self, node: Node) {
169 self.nodes.push(node);
170 }
171
172 pub fn add_edge(&mut self, edge: Edge) {
173 self.edges.push(edge);
174 }
175}