@graffiticode/basis 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/spec/spec.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Graffiticode Core Language Specification
2
2
 
3
3
  ```
4
- Version: 0.1.0
5
- Date: 2025-04-30
4
+ Version: 0.1.2
5
+ Date: 2026-01-26
6
6
  ```
7
7
 
8
8
  # Introduction
@@ -44,6 +44,8 @@ Function application is written in prefix style:
44
44
  add 1 2
45
45
  ```
46
46
 
47
+ Functions have fixed arity, so applications can be parsed unambiguously without grouping syntax. For example, `add 1 mul 2 3` parses as `add(1, mul(2, 3))` because the parser knows `add` takes 2 arguments and `mul` takes 2 arguments.
48
+
47
49
  Parentheses are used to defer application:
48
50
  ```
49
51
  map (double) [1 2 3]
@@ -55,10 +57,53 @@ map (double) [1 2 3]
55
57
  ```
56
58
 
57
59
  ### Records
60
+
61
+ Records may use **shorthand syntax** for fields where the value is a reference to a variable of the same name.
62
+
63
+ ```gc
64
+ let foo = 10..
65
+ {foo} | equivalent to {foo: 10}
58
66
  ```
67
+
68
+ This provides a concise way to construct records using in-scope variable names as field keys and values.
69
+
70
+ You can also mix shorthand and explicit fields:
71
+
72
+ ```gc
73
+ let x = 1..
74
+ let y = 2..
75
+ {x y z: 3} | equivalent to {x: 1, y: 2, z: 3}
76
+ ```
77
+
78
+ ```gc
59
79
  { name: "Alice", age: 30 }
60
80
  ```
61
81
 
82
+ ### Tags
83
+
84
+ A **tag value** is an arity-0 symbolic value used to represent symbolic variants in pattern matching or other symbolic forms.
85
+
86
+ Tag values are **only defined** by using record shorthand syntax. For example:
87
+
88
+ ```gc
89
+ {red blue green} | defines the tag values red, blue, and green
90
+ ```
91
+
92
+ Tag values:
93
+
94
+ - Must be introduced via record shorthand notation
95
+ - Are resolved as symbolic constants with identity semantics
96
+ - Match directly in `case` expressions:
97
+
98
+ ```
99
+ let color = get "red" {red blue green}
100
+ case color of
101
+ red: "warm"
102
+ blue: "cool"
103
+ _: "other"
104
+ end
105
+ ```
106
+
62
107
  ### Lambdas
63
108
  ```
64
109
  <x: add x 1>
@@ -76,6 +121,7 @@ let double = <x: mul 2 x>..
76
121
  ## Pattern Matching
77
122
 
78
123
  Pattern matching is done using `case`:
124
+
79
125
  ```
80
126
  case x of
81
127
  0: "zero"
@@ -92,29 +138,126 @@ Supports:
92
138
 
93
139
  Pattern matching on function arguments is disallowed.
94
140
 
95
- ### Tag Values
141
+ # Type System
96
142
 
97
- A **tag value** is an arity-0 symbolic value that can be used in pattern matching or to encode variant types.
143
+ Graffiticode includes a implicit structural type system. Every expression has
144
+ a statically inferred type, and type errors are detected at compile time.
145
+ Explicit type annotations are not included in the grammar.
98
146
 
147
+ ## Primitive Types
148
+
149
+ - `number` – Represents integers or floating-point numbers.
150
+ - `string` – Represents UTF-8 strings.
151
+ - `bool` – Represents Boolean values: `true` and `false`.
152
+ - `json` – Represents any JSON-compatible value (opaque, untyped).
153
+ - `any` – Used internally to denote an unconstrained type (e.g., during inference).
154
+
155
+ ## Composite Types
156
+
157
+ - **Lists** – Written as `[T]` where `T` is any type.
158
+ ```
159
+ [string] | list of strings
160
+ [number] | list of numbers
161
+ [[bool]] | list of lists of booleans
162
+ ```
163
+
164
+ - **Records** – Key-value maps with known keys and types.
165
+ ```
166
+ { name: string, age: number }
167
+ ```
168
+
169
+ - **Tuples** – Ordered, fixed-length collections with heterogeneous types.
170
+ ```
171
+ (number, string, bool)
172
+ ```
173
+
174
+ ## Function Types
175
+
176
+ Functions are written using Graffiticode lambda signature syntax:
99
177
  ```
