Skip to content

Commit c01106b

Browse files
committed
Merge branch 'master' of https://github.com/carp-lang/Carp into rectype
2 parents 4a28127 + c471fcc commit c01106b

14 files changed

+307
-46
lines changed

core/Array.carp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,30 @@ It will sum the previous sum with each new value, starting at `0`.")
4343
(set! total (~f total (unsafe-nth xs i))))
4444
total)))
4545

46+
(doc scan "Similar to `Array.reduce`, but instead returns an array with the starting element,
47+
and then all intermediate values.
48+
49+
For example, a scan using `Int.+` over the array [1 1 1 1 1] (starting at 0) will return [0 1 2 3 4 5].")
50+
(defn scan [f x xs]
51+
(let [n (length xs)
52+
ys (allocate (inc n))]
53+
(do
54+
(aset-uninitialized! &ys 0 @&x)
55+
(for [i 1 (inc n)]
56+
(aset-uninitialized! &ys i (~f (unsafe-nth &ys (dec i)) (unsafe-nth xs (dec i)))))
57+
ys)))
58+
59+
(doc endo-scan "Like `Array.scan`, but uses the first element of the array as the starting value.
60+
Also does not create a new array, but reuses the initial one instead (by taking ownership over `xs`.)
61+
62+
For example, an endo-scan using `Int.+` over the array [1 1 1 1 1] will return [1 2 3 4 5]")
63+
(defn endo-scan [f xs]
64+
(let [n (length &xs)]
65+
(do
66+
(for [i 1 n]
67+
(aset! &xs i (~f (unsafe-nth &xs (dec i)) (unsafe-nth &xs i))))
68+
xs)))
69+
4670
(doc empty? "checks whether the array `a` is empty.")
4771
(defn empty? [a]
4872
(= (Array.length a) 0))

core/ControlMacros.carp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
(defmacro => [:rest forms]
2929
(thread-first-internal forms))
3030

31-
(deprecated ==> "deprecated in favor of `==>`.")
31+
(deprecated ==> "deprecated in favor of `-->`.")
3232
(defmacro ==> [:rest forms]
3333
(thread-last-internal forms))
3434

core/StaticArray.carp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ stack-allocated. For a more flexible, heap-allocated version, you might want to
3838
(set! total (~f total (StaticArray.unsafe-nth xs i))))
3939
total)))
4040

