Skip to content

Commit 589e445

Browse files
committed
New Feature: add support for unit type
1 parent 5b5f5b2 commit 589e445

File tree

14 files changed

+491
-472
lines changed

14 files changed

+491
-472
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313
**Note**: Gaps between patch versions are faulty/broken releases.
1414
**Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.
1515

16+
# 0.2.0
17+
18+
- **Breaking Change**
19+
- model refactoring (@gcanti)
20+
- **New Feature**
21+
- add support for unit type (@gcanti)
22+
1623
# 0.1.0
1724

1825
- **Breaking Change**

examples/Writer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export type Writer<W, A> = {
2+
readonly type: "Writer";
3+
readonly value0: () => [A, W];
4+
};
5+
6+
export function writer<W, A>(value0: () => [A, W]): Writer<W, A> { return { type: "Writer", value0 }; }
7+

examples/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ import './User'
77
import './Constrained'
88
import './Tuple2'
99
import './State'
10+
import './Writer'
1011

generate-examples.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ const examples = Object.keys(E).map(k => (E as any)[k] as M.Data)
88

99
examples.forEach(example => {
1010
const code = print(example) + '\n\n'
11-
const filePath = path.join(__dirname, `./examples/${example.introduction.name}.ts`)
11+
const filePath = path.join(__dirname, `./examples/${example.name}.ts`)
1212
fs.writeFileSync(filePath, code, { encoding: 'utf-8' })
1313
})
1414

15-
const index = examples.map(example => `import './${example.introduction.name}'`).join('\n') + '\n\n'
15+
const index = examples.map(example => `import './${example.name}'`).join('\n') + '\n\n'
1616