100
- red
178
+ <number number: number> | function taking two numbers and returning a number
179
+ <string: [string]> | function taking a string and returning a list of strings
180
+ <list record: record> | common signature for structural transformation
101
181
  ```
102
182
 
103
- Tag values:
183
+ Function types are curried by default. That means:
184
+ ```
185
+ <number number: number>
186
+ ```
187
+ is equivalent to a function that returns another function:
188
+ ```
189
+ <number: <number: number>>
190
+ ```
104
191
 
105
- - Are unbound identifiers that appear in expression position.
106
- - May optionally be introduced via implicit enum definitions.
107
- - Match directly in `case` expressions:
192
+ ## Tag Sets and Tag Value Type
193
+
194
+ Tag values are symbolic constants introduced using record shorthand syntax. They have a default type of `tag`, and their identity is preserved across bindings.
195
+
196
+ ### Tag Identity and Type
197
+
198
+ Tag values are symbolic constants identified **solely by their name**. The same tag name (e.g., `A`) refers to the same symbolic value, regardless of where it is defined.
199
+
200
+ Tag values compare equal by name. For example, `A` in `{A B}` and `A` in `{A C}` are equal.
201
+
202
+ ### Tag Sets as Structural Types
203
+
204
+ Although tag values are globally identified by name, the **type system tracks which tags are expected to co-occur**.
205
+
206
+ For example, `{A B}` and `{A C}` are different types:
108
207
 
109
208
  ```
110
- case color of
209
+ {A B} | type: { A: tag, B: tag }
210
+ {A C} | type: { A: tag, C: tag }
211
+ ```
212
+
213
+ This allows the type system to constrain the expected context for pattern matching, record shapes, or enum-style uses of tags.
214
+
215
+ Using tags not part of the expected set results in a type error.
216
+
217
+ ### Default Type: `tag`
218
+
219
+ By default, a tag value has the type:
220
+
221
+ ```gc
222
+ tag
223
+ ```
224
+
225
+ When tags are introduced with:
226
+
227
+ ```
228
+ {A B C}
229
+ ```
230
+
231
+ the resulting type is:
232
+
233
+ ```
234
+ { A: tag, B: tag, C: tag }
235
+ ```
236
+
237
+ This allows for composition and safe comparison.
238
+
239
+ ### Example
240
+
241
+ ```gc
242
+ let colors = {red blue green}..
243
+ case red of
111
244
  red: "warm"
112
245
  blue: "cool"
113
246
  _: "other"
114
247
  end
