@starpak/lang 0.0.0-stage → 0.1.2

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.
Files changed (53) hide show
  1. package/LICENSE +22 -0
  2. package/lib/ast.d.ts +334 -0
  3. package/lib/ast.d.ts.map +1 -0
  4. package/lib/ast.js +9 -0
  5. package/lib/ast.js.map +1 -0
  6. package/lib/checker.d.ts +17 -0
  7. package/lib/checker.d.ts.map +1 -0
  8. package/lib/checker.js +525 -0
  9. package/lib/checker.js.map +1 -0
  10. package/lib/diagnostics.d.ts +46 -0
  11. package/lib/diagnostics.d.ts.map +1 -0
  12. package/lib/diagnostics.js +77 -0
  13. package/lib/diagnostics.js.map +1 -0
  14. package/lib/emit/lua.d.ts +19 -0
  15. package/lib/emit/lua.d.ts.map +1 -0
  16. package/lib/emit/lua.js +451 -0
  17. package/lib/emit/lua.js.map +1 -0
  18. package/lib/emit/ts.d.ts +28 -0
  19. package/lib/emit/ts.d.ts.map +1 -0
  20. package/lib/emit/ts.js +803 -0
  21. package/lib/emit/ts.js.map +1 -0
  22. package/lib/index.d.ts +17 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +49 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/lexer.d.ts +10 -0
  27. package/lib/lexer.d.ts.map +1 -0
  28. package/lib/lexer.js +282 -0
  29. package/lib/lexer.js.map +1 -0
  30. package/lib/parser.d.ts +11 -0
  31. package/lib/parser.d.ts.map +1 -0
  32. package/lib/parser.js +1133 -0
  33. package/lib/parser.js.map +1 -0
  34. package/lib/token.d.ts +32 -0
  35. package/lib/token.d.ts.map +1 -0
  36. package/lib/token.js +18 -0
  37. package/lib/token.js.map +1 -0
  38. package/lib/types.d.ts +63 -0
  39. package/lib/types.d.ts.map +1 -0
  40. package/lib/types.js +177 -0
  41. package/lib/types.js.map +1 -0
  42. package/package.json +10 -3
  43. package/src/ast.ts +459 -0
  44. package/src/checker.ts +554 -0
  45. package/src/diagnostics.ts +100 -0
  46. package/src/emit/lua.ts +484 -0
  47. package/src/emit/ts.ts +861 -0
  48. package/src/index.ts +21 -0
  49. package/src/lexer.ts +301 -0
  50. package/src/parser.ts +1251 -0
  51. package/src/token.ts +34 -0
  52. package/src/types.ts +218 -0
  53. package/README.md +0 -3
