Skip to content

Commit f4bcc28

Browse files
authored
feat: register-type improvements (#1332)
* fix: don't instantiate dummy fields for external types For ANSI C compatibility reasons, we add a dummy field for memberless types defined in Carp (see commit 59ef5bb). When registering a type with no fields, `(register-type A [])`, we'd also attempt to set our dummy field in the Carp generated initializer for the type. However, the registered type is totally opaque from the perspective of Carp, and we can't assume it has a field corresponding to our dummy field. This commit changes our handling of __dummy in initializers to avoid setting it for registered types. * feat: automatically implement str and prn for registered types This commit makes the auto-generated str and prn functions for registered types implement the str and prn interfaces, removing the need for users to call implements on these functions explicitly. It alters the signature of `autoDerive` in Primitives.hs slightly to make it more flexible (since registered types have no delete or copy functions that we can add to the implementation lists of these interfaces). * docs: add docs on register-type to CInterop.md The new documentation clarifies the usage of `register-type` and accounts for the changes in the prior two commits. * fix: fix function signatures for generic memberless initers Filter out dummy field arguments. * docs: Add details about type name overrides to CInterop.md * docs: clarify that users can implement delete for registered types
1 parent 102181d commit f4bcc28

File tree

3 files changed

+161
-24
lines changed

3 files changed

+161
-24
lines changed

docs/CInterop.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This is an extension of what is covered in the [Language Guide](./LanguageGuide.
1414
- [`Generics`](#generics)
1515
- [`emit-c`](#unsafe-emit-c)
1616
- [`preproc`](#unsafe-preproc)
17+
- [Registering Types](#register-types)
1718
- [Callbacks](#callbacks)
1819

1920

@@ -409,6 +410,113 @@ in compiler output. If your helper functions, macros, or preprocessor
409410
directives are lengthy or complex, you may want to define them in a separate
410411
`h` file and `relative-include` it in your Carp source instead.
411412

413+
### Registering Types
414+
415+
Carp supports a few different ways of registering types defined in C. You can
416+
register types using the `register-type` function. Calling `register-type` with
417+
only a symbol argument registers the C type with a name corresponding to the
418+
symbol. For example, the following code registers the C type `A` as the type
419+
`A` in Carp.
420+
421+
```c
422+
typedef int A;
423+
```
424+
425+
```clojure
426+
(register-type A)
427+
```
428+
429+
After this call to `register-type`, you can use the type `A` anywhere type
430+
names are valid in Carp code. For example, you can use it in function
431+
signatures:
432+
433+
```clojure
434+
(sig a-prn (Fn [A] String))
435+
```
436+
437+
The prior type registration *only* registers the type name in Carp. In other
438+
words, the type is entirely "opaque" from the perspective of your Carp program.
439+
Carp knows the type exists, but it knows nothing about its implementation or
440+
how to construct values of the type--all of that is left up to your C code.
441+
442+
If you want to construct values of this type from Carp code, you have two
443+
options:
444+
445+
1. You can define your own initializers for the type in C and register them in Carp.
446+
2. You can use `register-type` to generate initializers for the type in Carp.
447+
448+
If you define an initializer for the type in C, you can access it from Carp by
449+
using `register`:
450+
451+
```c
452+
typedef int A;
453+
454+
A initializer() {
455+
return 0;
456+
}
457+
```
458+
459+
```clojure
460+
(register-type A)
461+
(register initializer (Fn [] A))
462+
;; returns a value of type A
463+
(initializer)
464+
```
465+
466+
Alternatively, you can add a non-empty array of type members in your
467+
`register-type` call to have Carp generate initializers, getters and setters,
468+
and printing functions for the external type. The initializer Carp generates
469+
will only initialize the fields you specify. If you omit or misname a field,
470+
the generated initializer might cause errors.
471+
472+
```clojure
473+
(register-type B [])
474+
:i B
475+
=> B : Type
476+
init : (Fn [] B)
477+
prn : (Fn [(Ref B q)] String)
478+
str : (Fn [(Ref B q)] String)
479+
}
480+
(register-type C [x Int])
481+
:i C
482+
=> C : Type
483+
C : Module {
484+
init : (Fn [Int] C)
485+
prn : (Fn [(Ref C q) String])
486+
str : (Fn [(Ref C q) String])
487+
set-x : (Fn [C, Int] C)
488+
set-x! : (Fn [(Ref C q), Int] ())
489+
update-x : (Fn [C, (Ref (Fn [Int] Int) q)] C)
490+
x : (Fn [(Ref C q)] (Ref Int q))
491+
}
492+
```
493+
494+
The `prn` and `str` functions for the type will also automatically implement
495+
their corresponding interfaces.
496+
497+
Be mindful that Carp *does not manage the memory associated with external types
498+
by default!* Unlike types defined in Carp, Carp will not generate `copy` and
499+
`delete` functions for registered types. If you use generated initializers for
500+
a registered type for convenience, remember that you still need to manage the
501+
memory associated with values of the type manually. If you want Carp to manage
502+
the memory for a registered type, you can provide implementations of the `copy`
503+
and `delete` interfaces.
504+
505+
If needed, you can override the name Carp emits for a registered type by
506+
providing an additional string argument. This comes in handy when the type's
507+
name in C does not follow lisp or Carp naming conventions. For example, the
508+
type in C might begin with a lowercase letter, while Carp requires all types to
509+
begin with uppercase letters:
510+
511+
```clojure
512+
;; Emitted in C code as "A"
513+
(register-type A)
514+
;; Emitted in C code a "a_type"
515+
(register-type A "a_type")
516+
;; Emitted in C code as "b_type"
517+
(register-type B "b_type" [x Int])
518+
```
519+
412520
## Callbacks
413521

414522
Some C APIs rely on callbacks, let's define a C function that accepts a

src/Deftype.hs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,17 @@ moduleForDeftype innerEnv typeEnv env pathStrings typeName typeVariables rest i
5959
-- The variable 'insidePath' is the path used for all member functions inside the 'typeModule'.
6060
-- For example (module Vec2 [x Float]) creates bindings like Vec2.create, Vec2.x, etc.
6161
insidePath = pathStrings ++ [typeName]
62+
initmembers = case rest of
63+
-- ANSI C does not allow empty structs. We add a dummy member here to account for this.
64+
-- Note that we *don't* add this member for external types--we leave those definitions up to the user.
65+
-- The corresponding field is emitted for the struct definition in Emit.hs
66+
[(XObj (Arr []) ii t)] -> [(XObj (Arr [(XObj (Sym (SymPath [] "__dummy") Symbol) Nothing Nothing), (XObj (Sym (SymPath [] "Char") Symbol) Nothing Nothing)]) ii t)]
67+
_ -> rest
6268
in do
6369
validateMemberCases typeEnv env typeVariables rest
6470
let structTy = StructTy (ConcreteNameTy (SymPath pathStrings typeName)) typeVariables
6571
(okMembers, membersDeps) <- templatesForMembers typeEnv env insidePath structTy rest
66-
okInit <- binderForInit insidePath structTy rest
72+
okInit <- binderForInit insidePath structTy initmembers
6773
(okStr, strDeps) <- binderForStrOrPrn typeEnv env insidePath structTy rest "str"
6874
(okPrn, _) <- binderForStrOrPrn typeEnv env insidePath structTy rest "prn"
6975
(okDelete, deleteDeps) <- binderForDelete typeEnv env insidePath structTy rest
@@ -336,15 +342,21 @@ templateUpdater member _ =
336342
-- | Helper function to create the binder for the 'init' template.
337343
binderForInit :: [String] -> Ty -> [XObj] -> Either TypeError (String, Binder)
338344
binderForInit insidePath structTy@(StructTy (ConcreteNameTy _) _) [XObj (Arr membersXObjs) _ _] =
339-
if isTypeGeneric structTy
340-
then Right (genericInit StackAlloc insidePath structTy membersXObjs)
341-
else
342-
Right $
343-
instanceBinder
344-
(SymPath insidePath "init")
345-
(FuncTy (initArgListTypes membersXObjs) structTy StaticLifetimeTy)
346-
(concreteInit StackAlloc structTy membersXObjs)
347-
("creates a `" ++ show structTy ++ "`.")
345+
-- Remove the __dummy field from the members array to ensure we can call the initializer with no arguments.
346+
-- See the implementation of moduleForDeftype for more details.
347+
let nodummy = case membersXObjs of
348+
[(XObj (Sym (SymPath [] "__dummy") Symbol) Nothing Nothing), (XObj (Sym (SymPath [] "Char") Symbol) Nothing Nothing)] -> []
349+
_ -> membersXObjs
350+
in if isTypeGeneric structTy
351+
then Right (genericInit StackAlloc insidePath structTy membersXObjs)
352+
else
353+
Right $
354+
instanceBinder
355+
(SymPath insidePath "init")
356+
-- don't include the dummy field in arg lists
357+
(FuncTy (initArgListTypes nodummy) structTy StaticLifetimeTy)
358+
(concreteInit StackAlloc structTy membersXObjs)
359+
("creates a `" ++ show structTy ++ "`.")
348360
binderForInit _ _ _ = error "binderforinit"
349361

350362
-- | Generate a list of types from a deftype declaration.
@@ -361,7 +373,7 @@ concreteInit allocationMode originalStructTy@(StructTy (ConcreteNameTy _) _) mem
361373
let mappings = unifySignatures originalStructTy concreteStructTy
362374
correctedMembers = replaceGenericTypeSymbolsOnMembers mappings membersXObjs
363375
memberPairs = memberXObjsToPairs correctedMembers
364-
in (toTemplate $ "$p $NAME(" ++ joinWithComma (map memberArg (unitless memberPairs)) ++ ")")
376+
in (toTemplate $ "$p $NAME(" ++ joinWithComma (map memberArg (nodummy (unitless memberPairs))) ++ ")")
365377
)
366378
( \(FuncTy _ concreteStructTy _) ->
367379
let mappings = unifySignatures originalStructTy concreteStructTy
@@ -371,6 +383,9 @@ concreteInit allocationMode originalStructTy@(StructTy (ConcreteNameTy _) _) mem
371383
(\FuncTy {} -> [])
372384
where
373385
unitless = remove (isUnit . snd)
386+
nodummy = remove (isDummy . fst)
387+
isDummy "__dummy" = True
388+
isDummy _ = False
374389
concreteInit _ _ _ = error "concreteinit"
375390

376391
-- | The template for the 'init' and 'new' functions for a generic deftype.
@@ -379,7 +394,7 @@ genericInit allocationMode pathStrings originalStructTy@(StructTy (ConcreteNameT
379394
defineTypeParameterizedTemplate templateCreator path t docs
380395
where
381396
path = SymPath pathStrings "init"
382-
t = FuncTy (map snd (memberXObjsToPairs membersXObjs)) originalStructTy StaticLifetimeTy
397+
t = FuncTy (map snd (nodummy (memberXObjsToPairs membersXObjs))) originalStructTy StaticLifetimeTy
383398
docs = "creates a `" ++ show originalStructTy ++ "`."
384399
templateCreator = TemplateCreator $
385400
\typeEnv env ->
@@ -389,7 +404,7 @@ genericInit allocationMode pathStrings originalStructTy@(StructTy (ConcreteNameT
389404
let mappings = unifySignatures originalStructTy concreteStructTy
390405
correctedMembers = replaceGenericTypeSymbolsOnMembers mappings membersXObjs
391406
memberPairs = memberXObjsToPairs correctedMembers
392-
in (toTemplate $ "$p $NAME(" ++ joinWithComma (map memberArg (remove (isUnit . snd) memberPairs)) ++ ")")
407+
in (toTemplate $ "$p $NAME(" ++ joinWithComma (map memberArg (nodummy (remove (isUnit . snd) memberPairs))) ++ ")")
393408
)
394409
( \(FuncTy _ concreteStructTy _) ->
395410
let mappings = unifySignatures originalStructTy concreteStructTy
@@ -401,6 +416,9 @@ genericInit allocationMode pathStrings originalStructTy@(StructTy (ConcreteNameT
401416
Left _ -> []
402417
Right ok -> ok
403418
)
419+
nodummy = remove (isDummy . fst)
420+
isDummy "__dummy" = True
421+
isDummy _ = False
404422
genericInit _ _ _ _ = error "genericinit"
405423

406424
tokensForInit :: AllocationMode -> String -> [XObj] -> [Token]
@@ -421,7 +439,7 @@ tokensForInit allocationMode typeName membersXObjs =
421439
"}"
422440
]
423441
where
424-
assignments [] = " instance.__dummy = 0;"
442+
assignments [] = ""
425443
assignments _ = go unitless
426444
where
427445
go [] = ""
@@ -537,9 +555,13 @@ calculateStructStrSize typeEnv env members s@(StructTy (ConcreteNameTy _) _) =
537555
calculateStructStrSize _ _ _ _ = error "calculatestructstrsize"
538556

539557
-- | Generate C code for assigning to a member variable.
540-
-- | Needs to know if the instance is a pointer or stack variable.
558+
-- Needs to know if the instance is a pointer or stack variable.
559+
-- Also handles the special dummy member we add for empty structs to be ANSI C compatible.
541560
memberAssignment :: AllocationMode -> String -> String
542-
memberAssignment allocationMode memberName = " instance" ++ sep ++ memberName ++ " = " ++ memberName ++ ";"
561+
memberAssignment allocationMode memberName =
562+
case memberName of
563+
"__dummy" -> " instance" ++ sep ++ memberName ++ " = " ++ "0" ++ ";"
564+
_ -> " instance" ++ sep ++ memberName ++ " = " ++ memberName ++ ";"
543565
where
544566
sep = case allocationMode of
545567
StackAlloc -> "."

src/Primitives.hs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,10 @@ primitiveRegisterTypeWithFields ctx x t override members =
263263
Right ctx' = update ctx
264264
-- TODO: Another case where define does not get formally qualified deps!
265265
contextWithDefs <- liftIO $ foldM (define True) ctx' (map Qualified deps)
266-
pure (contextWithDefs, dynamicNil)
266+
autoDerive contextWithDefs (StructTy (ConcreteNameTy (unqualify path')) [])
267+
[ lookupBinderInTypeEnv contextWithDefs (markQualified (SymPath [] "str")),
268+
lookupBinderInTypeEnv contextWithDefs (markQualified (SymPath [] "prn"))
269+
]
267270
path = SymPath [] t
268271
preExistingModule = case lookupBinderInGlobalEnv ctx path of
269272
Right (Binder _ (XObj (Mod found et) _ _)) -> Just (found, et)
@@ -612,6 +615,10 @@ deftype ctx x@(XObj (Sym (SymPath [] name) _) _ _) constructor =
612615
case e of
613616
Left err -> pure (evalError ctx (show err) (xobjInfo x))
614617
Right t -> autoDerive ctxWithType t
618+
[ lookupBinderInTypeEnv ctxWithType (markQualified (SymPath [] "delete")),
619+
lookupBinderInTypeEnv ctxWithType (markQualified (SymPath [] "str")),
620+
lookupBinderInTypeEnv ctxWithType (markQualified (SymPath [] "copy"))
621+
]
615622
deftype ctx x@(XObj (Lst ((XObj (Sym (SymPath [] name) _) _ _) : tyvars)) _ _) constructor =
616623
do
617624
(ctxWithType, e) <-
@@ -623,6 +630,10 @@ deftype ctx x@(XObj (Lst ((XObj (Sym (SymPath [] name) _) _ _) : tyvars)) _ _) c
623630
case e of
624631
Left err -> pure (evalError ctx (show err) (xobjInfo x))
625632
Right t -> autoDerive ctxWithType t
633+
[ lookupBinderInTypeEnv ctxWithType (markQualified (SymPath [] "delete")),
634+
lookupBinderInTypeEnv ctxWithType (markQualified (SymPath [] "str")),
635+
lookupBinderInTypeEnv ctxWithType (markQualified (SymPath [] "copy"))
636+
]
626637
deftype ctx name _ = pure $ toEvalError ctx name (InvalidTypeName name)
627638

628639
checkVariables :: [XObj] -> Maybe [Ty]
@@ -658,21 +669,17 @@ unwrapTypeErr ctx (Left err) = Left (typeErrorToString ctx err)
658669
unwrapTypeErr _ (Right x) = Right x
659670

660671
-- | Automatically derive implementations of interfaces.
661-
autoDerive :: Context -> Ty -> IO (Context, Either EvalError XObj)
662-
autoDerive c ty =
672+
autoDerive :: Context -> Ty -> [Either ContextError Binder] -> IO (Context, Either EvalError XObj)
673+
autoDerive c ty interfaces =
663674
let (SymPath mods tyname) = (getStructPath ty)
664675
implBinder :: String -> Ty -> Binder
665676
implBinder name t = Binder emptyMeta (XObj (Sym (SymPath (mods ++ [tyname]) name) Symbol) (Just dummyInfo) (Just t))
666677
getSig :: String -> Ty
667678
getSig "delete" = FuncTy [ty] UnitTy StaticLifetimeTy
668679
getSig "str" = FuncTy [RefTy ty (VarTy "q")] StringTy StaticLifetimeTy
680+
getSig "prn" = FuncTy [RefTy ty (VarTy "q")] StringTy StaticLifetimeTy
669681
getSig "copy" = FuncTy [RefTy ty (VarTy "q")] ty StaticLifetimeTy
670682
getSig _ = VarTy "z"
671-
interfaces =
672-
[ lookupBinderInTypeEnv c (markQualified (SymPath [] "delete")),
673-
lookupBinderInTypeEnv c (markQualified (SymPath [] "str")),
674-
lookupBinderInTypeEnv c (markQualified (SymPath [] "copy"))
675-
]
676683
registration interface =
677684
let name = getSimpleName (binderXObj interface)
678685
sig = getSig name

0 commit comments

Comments
 (0)