115
248
  ```
116
249
 
117
- Tags are resolved as special constants with symbolic identity. They are case-sensitive and may be compared for equality using regular pattern match semantics.
250
+ In this case, `red`, `blue`, and `green` are all of type `tag`, and `case` matches by identity.
251
+
252
+ ## Example: Annotated Definitions
253
+
254
+ ```gc
255
+ let inc = <x: add x 1> | type: <number: number>
256
+ let greet = <name: concat ["Hello, " name]> | type: <string: string>
257
+ let zip = <xs ys: zipLists xs ys> | type: <[T] [U]: [(T, U)]>
258
+ ```
259
+
260
+ (Note: actual Graffiticode does not use type annotations in `let` bindings; types are inferred.)
118
261
 
119
262
  # Semantics
120
263
 
package/src/compiler.js CHANGED
@@ -73,6 +73,7 @@ class Visitor {
73
73
  case "STR":
74
74
  case "IDENT":
75
75
  case "BOOL":
76
+ case "TAG":
76
77
  elts[0] = n.elts[0];
77
78
  break;
78
79
  default:
@@ -173,9 +174,14 @@ export class Checker extends Visitor {
173
174
  const val = node;
174
175
  resume(err, val);
175
176
  }
177
+ TAG(node, options, resume) {
178
+ const err = [];
179
+ const val = node;
180
+ resume(err, val);
181
+ }
176
182
  STR(node, options, resume) {
177
183
  const err = [];
178
- const val = node.elts[0];
184
+ const val = node;
179
185
  resume(err, val);
180
186
  }
181
187
  JSON(node, options, resume) {
@@ -609,7 +615,7 @@ export class Transformer extends Visitor {
609
615
  }
610
616
  const patternNid = this.internPattern(pattern);
611
617
  if (patternNid === this.internPattern(node) ||
612
- patternNid === this.internPattern(newNode('_', []))) {
618
+ patternNid === this.internPattern(newNode('TAG', ['_']))) {
613
619
  return true;
614
620
  }
615
621
  if (pattern.tag === node.tag) {
@@ -648,8 +654,9 @@ export class Transformer extends Visitor {
648
654
  return matches;
649
655
  }
650
656
  CATCH_ALL(node, options, resume) {
657
+ // Fallback for unknown node types.
651
658
  const err = [];
652
- const val = node; // Use the node as the tag value.
659
+ const val = node;
653
660
  resume(err, val);
654
661
  }
655
662
  PROG(node, options, resume) {
@@ -742,6 +749,11 @@ export class Transformer extends Visitor {
742
749
  const val = word?.val !== undefined ? word.val : node.elts[0];
743
750
  resume(err, val);
744
751
  }
752
+ TAG(node, options, resume) {
753
+ const err = [];
754
+ const val = { tag: node.elts[0] };
755
+ resume(err, val);
756
+ }
745
757
  STR(node, options, resume) {
746
758
  const err = [];
747
759
  const val = node.elts[0];
@@ -827,6 +839,10 @@ export class Transformer extends Visitor {
827
839
  const ndx = [];
828
840
  for (let elt of node.elts) {
829
841
  this.visit(elt, options, (e0, v0) => {
842
+ console.log(
843
+ "RECORD()",
844
+ "v0=" + JSON.stringify(v0, null, 2),
845
+ );
830
846
  err = err.concat(e0);
831
847
  ndx[elt] = v0;
832
848
  if (++len === node.elts.length) {
@@ -1035,7 +1051,8 @@ export class Transformer extends Visitor {
1035
1051
  const val = `${v0}`;
1036
1052
  const expr = (
1037
1053
  v0 === null && {tag: "NUL", elts: []} ||
1038
- v0.tag !== undefined && v0 || // We've got a tag value.
1054
+ v0?.tag !== undefined && !v0.elts && {tag: "TAG", elts: [v0.tag]} ||
1055
+ v0?.tag !== undefined && v0 || // Already an AST node.
1039
1056
  type === "boolean" && {tag: "BOOL", elts: [val]} ||
1040
1057
  type === "number" && {tag: "NUM", elts: [val]} ||
1041
1058
  {tag: "STR", elts: [val]}
package/src/lexicon.js CHANGED
@@ -3,256 +3,256 @@ export const lexicon = {
3
3
  "tk": 1,
4
4
  "name": "PRINT",
5
5
  "cls": "function",
6
- "length": 1,
7
6
  "arity": 1,
7
+ "type": "<any: record>",
8
8
  "description": "Outputs a value to the form."
9
9
  },
10
10
  "get": {
11
11
  "tk": 1,
12
12
  "name": "GET",
13
13
  "cls": "function",
14
- "length": 2,
15
14
  "arity": 2,
15
+ "type": "<integer record|list: any>",
16
16
  "description": "Retrieves a value from a record or list by key or index."
17
17
  },
18
18
  "set": {
19
19
  "tk": 1,
20
20
  "name": "SET",
21
21
  "cls": "function",
22
- "length": 3,
23
22
  "arity": 3,
23
+ "type": "<integer any record|list: record|list>",
24
24
  "description": "Returns a new record or list with the specified key or index updated."
25
25
  },
26
26
  "nth": {
27
27
  "tk": 1,
28
28
  "name": "NTH",
29
29
  "cls": "function",
30
- "length": 2,
31
30
  "arity": 2,
31
+ "type": "<integer list: any>",
32
32
  "description": "Returns the nth element of a list by index."
33
33
  },
34
34
  "sub": {
35
35
  "tk": 1,
36
36
  "name": "SUB",
37
37
  "cls": "function",
38
- "length": 2,
39
38
  "arity": 2,
39
+ "type": "<number number: number>",
40
40
  "description": "Subtracts the second number from the first."
41
41
  },
42
42
  "filter": {
43
43
  "tk": 1,
44
44
  "name": "FILTER",
45
45
  "cls": "function",
46
- "length": 2,
47
46
  "arity": 2,
47
+ "type": "<lambda list: list>",
48
48
  "description": "Returns a list of elements that match a predicate."
49
49
  },
50
50
  "reduce": {
51
51
  "tk": 1,
52
52
  "name": "REDUCE",
53
53
  "cls": "function",
54
- "length": 3,
55
54
  "arity": 3,
55
+ "type": "<lambda any list: any>",
56
56
  "description": "Reduces a list to a single value using a binary function and initial value."
57
57
  },
58
58
  "map": {
59
59
  "tk": 1,
60
60
  "name": "MAP",
61
61
  "cls": "function",
62
- "length": 2,
63
62
  "arity": 2,
63
+ "type": "<lambda list: list>",
64
64
  "description": "Applies a function to each element in a list and returns a new list."
65
65
  },
66
66
  "lt": {
67
67
  "tk": 1,
68
68
  "name": "LT",
69
69
  "cls": "function",
70
- "length": 2,
71
70
  "arity": 2,
71
+ "type": "<number number: boolean>",
72
72
  "description": "Returns true if the first value is less than the second."
73
73
  },
74
74
  "le": {
75
75
  "tk": 1,
76
76
  "name": "LE",
77
77
  "cls": "function",
78
- "length": 2,
79
78
  "arity": 2,
79
+ "type": "<number number: boolean>",
80
80
  "description": "Returns true if the first value is less than or equal to the second."
81
81
  },
82
82
  "gt": {
83
83
  "tk": 1,
84
84
  "name": "GT",
85
85
  "cls": "function",
86
- "length": 2,
87
86
  "arity": 2,
87
+ "type": "<number number: boolean>",
88
88
  "description": "Returns true if the first value is greater than the second."
89
89
  },
90
90
  "ge": {
91
91
  "tk": 1,
92
92
  "name": "GE",
93
93
  "cls": "function",
94
- "length": 2,
95
94
  "arity": 2,
95
+ "type": "<number number: boolean>",
96
96
  "description": "Returns true if the first value is greater than or equal to the second."
97
97
  },
98
98
  "ne": {
99
99
  "tk": 1,
100
100
  "name": "NE",
101
101
  "cls": "function",
102
- "length": 2,
103
102
  "arity": 2,
103
+ "type": "<number number: boolean>",
104
104
  "description": "Returns true if the two values are not equal."
105
105
  },
106
106
  "len": {
107
107
  "tk": 1,
108
108
  "name": "LEN",
109
109
  "cls": "function",
110
- "length": 1,
111
110
  "arity": 1,
111
+ "type": "<list|string: integer>",
112
112
  "description": "Returns the length of a list or string."
113
113
  },
114
114
  "concat": {
115
115
  "tk": 1,
116
116
  "name": "CONCAT",
117
117
  "cls": "function",
118
- "length": 1,
119
118
  "arity": 1,
119
+ "type": "<list: list|string>",
120
120
  "description": "Concatenates a list of strings or nested lists."
121
121
  },
122
122
  "add": {
123
123
  "tk": 1,
124
124
  "name": "ADD",
125
125
  "cls": "function",
126
- "length": 2,
127
126
  "arity": 2,
127
+ "type": "<number number: number>",
128
128
  "description": "Adds two numbers."
129
129
  },
130
130
  "mul": {
131
131
  "tk": 1,
132
132
  "name": "MUL",
133
133
  "cls": "function",
134
- "length": 2,
135
134
  "arity": 2,
135
+ "type": "<number number: number>",
136
136
  "description": "Multiplies two numbers."
137
137
  },
138
138
  "pow": {
139
139
  "tk": 1,
140
140
  "name": "POW",
141
141
  "cls": "function",
142
- "length": 2,
143
142
  "arity": 2,
143
+ "type": "<number number: number>",
144
144
  "description": "Raises the first number to the power of the second."
145
145
  },
146
146
  "apply": {
147
147
  "tk": 1,
148
148
  "name": "APPLY",
149
149
  "cls": "function",
150
- "length": 2,
151
150
  "arity": 2,
151
+ "type": "<number number: number>",
152
152
  "description": "Applies a function to a list of arguments."
153
153
  },
154
154
  "data": {
155
155
  "tk": 1,
156
156
  "name": "DATA",
157
157
  "cls": "function",
158
- "length": 1,
159
158
  "arity": 1,
160
- "description": "Returns the raw data payload from a structured input."
159
+ "type": "<record: record>",
160
+ "description": "Returns the data from the upstream task, or the argument value if no input exists."
161
161
  },
162
162
  "json": {
163
163
  "tk": 1,
164
164
  "name": "JSON",
165
165
  "cls": "function",
166
- "length": 1,
167
166
  "arity": 1,
168
- "description": "Parses a string as JSON or serializes a value to JSON."
167
+ "type": "<string: any>",
168
+ "description": "Parses a string as JSON."
169
169
  },
170
170
  "eq": {
171
171
  "tk": 1,
172
172
  "name": "EQ",
173
173
  "cls": "function",
174
- "length": 2,
175
174
  "arity": 2,
175
+ "type": "<number number: boolean>",
176
176
  "description": "Returns true if the two values are equal."
177
177
  },
178
178
  "mod": {
179
179
  "tk": 1,
180
180
  "name": "MOD",
181
181
  "cls": "function",
182
- "length": 2,
183
182
  "arity": 2,
183
+ "type": "<number number: integer>",
184
184
  "description": "Returns the remainder of dividing the first number by the second."
185
185
  },
186
186
  "min": {
187
187
  "tk": 1,
188
188
  "name": "MIN",
189
189
  "cls": "function",
190
- "length": 2,
191
190
  "arity": 2,
191
+ "type": "<number number: number>",
192
192
  "description": "Returns the smaller of two values."
193
193
  },
194
194
  "max": {
195
195
  "tk": 1,
196
196
  "name": "MAX",
197
197
  "cls": "function",
198
- "length": 2,
199
198
  "arity": 2,
199
+ "type": "<number number: number>",
200
200
  "description": "Returns the larger of two values."
201
201
  },
202
202
  "range": {
203
203
  "tk": 1,
204
204
  "name": "RANGE",
205
205
  "cls": "function",
206
- "length": 3,
207
206
  "arity": 3,
207
+ "type": "<number number number: list>",
208
208
  "description": "Generates a list of numbers from start to end using a step."
209
209
  },
210
210
  "not": {
211
211
  "tk": 1,
212
212
  "name": "NOT",
213
213
  "cls": "function",
214
- "length": 1,
215
214
  "arity": 1,
215
+ "type": "<boolean: boolean>",
216
216
  "description": "Returns the logical negation of a boolean value."
217
217
  },
218
218
  "equiv": {
219
219
  "tk": 1,
220
220
  "name": "EQUIV",
221
221
  "cls": "function",
222
- "length": 2,
223
222
  "arity": 2,
223
+ "type": "<any any: boolean>",
224
224
  "description": "Returns true if the two values are semantically equivalent."
225
225
  },
226
226
  "or": {
227
227
  "tk": 1,
228
228
  "name": "OR",
229
229
  "cls": "function",
230
- "length": 2,
231
230
  "arity": 2,
231
+ "type": "<boolean boolean: boolean>",
232
232
  "description": "Returns true if at least one of the two values is true."
233
233
  },
234
234
  "and": {
235
235
  "tk": 1,
236
236
  "name": "AND",
237
237
  "cls": "function",
238
- "length": 2,
239
238
  "arity": 2,
239
+ "type": "<boolean boolean: boolean>",
240
240
  "description": "Returns true if both values are true."
241
241
  },
242
242
  "hd": {
243
243
  "tk": 1,
244
244
  "name": "HD",
245
245
  "cls": "function",
246
- "length": 1,
247
246
  "arity": 1,
247
+ "type": "<list: any>",
248
248
  "description": "Returns the first element of a list."
249
249
  },
250
250
  "tl": {
251
251
  "tk": 1,
252
252
  "name": "TL",
253
253
  "cls": "function",
254
- "length": 1,
255
254
  "arity": 1,
255
+ "type": "<list: list>",
256
256
  "description": "Returns the list without its first element."
257
257
  },
258
258
  "cons": {
@@ -261,22 +261,23 @@ export const lexicon = {
261
261
  "cls": "function",
262
262
  "length": 2,
263
263
  "arity": 2,
264
+ "type": "<any list: list>",
264
265
  "description": "Prepends an element to the front of a list."
265
266
  },
266
267
  "append": {
267
268
  "tk": 1,
268
269
  "name": "APPEND",
269
270
  "cls": "function",
270
- "length": 2,
271
271
  "arity": 2,
272
+ "type": "<any list: list>",
272
273
  "description": "Appends an element to the end of a list."
273
274
  },
274
275
  "log": {
275
276
  "tk": 1,
276
277
  "name": "LOG",
277
278
  "cls": "function",
278
- "length": 1,
279
279
  "arity": 1,
280
+ "type": "<any: any>",
280
281
  "description": "Logs the value to the console and returns the value (identity function)."
281
282
  }
282
283
  };