package/src/token.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Spak Lang - Token definitions
3
+ */
4
+
5
+ export const TokenType = {
6
+ Number: 'Number',
7
+ String: 'String',
8
+ FString: 'FString',
9
+ Identifier: 'Identifier',
10
+ Keyword: 'Keyword',
11
+ Operator: 'Operator',
12
+ Newline: 'Newline',
13
+ Semicolon: 'Semicolon',
14
+ EOF: 'EOF',
15
+ } as const
16
+
17
+ export type TokenType = typeof TokenType[keyof typeof TokenType]
18
+
19
+ export interface Token {
20
+ type: TokenType
21
+ /** Raw lexeme for identifiers/keywords/operators, decoded content for strings. */
22
+ value: string
23
+ /** 1-based line of the first character. */
24
+ line: number
25
+ /** 1-based column of the first character. */
26
+ column: number
27
+ /** Character offset of the first character. */
28
+ start: number
29
+ /** Character offset just past the last character. */
30
+ end: number
31
+ fstring?: boolean
32
+ /** Doc comment (`---`) that directly precedes this token, if any. */
33
+ doc?: string
34
+ }
package/src/types.ts ADDED
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Spak Lang - Type model and compatibility rules (M2)
3
+ *
4
+ * Types only exist at check time; nothing here is emitted to runtime code.
5
+ * The model mirrors `docs/syntax/typecheck.md`.
6
+ */
7
+
8
+ import type { TypeAnn } from './ast'
9
+
10
+ export type Type =
11
+ | AnyType
12
+ | NilType
13
+ | PrimType
14
+ | ArrayType
15
+ | DictType
16
+ | RecordType
17
+ | FnType
18
+ | UnionType
19
+ | NamedType
20
+
21
+ export interface AnyType {
22
+ kind: 'any'
23
+ }
24
+
25
+ export interface NilType {
26
+ kind: 'nil'
27
+ }
28
+
29
+ export interface PrimType {
30
+ kind: 'prim'
31
+ name: 'boolean' | 'number' | 'string'
32
+ }
33
+
34
+ export interface ArrayType {
35
+ kind: 'array'
36
+ element: Type
37
+ }
38
+
39
+ export interface DictType {
40
+ kind: 'dict'
41
+ key: Type
42
+ value: Type
43
+ }
44
+
45
+ export interface RecordType {
46
+ kind: 'record'
47
+ fields: Map<string, Type>
48
+ }
49
+
50
+ export interface FnType {
51
+ kind: 'fn'
52
+ params: Type[]
53
+ returns: Type
54
+ }
55
+
56
+ export interface UnionType {
57
+ kind: 'union'
58
+ types: Type[]
59
+ }
60
+
61
+ export interface NamedType {
62
+ kind: 'named'
63
+ name: string
64
+ /** Filled after alias resolution when the name is a type alias. */
65
+ resolved?: Type
66
+ }
67
+
68
+ export const ANY: AnyType = { kind: 'any' }
69
+ export const NIL: NilType = { kind: 'nil' }
70
+ export const NUMBER: PrimType = { kind: 'prim', name: 'number' }
71
+ export const STRING: PrimType = { kind: 'prim', name: 'string' }
72
+ export const BOOLEAN: PrimType = { kind: 'prim', name: 'boolean' }
73
+
74
+ const PRIMS: Record<string, PrimType> = {
75
+ number: NUMBER,
76
+ string: STRING,
77
+ boolean: BOOLEAN,
78
+ bool: BOOLEAN,
79
+ }
80
+
81
+ /** Resolve an AST type annotation into the checker's type model. */
82
+ export function fromTypeAnn(ann: TypeAnn): Type {
83
+ switch (ann.kind) {
84
+ case 'named': {
85
+ const prim = PRIMS[ann.name]
86
+ if (prim) return prim
87
+ if (ann.name === 'any') return ANY
88
+ if (ann.name === 'nil') return NIL
89
+ return { kind: 'named', name: ann.name }
90
+ }
91
+ case 'array':
92
+ return { kind: 'array', element: fromTypeAnn(ann.element) }
93
+ case 'dict':
94
+ return { kind: 'dict', key: fromTypeAnn(ann.key), value: fromTypeAnn(ann.value) }
95
+ case 'record': {
96
+ const fields = new Map<string, Type>()
97
+ for (const field of ann.fields) fields.set(field.name, fromTypeAnn(field.type))
98
+ return { kind: 'record', fields }
99
+ }
100
+ case 'union':
101
+ return union(ann.types.map(fromTypeAnn))
102
+ case 'fn':
103
+ return {
104
+ kind: 'fn',
105
+ params: ann.params.map(fromTypeAnn),
106
+ returns: fromTypeAnn(ann.returns),
107
+ }
108
+ case 'optional':
109
+ return union([fromTypeAnn(ann.type), NIL])
110
+ default:
111
+ return ANY
112
+ }
113
+ }
114
+
115
+ /** Normalise a union: flatten, drop duplicates, collapse `any`, keep `nil` last. */
116
+ export function union(types: Type[]): Type {
117
+ const flat: Type[] = []
118
+ for (const type of types) {
119
+ if (type.kind === 'union') flat.push(...type.types)
120
+ else flat.push(type)
121
+ }
122
+ const seen = new Set<string>()
123
+ const out: Type[] = []
124
+ for (const type of flat) {
125
+ const key = print(type)
126
+ if (seen.has(key)) continue
127
+ seen.add(key)
128
+ out.push(type)
129
+ }
130
+ if (out.some(type => type.kind === 'any')) return ANY
131
+ if (out.length === 1) return out[0]
132
+ const nonNil: Type[] = out.filter(type => type.kind !== 'nil')
133
+ const nils: Type[] = out.filter(type => type.kind === 'nil')
134
+ return { kind: 'union', types: [...nonNil, ...nils] }
135
+ }
136
+
137
+ export function isNil(type: Type): boolean {
138
+ return type.kind === 'nil'
139
+ }
140
+
141
+ /** Remove `nil` from a type (used by `if x then` narrowing). */
142
+ export function withoutNil(type: Type): Type {
143
+ if (type.kind !== 'union') return type.kind === 'nil' ? ANY : type
144
+ const rest = type.types.filter(item => item.kind !== 'nil')
145
+ if (!rest.length) return ANY
146
+ return union(rest)
147
+ }
148
+
149
+ /** `target` accepts `source`? Structural, with `any` behaving as both directions. */
150
+ export function isAssignable(target: Type, source: Type): boolean {
151
+ if (target.kind === 'any' || source.kind === 'any') return true
152
+ if (target.kind === 'named' && target.resolved) return isAssignable(target.resolved, source)
153
+ if (source.kind === 'named' && source.resolved) return isAssignable(target, source.resolved)
154
+
155
+ if (target.kind === 'union') {
156
+ if (source.kind === 'union') return source.types.every(item => isAssignable(target, item))
157
+ return target.types.some(item => isAssignable(item, source))
158
+ }
159
+ if (source.kind === 'union') return source.types.every(item => isAssignable(target, item))
160
+
161
+ if (target.kind !== source.kind) return false
162
+
163
+ switch (target.kind) {
164
+ case 'nil':
165
+ return true
166
+ case 'prim':
167
+ return source.kind === 'prim' && source.name === target.name
168
+ case 'array':
169
+ return source.kind === 'array' && isAssignable(target.element, source.element)
170
+ case 'dict':
171
+ return source.kind === 'dict'
172
+ && isAssignable(target.key, source.key)
173
+ && isAssignable(target.value, source.value)
174
+ case 'record': {
175
+ if (source.kind !== 'record') return false
176
+ for (const [name, type] of target.fields) {
177
+ const found = source.fields.get(name)
178
+ if (!found || !isAssignable(type, found)) return false
179
+ }
180
+ return true
181
+ }
182
+ case 'fn': {
183
+ if (source.kind !== 'fn' || source.params.length !== target.params.length) return false
184
+ const paramsOk = target.params.every((type, index) => isAssignable(source.params[index], type))
185
+ return paramsOk && isAssignable(target.returns, source.returns)
186
+ }
187
+ case 'named':
188
+ return source.kind === 'named' && source.name === target.name
189
+ default:
190
+ return false
191
+ }
192
+ }
193
+
194
+ /** Human readable type, used in diagnostics. */
195
+ export function print(type: Type): string {
196
+ switch (type.kind) {
197
+ case 'any':
198
+ return 'any'
199
+ case 'nil':
200
+ return 'nil'
201
+ case 'prim':
202
+ return type.name
203
+ case 'array':
204
+ return `[${print(type.element)}]`
205
+ case 'dict':
206
+ return `{${print(type.key)}: ${print(type.value)}}`
207
+ case 'record': {
208
+ const fields = [...type.fields].map(([name, value]) => `${name}: ${print(value)}`)
209
+ return `{ ${fields.join(', ')} }`
210
+ }
211
+ case 'fn':
212
+ return `(${type.params.map(print).join(', ')}) -> ${print(type.returns)}`
213
+ case 'union':
214
+ return type.types.map(print).join(' | ')
215
+ case 'named':
216
+ return type.name
217
+ }
218
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.