41+
(doc scan! "Scans and replaces the array in-place, using a binary function.
42+
43+
For example, give `(def numbers [1 1 1])`, a `scan!` using `Int.+` will mutate `numbers` to be `[1 2 3]`.")
44+
(defn scan! [f xs]
45+
(let [n (StaticArray.length xs)]
46+
(for [i 1 n]
47+
(StaticArray.aset! xs i (~f @(unsafe-nth xs (dec i)) @(unsafe-nth xs i))))))
48+
4149
(doc = "compares two static arrays.")
4250
(defn = [a b]
4351
(if (/= (StaticArray.length a) (StaticArray.length b))

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: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,25 @@ moduleForDeftype innerEnv typeEnv env pathStrings typeName typeVariables rest i
6262
-- For example (module Vec2 [x Float]) creates bindings like Vec2.create, Vec2.x, etc.
6363
insidePath = pathStrings ++ [typeName]
6464
candidate = TypeCandidate {restriction = AllowOnlyNamesInScope, typename = typeName, variables = typeVariables, typemembers = [], interfaceConstraints = [], candidateTypeEnv = typeEnv, candidateEnv = env}
65+
initmembers = case rest of
66+
-- ANSI C does not allow empty structs. We add a dummy member here to account for this.
67+
-- Note that we *don't* add this member for external types--we leave those definitions up to the user.
68+
-- The corresponding field is emitted for the struct definition in Emit.hs
69+
[(XObj (Arr []) ii t)] -> [(XObj (Arr [(XObj (Sym (SymPath [] "__dummy") Symbol) Nothing Nothing), (XObj (Sym (SymPath [] "Char") Symbol) Nothing Nothing)]) ii t)]
70+
_ -> rest
6571
in do
66-
mems <- case rest of
72+
mems <- case initmembers of
6773
[XObj (Arr membersXObjs) _ _] -> Right membersXObjs
6874
_ -> Left $ NotAValidType (XObj (Sym (SymPath pathStrings typeName) Symbol) i (Just TypeTy))
6975
let structTy = StructTy (ConcreteNameTy (SymPath pathStrings typeName)) typeVariables
70-
ptrmembers = map (recursiveMembersToPointers structTy) rest
76+
ptrmembers = map (recursiveMembersToPointers structTy) initmembers
7177
innermems <- case ptrmembers of
7278
[XObj (Arr membersXObjs) _ _] -> Right membersXObjs
7379
_ -> Left $ NotAValidType (XObj (Sym (SymPath pathStrings typeName) Symbol) i (Just TypeTy))
7480
okRecursive (candidate {typemembers = mems})
7581
validateMembers typeEnv env (candidate {typemembers = innermems})
7682
(okMembers, membersDeps) <- templatesForMembers typeEnv env insidePath structTy ptrmembers
77-
okInit <- if (any (isValueRecursive structTy) ptrmembers) then recursiveProductInitBinder insidePath structTy ptrmembers else binderForInit insidePath structTy ptrmembers
83+
okInit <- if (any (isValueRecursive structTy) ptrmembers) then recursiveProductInitBinder insidePath structTy ptrmembers else binderForInit insidePath structTy initmembers
7884
okMake <- recursiveProductMakeBinder insidePath structTy ptrmembers
7985
(okStr, strDeps) <- binderForStrOrPrn typeEnv env insidePath structTy ptrmembers "str"
8086
(okPrn, _) <- binderForStrOrPrn typeEnv env insidePath structTy ptrmembers"prn"
@@ -359,16 +365,22 @@ templateUpdater member _ =
359365

360366
-- | Helper function to create the binder for the 'init' template.
361367
binderForInit :: [String] -> Ty -> [XObj] -> Either TypeError (String, Binder)
362-
binderForInit insidePath structTy@(StructTy (ConcreteNameTy _) _) [(XObj (Arr membersXObjs) _ _)] =
363-
if isTypeGeneric structTy
364-
then Right (genericInit StackAlloc insidePath structTy membersXObjs)
365-
else
366-
Right $
367-
instanceBinder
368-
(SymPath insidePath "init")
369-
(FuncTy (initArgListTypes membersXObjs) structTy StaticLifetimeTy)
370-
(concreteInit StackAlloc structTy membersXObjs)
371-
("creates a `" ++ show structTy ++ "`.")
368+
binderForInit insidePath structTy@(StructTy (ConcreteNameTy _) _) [XObj (Arr membersXObjs) _ _] =
369+
-- Remove the __dummy field from the members array to ensure we can call the initializer with no arguments.
370+
-- See the implementation of moduleForDeftype for more details.
371+
let nodummy = case membersXObjs of
372+
[(XObj (Sym (SymPath [] "__dummy") Symbol) Nothing Nothing), (XObj (Sym (SymPath [] "Char") Symbol) Nothing Nothing)] -> []
373+
_ -> membersXObjs
374+
in if isTypeGeneric structTy
375+
then Right (genericInit StackAlloc insidePath structTy membersXObjs)
376+
else
377+
Right $
378+
instanceBinder
379+
(SymPath insidePath "init")
380+
-- don't include the dummy field in arg lists
381+
(FuncTy (initArgListTypes nodummy) structTy StaticLifetimeTy)
382+
(concreteInit StackAlloc structTy membersXObjs)
383+
("creates a `" ++ show structTy ++ "`.")
372384
binderForInit _ _ _ = error "binderforinit"
373385

374386
-- | Generate a list of types from a deftype declaration.
@@ -385,7 +397,7 @@ concreteInit allocationMode originalStructTy@(StructTy (ConcreteNameTy _) _) mem
385397
let mappings = unifySignatures originalStructTy concreteStructTy
386398
correctedMembers = replaceGenericTypeSymbolsOnMembers mappings membersXObjs
387399
memberPairs = memberXObjsToPairs correctedMembers
388-
in (toTemplate $ "$p $NAME(" ++ joinWithComma (map memberArg (unitless memberPairs)) ++ ")")
400+
in (toTemplate $ "$p $NAME(" ++ joinWithComma (map memberArg (nodummy (unitless memberPairs))) ++ ")")
389401
)
390402
( \(FuncTy _ concreteStructTy _) ->
391403
let mappings = unifySignatures originalStructTy concreteStructTy
@@ -395,6 +407,9 @@ concreteInit allocationMode originalStructTy@(StructTy (ConcreteNameTy _) _) mem
395407
(\FuncTy {} -> [])
396408
where
397409
unitless = remove (isUnit . snd)
410+
nodummy = remove (isDummy . fst)
411+
isDummy "__dummy" = True
412+
isDummy _ = False
398413
concreteInit _ _ _ = error "concreteinit"
399414

400415
-- | The template for the 'init' and 'new' functions for a generic deftype.
@@ -403,7 +418,7 @@ genericInit allocationMode pathStrings originalStructTy@(StructTy (ConcreteNameT
403418
defineTypeParameterizedTemplate templateCreator path t docs
404419
where
405420
path = SymPath pathStrings "init"
406-
t = FuncTy (map snd (memberXObjsToPairs membersXObjs)) originalStructTy StaticLifetimeTy
421+
t = FuncTy (map snd (nodummy (memberXObjsToPairs membersXObjs))) originalStructTy StaticLifetimeTy
407422
docs = "creates a `" ++ show originalStructTy ++ "`."
408423
templateCreator = TemplateCreator $
409424
\typeEnv env ->
@@ -413,7 +428,7 @@ genericInit allocationMode pathStrings originalStructTy@(StructTy (ConcreteNameT
413428
let mappings = unifySignatures originalStructTy concreteStructTy
414429
correctedMembers = replaceGenericTypeSymbolsOnMembers mappings membersXObjs
415430
memberPairs = memberXObjsToPairs correctedMembers
416-
in (toTemplate $ "$p $NAME(" ++ joinWithComma (map memberArg (remove (isUnit . snd) memberPairs)) ++ ")")
431+
in (toTemplate $ "$p $NAME(" ++ joinWithComma (map memberArg (nodummy (remove (isUnit . snd) memberPairs))) ++ ")")
417432
)
418433
( \(FuncTy _ concreteStructTy _) ->
419434
let mappings = unifySignatures originalStructTy concreteStructTy
@@ -425,6 +440,9 @@ genericInit allocationMode pathStrings originalStructTy@(StructTy (ConcreteNameT
425440
Left _ -> []
426441
Right ok -> ok
427442
)
443+
nodummy = remove (isDummy . fst)
444+
isDummy "__dummy" = True
445+
isDummy _ = False
428446
genericInit _ _ _ _ = error "genericinit"
429447

430448
tokensForInit :: AllocationMode -> String -> [XObj] -> [Token]
@@ -445,7 +463,7 @@ tokensForInit allocationMode typeName membersXObjs =
445463
"}"
446464
]
447465
where
448-
assignments [] = " instance.__dummy = 0;"
466+
assignments [] = ""
449467
assignments _ = go unitless
450468
where
451469
go [] = ""
@@ -562,9 +580,13 @@ calculateStructStrSize typeEnv env members s@(StructTy (ConcreteNameTy _) _) =
562580
calculateStructStrSize _ _ _ _ = error "calculatestructstrsize"
563581

564582
-- | Generate C code for assigning to a member variable.
565-
-- | Needs to know if the instance is a pointer or stack variable.
583+
-- Needs to know if the instance is a pointer or stack variable.
584+
-- Also handles the special dummy member we add for empty structs to be ANSI C compatible.
566585
memberAssignment :: AllocationMode -> String -> String
567-
memberAssignment allocationMode memberName = " instance" ++ sep ++ memberName ++ " = " ++ memberName ++ ";"
586+
memberAssignment allocationMode memberName =
587+
case memberName of
588+
"__dummy" -> " instance" ++ sep ++ memberName ++ " = " ++ "0" ++ ";"
589+
_ -> " instance" ++ sep ++ memberName ++ " = " ++ memberName ++ ";"
568590
where
569591
sep = case allocationMode of
570592
StackAlloc -> "."

src/Emit.hs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,9 +446,7 @@ toC toCMode (Binder meta root) = emitterSrc (execState (visit startingIndent roo
446446
when isNotVoid $
447447
appendToSrc (addIndent indent' ++ retVar ++ " = " ++ caseExprRetVal ++ ";\n")
448448
let Just caseLhsInfo' = caseLhsInfo
449-
when
450-
(matchMode == MatchValue)
451-
(delete indent' caseLhsInfo')
449+
delete indent' caseLhsInfo'
452450
appendToSrc (addIndent indent ++ "}\n")
453451
in do
454452
exprVar <- visit indent expr
@@ -533,7 +531,8 @@ toC toCMode (Binder meta root) = emitterSrc (execState (visit startingIndent roo
533531
var <- visit indent value
534532
let Just t = ty
535533
fresh = mangle (freshVar info)
536-
appendToSrc (addIndent indent ++ tyToCLambdaFix t ++ " " ++ fresh ++ " = " ++ var ++ "; // From the 'the' function.\n")
534+
unless (isUnit t)
535+
(appendToSrc (addIndent indent ++ tyToCLambdaFix t ++ " " ++ fresh ++ " = " ++ var ++ "; // From the 'the' function.\n"))
537536
pure fresh
538537
-- Ref
539538
[XObj Ref _ _, value] ->

src/Expand.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ expand eval ctx xobj =
4141
Lst _ -> expandList xobj
4242
Arr _ -> expandArray xobj
4343
Sym _ _ -> expandSymbol xobj
44+
-- This case is needed to ensure we expand naked mod names to initers consistently.
45+
-- Consider both:
46+
-- (width (address &(B 2)))
47+
-- (width B)
48+
-- The first case is correct code and was handled by expandList. The second case is an error and previously resulted in a loop because
49+
-- module expansion wasn't handled in expandSymbol, but handling it there
50+
-- by ending the expansion loop breaks init expansion in the first case,
51+
-- since expandList calls expand.
52+
-- So, we have no choice but to add a case here to cut the recursion and to expand this form consistently in all places.
53+
Mod e _ ->
54+
let pathToModule = pathToEnv e
55+
implicitInit = XObj (Sym (SymPath pathToModule "init") Symbol) (xobjInfo xobj) (xobjTy xobj)
56+
in pure (ctx, Right implicitInit)
4457
_ -> pure (ctx, Right xobj)
4558
where
4659
expandList :: XObj -> IO (Context, Either EvalError XObj)

src/InitialTypes.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ initialTypes typeEnv rootEnv root = evalState (visit rootEnv root) 0
9898
If -> pure (Left (InvalidObj If xobj))
9999
While -> pure (Left (InvalidObj While xobj))
100100
Do -> pure (Left (InvalidObj Do xobj))
101-
(Mod _ _) -> pure (Left (InvalidObj If xobj))
101+
m@(Mod _ _) -> pure (Left (InvalidObj m xobj))
102102
e@(Deftype _) -> pure (Left (InvalidObj e xobj))
103103
e@(External _) -> pure (Left (InvalidObj e xobj))
104104
e@(ExternalType _) -> pure (Left (InvalidObj e xobj))

0 commit comments

Comments
 (0)