1717
fs.writeFileSync(path.join(__dirname, `./examples/index.ts`), index, { encoding: 'utf-8' })

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fp-ts-codegen",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "TypeScript code generation from a haskell-like syntax for ADT",
55
"files": [
66
"lib"

src/ast.ts

Lines changed: 65 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { none } from 'fp-ts/lib/Option'
1+
import { none, Option, some } from 'fp-ts/lib/Option'
22
import { Reader, reader, ask } from 'fp-ts/lib/Reader'
33
import { array, empty } from 'fp-ts/lib/Array'
44
import * as ts from 'typescript'
@@ -44,29 +44,35 @@ const getMemberName = (m: M.Member, position: number): string => {
4444
return m.name.getOrElseL(() => `value${position}`)
4545
}
4646

47-
const getDomainParameterName = (type: M.Type): string => {
47+
const getDomainParameterName = (type: M.Type): Option<string> => {
4848
switch (type.kind) {
49-
case 'TypeReference':
50-
return getFirstLetterLowerCase(type.name)
51-
case 'TupleType':
52-
return 'tuple'
53-
case 'FunctionType':
54-
return 'f'
49+
case 'Ref':
50+
return some(getFirstLetterLowerCase(type.name))
51+
case 'Tuple':
52+
return some('tuple')
53+
case 'Fun':
54+
return some('f')
55+
case 'Unit':
56+
return none
5557
}
5658
}
5759

5860
const getTypeNode = (type: M.Type): ts.TypeNode => {
5961
switch (type.kind) {
60-
case 'TypeReference':
62+
case 'Ref':
6163
return ts.createTypeReferenceNode(type.name, type.parameters.map(p => getTypeNode(p)))
62-
case 'TupleType':
63-
return ts.createTupleTypeNode([getTypeNode(type.fst), getTypeNode(type.snd), ...type.other.map(getTypeNode)])
64-
case 'FunctionType':
64+
case 'Tuple':
65+
return ts.createTupleTypeNode(type.types.map(getTypeNode))
66+
case 'Fun':
6567
return ts.createFunctionTypeNode(
6668
undefined,
67-
[getParameterDeclaration(getDomainParameterName(type.domain), getTypeNode(type.domain))],
69+
getDomainParameterName(type.domain)
70+
.map(domainName => [getParameterDeclaration(domainName, getTypeNode(type.domain))])
71+
.getOrElse([]),
6872
getTypeNode(type.codomain)
6973
)
74+
case 'Unit':
75+
return ts.createTypeReferenceNode('undefined', [])
7076
}
7177
}
7278

@@ -83,9 +89,9 @@ const getDataLiteralEncoding = (d: M.Data): AST<Array<ts.Node>> => {
8389
)
8490
return [
8591
getTypeAliasDeclaration(
86-
d.introduction.name,
92+
d.name,
8793
unionType,
88-
d.introduction.parameters.map(p =>
94+
d.parameterDeclarations.map(p =>
8995
ts.createTypeParameterDeclaration(p.name, p.constraint.map(getTypeNode).toUndefined())
9096
)
9197
)
@@ -145,8 +151,8 @@ const getDataModuleDeclaration = (d: M.Data): ts.ModuleDeclaration => {
145151
ts.createModuleBlock([
146152
getInterfaceDeclaration(URI2HKT, parameters.map(p => ts.createTypeParameterDeclaration(p)), [
147153
getPropertySignature(
148-
d.introduction.name,
149-
ts.createTypeReferenceNode(d.introduction.name, parameters.map(p => ts.createTypeReferenceNode(p, []))),
154+
d.name,
155+
ts.createTypeReferenceNode(d.name, parameters.map(p => ts.createTypeReferenceNode(p, []))),
150156
false
151157
)
152158
])
@@ -182,7 +188,7 @@ const getMethod = (
182188
}
183189

184190
const getFoldMethod = (d: M.Data, c: M.Constructor, name: string, isEager: boolean) => {
185-
const returnTypeParameterName = getFoldReturnTypeParameterName(d.introduction)
191+
const returnTypeParameterName = getFoldReturnTypeParameterName(d)
186192
const handlerExpression = ts.createIdentifier(getFoldHandlerName(c))
187193
const returnExpression =
188194
isNullaryConstructor(c) && isEager
@@ -210,19 +216,19 @@ const getDataFptsEncoding = (d: M.Data): AST<Array<ts.Node>> => {
210216
d.constructors.toArray().map(c => {
211217
return ts.createTypeReferenceNode(
212218
c.name,
213-
d.introduction.parameters.map(p => ts.createTypeReferenceNode(p.name, []))
219+
d.parameterDeclarations.map(p => ts.createTypeReferenceNode(p.name, []))
214220
)
215221
})
216222
)
217223

218224
const moduleDeclaration = getDataModuleDeclaration(d)
219225

220-
const uriValue = getConstantDeclaration('URI', ts.createStringLiteral(d.introduction.name))
226+
const uriValue = getConstantDeclaration('URI', ts.createStringLiteral(d.name))
221227

222228
const uriType = getTypeAliasDeclaration('URI', ts.createTypeQueryNode(ts.createIdentifier('URI')))
223229

224230
const classes = d.constructors.toArray().map(c => {
225-
const typeParameters = d.introduction.parameters.map(p => ts.createTypeParameterDeclaration(p.name))
231+
const typeParameters = d.parameterDeclarations.map(p => ts.createTypeParameterDeclaration(p.name))
226232
const parameters: Array<ts.ParameterDeclaration> = c.members.map((m, position) => {
227233
return getParameterDeclaration(getMemberName(m, position), getTypeNode(m.type), true)
228234
})
@@ -247,12 +253,13 @@ const getDataFptsEncoding = (d: M.Data): AST<Array<ts.Node>> => {
247253

248254
const URI = getPropertyDeclaration('_URI', ts.createTypeReferenceNode('URI', []), undefined, true)
249255

250-
// TODO
251-
let folds: Array<ts.ClassElement>
252-
if (isEagerFoldSupported(d)) {
253-
folds = [getFoldMethod(d, c, e.foldName, true), getFoldMethod(d, c, `${e.foldName}L`, false)]
254-
} else {
255-
folds = [getFoldMethod(d, c, e.foldName, false)]
256+
let folds: Array<ts.ClassElement> = empty
257+
if (isSumType(d)) {
258+
if (isEagerFoldSupported(d)) {
259+
folds = [getFoldMethod(d, c, e.foldName, true), getFoldMethod(d, c, `${e.foldName}L`, false)]
260+
} else {
261+
folds = [getFoldMethod(d, c, e.foldName, false)]
262+
}
256263
}
257264

258265
const members: Array<ts.ClassElement> = [tag, ...fptsParameters, URI, constructor, ...folds]
@@ -264,8 +271,8 @@ const getDataFptsEncoding = (d: M.Data): AST<Array<ts.Node>> => {
264271
'value',
265272
undefined,
266273
ts.createTypeReferenceNode(
267-
d.introduction.name,
268-
d.introduction.parameters.map(_ => ts.createTypeReferenceNode('never', []))
274+
d.name,
275+
d.parameterDeclarations.map(_ => ts.createTypeReferenceNode('never', []))
269276
),
270277
ts.createNew(ts.createIdentifier(c.name), undefined, [])
271278
)
@@ -286,9 +293,9 @@ const getDataFptsEncoding = (d: M.Data): AST<Array<ts.Node>> => {
286293
uriValue,
287294
uriType,
288295
getTypeAliasDeclaration(
289-
d.introduction.name,
296+
d.name,
290297
unionType,
291-
d.introduction.parameters.map(p =>
298+
d.parameterDeclarations.map(p =>
292299
ts.createTypeParameterDeclaration(p.name, p.constraint.map(getTypeNode).toUndefined())
293300
)
294301
),
@@ -298,7 +305,7 @@ const getDataFptsEncoding = (d: M.Data): AST<Array<ts.Node>> => {
298305
}
299306

300307
const getDataParametersLength = (d: M.Data): number => {
301-
return d.introduction.parameters.length
308+
return d.parameterDeclarations.length
302309
}
303310

304311
const shouldUseLiteralEncoding = (d: M.Data): AST<boolean> => {
@@ -359,12 +366,12 @@ const getParameterDeclaration = (
359366
)
360367
}
361368

362-
const getLiteralNullaryConstructor = (c: M.Constructor, introduction: M.Introduction): AST<ts.Node> => {
369+
const getLiteralNullaryConstructor = (c: M.Constructor, d: M.Data): AST<ts.Node> => {
363370
return new Reader(e => {
364371
const name = getFirstLetterLowerCase(c.name)
365372
const type = ts.createTypeReferenceNode(
366-
introduction.name,
367-
introduction.parameters.map(p =>
373+
d.name,
374+
d.parameterDeclarations.map(p =>
368375
p.constraint.map(c => getTypeNode(c)).getOrElse(ts.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword))
369376
)
370377
)
@@ -373,10 +380,10 @@ const getLiteralNullaryConstructor = (c: M.Constructor, introduction: M.Introduc
373380
})
374381
}
375382

376-
const getLiteralConstructor = (c: M.Constructor, introduction: M.Introduction): AST<ts.Node> => {
383+
const getLiteralConstructor = (c: M.Constructor, d: M.Data): AST<ts.Node> => {
377384
return new Reader(e => {
378385
const name = getFirstLetterLowerCase(c.name)
379-
const typeParameters = introduction.parameters.map(p => {
386+
const typeParameters = d.parameterDeclarations.map(p => {
380387
return ts.createTypeParameterDeclaration(p.name, p.constraint.map(getTypeNode).toUndefined())
381388
})
382389
const parameters = c.members.map((m, position) => {
@@ -385,8 +392,8 @@ const getLiteralConstructor = (c: M.Constructor, introduction: M.Introduction):
385392
return getParameterDeclaration(name, type)
386393
})
387394
const type = ts.createTypeReferenceNode(
388-
introduction.name,
389-
introduction.parameters.map(p => ts.createTypeReferenceNode(p.name, []))
395+
d.name,
396+
d.parameterDeclarations.map(p => ts.createTypeReferenceNode(p.name, []))
390397
)
391398
const body = ts.createBlock([
392399
ts.createReturn(
@@ -406,20 +413,20 @@ const getLiteralConstructor = (c: M.Constructor, introduction: M.Introduction):
406413
const getConstructorsLiteralEncoding = (d: M.Data): AST<Array<ts.Node>> => {
407414
const constructors = d.constructors.toArray().map(c => {
408415
if (c.members.length === 0) {
409-
return getLiteralNullaryConstructor(c, d.introduction)
416+
return getLiteralNullaryConstructor(c, d)
410417
} else {
411-
return getLiteralConstructor(c, d.introduction)
418+
return getLiteralConstructor(c, d)
412419
}
413420
})
414421
return array.sequence(reader)(constructors)
415422
}
416423

417-
const getFptsNullaryConstructor = (c: M.Constructor, introduction: M.Introduction): AST<ts.Node> => {
424+
const getFptsNullaryConstructor = (c: M.Constructor, d: M.Data): AST<ts.Node> => {
418425
return new Reader(_ => {
419426
const name = getFirstLetterLowerCase(c.name)
420427
const type = ts.createTypeReferenceNode(
421-
introduction.name,
422-
introduction.parameters.map(p =>
428+
d.name,
429+
d.parameterDeclarations.map(p =>
423430
p.constraint.map(c => getTypeNode(c)).getOrElse(ts.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword))
424431
)
425432
)
@@ -428,10 +435,10 @@ const getFptsNullaryConstructor = (c: M.Constructor, introduction: M.Introductio
428435
})
429436
}
430437

431-
const getFptsConstructor = (c: M.Constructor, introduction: M.Introduction): AST<ts.Node> => {
438+
const getFptsConstructor = (c: M.Constructor, d: M.Data): AST<ts.Node> => {
432439
return new Reader(_ => {
433440
const name = getFirstLetterLowerCase(c.name)
434-
const typeParameters = introduction.parameters.map(p => {
441+
const typeParameters = d.parameterDeclarations.map(p => {
435442
return ts.createTypeParameterDeclaration(p.name, p.constraint.map(getTypeNode).toUndefined())
436443
})
437444
const parameters = c.members.map((m, position) => {
@@ -440,8 +447,8 @@ const getFptsConstructor = (c: M.Constructor, introduction: M.Introduction): AST
440447
return getParameterDeclaration(name, type)
441448
})
442449
const type = ts.createTypeReferenceNode(
443-
introduction.name,
444-
introduction.parameters.map(p => ts.createTypeReferenceNode(p.name, []))
450+
d.name,
451+
d.parameterDeclarations.map(p => ts.createTypeReferenceNode(p.name, []))
445452
)
446453
const body = ts.createBlock([
447454
ts.createReturn(
@@ -461,9 +468,9 @@ const getFptsConstructor = (c: M.Constructor, introduction: M.Introduction): AST
461468
const getConstructorsFptsEncoding = (d: M.Data): AST<Array<ts.Node>> => {
462469
const constructors = d.constructors.toArray().map(c => {
463470
if (c.members.length === 0) {
464-
return getFptsNullaryConstructor(c, d.introduction)
471+
return getFptsNullaryConstructor(c, d)
465472
} else {
466-
return getFptsConstructor(c, d.introduction)
473+
return getFptsConstructor(c, d)
467474
}
468475
})
469476
return array.sequence(reader)(constructors)
@@ -487,11 +494,11 @@ const isSumType = (d: M.Data): boolean => {
487494
return d.constructors.length() > 1
488495
}
489496

490-
const getFoldReturnTypeParameterName = (i: M.Introduction): string => {
497+
const getFoldReturnTypeParameterName = (d: M.Data): string => {
491498
const base = 'R'
492499
let candidate = base
493500
let counter = 0
494-
while (i.parameters.findIndex(({ name }) => candidate === name) !== -1) {
501+
while (d.parameterDeclarations.findIndex(({ name }) => candidate === name) !== -1) {
495502
candidate = base + ++counter
496503
}
497504
return candidate
@@ -506,7 +513,7 @@ const getFoldPositionalHandlers = (
506513
isEager: boolean,
507514
usedConstructor?: M.Constructor
508515
): Array<ts.ParameterDeclaration> => {
509-
const returnTypeParameterName = getFoldReturnTypeParameterName(d.introduction)
516+
const returnTypeParameterName = getFoldReturnTypeParameterName(d)
510517
return d.constructors.toArray().map(c => {
511518
const type =
512519
isEager && isNullaryConstructor(c)
@@ -523,7 +530,7 @@ const getFoldPositionalHandlers = (
523530
}
524531

525532
const getFoldRecordHandlers = (d: M.Data, handlersName: string, isEager: boolean): Array<ts.ParameterDeclaration> => {
526-
const returnTypeParameterName = getFoldReturnTypeParameterName(d.introduction)
533+
const returnTypeParameterName = getFoldReturnTypeParameterName(d)
527534
const type = ts.createTypeLiteralNode(
528535
d.constructors.toArray().map(c => {
529536
const type =
@@ -597,16 +604,13 @@ const getFold = (d: M.Data, name: string, isEager: boolean): AST<ts.FunctionDecl
597604
const matcheeName = e.matcheeName
598605
const tagName = e.tagName
599606
const handlersStyle = e.handlersStyle
600-
const returnTypeParameterName = getFoldReturnTypeParameterName(d.introduction)
601-
const typeParameters = d.introduction.parameters
602-
.concat(M.parameter(returnTypeParameterName, none))
607+
const returnTypeParameterName = getFoldReturnTypeParameterName(d)
608+
const typeParameters = d.parameterDeclarations
609+
.concat(M.parameterDeclaration(returnTypeParameterName, none))
603610
.map(p => ts.createTypeParameterDeclaration(p.name, p.constraint.map(getTypeNode).toUndefined()))
604611
const matchee = getParameterDeclaration(
605612
matcheeName,
606-
ts.createTypeReferenceNode(
607-
d.introduction.name,
608-
d.introduction.parameters.map(p => ts.createTypeReferenceNode(p.name, []))
609-
)
613+
ts.createTypeReferenceNode(d.name, d.parameterDeclarations.map(p => ts.createTypeReferenceNode(p.name, [])))
610614
)
611615
const handlers =
612616
handlersStyle.type === 'positional'

0 commit comments

Comments
 (0)