Skip to content

Commit 5c00b03

Browse files
committed
feat: add WriteOnceReadMany and utils
`UNIT_SYMBOLS`, `UNIT_VALUES`, `UNIT_MAPPING`, `ALL_VALUES`,`ALL_SYMBOLS`, ALL_MAPPING`, `SYMBOLIC_UNIT_VALUES` are instances of this collection type. With this data type only a certain set of operations are permitted on these collections.
1 parent 2c869e3 commit 5c00b03

File tree

5 files changed

+82
-31
lines changed

5 files changed

+82
-31
lines changed

src/DynamicQuantities.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ export ustrip, dimension
1111
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
1212
export uparse, @u_str, sym_uparse, @us_str, uexpand, uconvert
1313

14+
const INDEX_TYPE = UInt16
15+
1416
include("internal_utils.jl")
1517
include("fixed_rational.jl")
18+
include("write_once_read_many.jl")
1619
include("types.jl")
1720
include("utils.jl")
1821
include("math.jl")
@@ -39,7 +42,7 @@ using .Units: UNIT_SYMBOLS
3942
let _units_import_expr = :(using .Units: m, g)
4043
append!(
4144
_units_import_expr.args[1].args,
42-
map(s -> Expr(:(.), s), filter(s -> s (:m, :g), UNIT_SYMBOLS))
45+
Expr(:(.), s) for s in UNIT_SYMBOLS if s (:m, :g)
4346
)
4447
eval(_units_import_expr)
4548
end

src/register_units.jl

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import .SymbolicUnits:
33
SymbolicDimensionsSingleton, SYMBOLIC_UNIT_VALUES, update_symbolic_unit_values!
44

55
# Update the unit collections
6-
function update_unit_mapping(name, value, unit_mapping::Dict{Symbol,Int} = UNIT_MAPPING)
7-
unit_mapping[name] = length(unit_mapping) + 1
8-
end
6+
const UNIT_UPDATE_LOCK = Threads.SpinLock()
97

108
function update_all_values(name_symbol, unit)
11-
push!(ALL_SYMBOLS, name_symbol)
12-
push!(ALL_VALUES, unit)
13-
ALL_MAPPING[name_symbol] = INDEX_TYPE(length(ALL_MAPPING) + 1)
9+
lock(UNIT_UPDATE_LOCK) do
10+
push!(ALL_SYMBOLS, name_symbol)
11+
push!(ALL_VALUES, unit)
12+
i = lastindex(ALL_VALUES)
13+
ALL_MAPPING[name_symbol] = i
14+
UNIT_MAPPING[name_symbol] = i
15+
update_symbolic_unit_values!(name_symbol)
16+
end
1417
end
1518

1619
# Register
@@ -20,16 +23,20 @@ end
2023

2124
function _register_unit(name::Symbol, value)
2225
name_symbol = Meta.quot(name)
23-
reg_expr = _lazy_register_unit(name, value)
24-
push!(reg_expr.args,
25-
quote
26-
$update_unit_mapping($name_symbol, $value)
26+
index = get(ALL_MAPPING, name, INDEX_TYPE(0))
27+
if iszero(index)
28+
reg_expr = _lazy_register_unit(name, value)
29+
push!(reg_expr.args, quote
2730
$update_all_values($name_symbol, $value)
28-
$update_symbolic_unit_values!($name_symbol)
29-
# suppress the print of `SYMBOLIC_UNIT_VALUES`
3031
nothing
31-
end
32-
)
33-
return reg_expr
32+
end)
33+
return reg_expr
34+
else
35+
unit = ALL_VALUES[index]
36+
# When a utility function to expand `value` to its final form becomes
37+
# available, enable the following check. This will avoid throwing an error
38+
# if user is trying to register an existing unit with matching values.
39+
# unit.value != value && throw("Unit $name is already defined as $unit")
40+
throw("Unit `$name` is already defined as `$unit`")
41+
end
3442
end
35-

src/symbolic_dimensions.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import ..WriteOnceReadMany
2+
import ..INDEX_TYPE
13
import .Units: UNIT_SYMBOLS, UNIT_MAPPING, UNIT_VALUES
24
import .Constants: CONSTANT_SYMBOLS, CONSTANT_MAPPING, CONSTANT_VALUES
35

@@ -8,13 +10,9 @@ disambiguate_symbol(s) = s in SYMBOL_CONFLICTS ? Symbol(s, :_constant) : s
810
# Prefer units over constants:
911
# For example, this means we can't have a symbolic Planck's constant,
1012
# as it is just "hours" (h), which is more common.
11-
const INDEX_TYPE = UInt16
12-
# Prefer units over constants:
13-
# For example, this means we can't have a symbolic Planck's constant,
14-
# as it is just "hours" (h), which is more common.
15-
const ALL_SYMBOLS = [UNIT_SYMBOLS..., disambiguate_symbol.(CONSTANT_SYMBOLS)...]
16-
const ALL_VALUES = [UNIT_VALUES..., CONSTANT_VALUES...]
17-
const ALL_MAPPING = Dict(ALL_SYMBOLS .=> (INDEX_TYPE(1):INDEX_TYPE(length(ALL_SYMBOLS))))
13+
const ALL_SYMBOLS = WriteOnceReadMany([UNIT_SYMBOLS..., disambiguate_symbol.(CONSTANT_SYMBOLS)...])
14+
const ALL_VALUES = WriteOnceReadMany([UNIT_VALUES..., CONSTANT_VALUES...])
15+
const ALL_MAPPING = WriteOnceReadMany(Dict(s => INDEX_TYPE(i) for (i, s) in enumerate(ALL_SYMBOLS)))
1816

1917
"""
2018
AbstractSymbolicDimensions{R} <: AbstractDimensions{R}
@@ -375,6 +373,7 @@ module SymbolicUnits
375373
import ..DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE
376374
import ..DEFAULT_VALUE_TYPE
377375
import ..DEFAULT_DIM_BASE_TYPE
376+
import ..WriteOnceReadMany
378377

379378
# Lazily create unit symbols (since there are so many)
380379
module Constants
@@ -403,7 +402,7 @@ module SymbolicUnits
403402
import .Constants as SymbolicConstants
404403
import .Constants: SYMBOLIC_CONSTANT_VALUES
405404

406-
const SYMBOLIC_UNIT_VALUES = DEFAULT_SYMBOLIC_QUANTITY_TYPE[]
405+
const SYMBOLIC_UNIT_VALUES = WriteOnceReadMany{Vector{DEFAULT_SYMBOLIC_QUANTITY_TYPE}}()
407406

408407
function update_symbolic_unit_values!(unit, symbolic_unit_values = SYMBOLIC_UNIT_VALUES)
409408
@eval begin
@@ -415,7 +414,8 @@ module SymbolicUnits
415414
end
416415
end
417416

418-
update_symbolic_unit_values!.(UNIT_SYMBOLS)
417+
update_symbolic_unit_values!(w::WriteOnceReadMany) = update_symbolic_unit_values!.(w._raw_data)
418+
update_symbolic_unit_values!(UNIT_SYMBOLS)
419419

420420
"""
421421
sym_uparse(raw_string::AbstractString)

src/units.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
module Units
22

3+
import ..WriteOnceReadMany
34
import ..DEFAULT_DIM_TYPE
45
import ..DEFAULT_VALUE_TYPE
56
import ..DEFAULT_QUANTITY_TYPE
67

78
@assert DEFAULT_VALUE_TYPE == Float64 "`units.jl` must be updated to support a different default value type."
89

9-
const UNIT_SYMBOLS = Symbol[]
10-
const UNIT_VALUES = DEFAULT_QUANTITY_TYPE[]
11-
const UNIT_MAPPING = Dict{Symbol,Int}()
10+
const UNIT_SYMBOLS = WriteOnceReadMany{Vector{Symbol}}()
11+
const UNIT_VALUES = WriteOnceReadMany{Vector{DEFAULT_QUANTITY_TYPE}}()
1212

1313
macro _lazy_register_unit(name, value)
1414
return esc(_lazy_register_unit(name, value))
@@ -22,7 +22,6 @@ end
2222
function _lazy_register_unit(name::Symbol, value)
2323
name_symbol = Meta.quot(name)
2424
quote
25-
haskey($UNIT_MAPPING, $name_symbol) && throw("Unit $($name_symbol) already exists.")
2625
const $name = $value
2726
push!($UNIT_SYMBOLS, $name_symbol)
2827
push!($UNIT_VALUES, $name)
@@ -207,6 +206,6 @@ end
207206
# The user should define these instead.
208207

209208
# Update `UNIT_MAPPING` with all internally defined unit symbols.
210-
merge!(UNIT_MAPPING, Dict(UNIT_SYMBOLS .=> 1:lastindex(UNIT_SYMBOLS)))
209+
const UNIT_MAPPING = WriteOnceReadMany(Dict(s => i for (i, s) in enumerate(UNIT_SYMBOLS)))
211210

212211
end

src/write_once_read_many.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
WriteOnceReadMany()
3+
4+
Used for storing units, values, symbolic-units.
5+
"""
6+
struct WriteOnceReadMany{V}
7+
_raw_data::V
8+
9+
WriteOnceReadMany(_raw_data) = new{typeof(_raw_data)}(_raw_data)
10+
WriteOnceReadMany{T}() where T = WriteOnceReadMany(T())
11+
end
12+
13+
# Utility functions
14+
for f in (:enumerate, :length, :lastindex)
15+
@eval begin
16+
Base.$f(w::WriteOnceReadMany) = $f(w._raw_data)
17+
end
18+
end
19+
20+
Base.getindex(w::WriteOnceReadMany, i::Union{Int, INDEX_TYPE, Symbol}) = getindex(w._raw_data, i)
21+
22+
Base.iterate(w::WriteOnceReadMany) = iterate(w._raw_data)
23+
Base.iterate(w::WriteOnceReadMany, i::Int) = iterate(w._raw_data, i)
24+
25+
Base.intersect(w::WriteOnceReadMany, v::AbstractSet) = intersect(w._raw_data, v)
26+
Base.intersect(v::AbstractSet, w::WriteOnceReadMany) = intersect(v, w._raw_data)
27+
28+
Base.push!(w::WriteOnceReadMany, val...) = push!(w._raw_data, val...)
29+
30+
for f in (:findfirst, :filter)
31+
@eval begin
32+
Base.$f(val::Function, w::WriteOnceReadMany) = $f(val, w._raw_data)
33+
end
34+
end
35+
36+
Base.setindex!(w::DynamicQuantities.WriteOnceReadMany{Dict{Symbol, INDEX_TYPE}}, i::Int, s::Symbol) = setindex!(w, INDEX_TYPE(i), s)
37+
function Base.setindex!(w::DynamicQuantities.WriteOnceReadMany{Dict{Symbol, T}}, i::T, s::Symbol) where T <: Union{Int, INDEX_TYPE}
38+
haskey(w._raw_data, s) && throw("Unit $s already exists at index $(w[s])")
39+
setindex!(w._raw_data, i, s)
40+
end
41+
42+
Base.get(w::WriteOnceReadMany{Dict{Symbol, INDEX_TYPE}}, a, b) = get(w._raw_data, a, b)

0 commit comments

Comments
 (0)