Skip to content

Commit 5077cde

Browse files
authored
Merge pull request #1 from savi-lang/add/initial-library
Add initial `IPAddress` library code.
2 parents 885c785 + 68374cc commit 5077cde

7 files changed

Lines changed: 160 additions & 3 deletions

File tree

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright DATE COPYRIGHT_HOLDER
1+
Copyright 2022 Joe Eli McIlvain
22

33
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
44

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
A base repository for Savi language libraries, with common CI actions configured.
1+
# IPAddress
22

3-
See the [Guide](https://github.com/savi-lang/base-standard-library/wiki/Guide) for details on how it works and how to use it for your own libraries.
3+
A Savi standard library for IPv4 and IPv6 address parsing, printing, and manipulation.

manifest.savi

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
:manifest lib IPAddress
2+
:sources "src/*.savi"
3+
4+
:manifest bin "spec"
5+
:copies IPAddress
6+
:sources "spec/*.savi"
7+
8+
:dependency Spec v0
9+
:from "github:savi-lang/Spec"
10+
:depends on Map
11+
:depends on Time
12+
:depends on Timer
13+
14+
:transitive dependency Map v0
15+
:from "github:savi-lang/Map"
16+
17+
:transitive dependency Time v0
18+
:from "github:savi-lang/Time"
19+
20+
:transitive dependency Timer v0
21+
:from "github:savi-lang/Timer"
22+
:depends on Time

spec/IPAddress.Spec.savi

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
:class IPAddress.Spec
2+
:is Spec
3+
:const describes: "IPAddress"
4+
5+
:it "creates an IPv4 address from a raw U32 value"
6+
assert: "\(IPAddress.new_raw_v4(0x00_00_00_00))" == "0.0.0.0"
7+
assert: "\(IPAddress.new_raw_v4(0xFF_FF_FF_FF))" == "255.255.255.255"
8+
assert: "\(IPAddress.new_raw_v4(0x01_23_45_67))" == "1.35.69.103"
9+
assert: "\(IPAddress.new_raw_v4(0xFE_DC_BA_98))" == "254.220.186.152"
10+
11+
:it "creates an IPv6 address from two raw U64 values"
12+
assert: "\(IPAddress.new_raw_v6(0, 0))" == "::"
13+
assert: "\(IPAddress.new_raw_v6(0, 1))" == "::1"
14+
assert: "\(IPAddress.new_raw_v6(0xFF02_0000_0000_0000, 0))" == "ff02::"
15+
assert: "\(IPAddress.new_raw_v6(0xFF02_0000_0000_0000, 1))" == "ff02::1"
16+
assert: "\(IPAddress.new_raw_v6(0xFF02_03EE_0000_0000, 0))" == "ff02:3ee::"
17+
assert: "\(IPAddress.new_raw_v6(0xFF02_03EE_0000_0000, 1))" == "ff02:3ee::1"
18+
assert: "\(IPAddress.new_raw_v6(0xB, 0x2))" == "::b:0:0:0:2"
19+
assert: (
20+
"\(IPAddress.new_raw_v6(0x0123_4567_89AB_CDEF, 0xFEDC_BA98_7654_3210))"
21+
== "123:4567:89ab:cdef:fedc:ba98:7654:3210"
22+
)

spec/Main.savi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:actor Main
2+
:new (env Env)
3+
Spec.Process.run(env, [
4+
Spec.Run(IPAddress.Spec).new(env)
5+
])

src/IPAddress.Format.savi

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
:struct IPAddress.Format
2+
:is IntoString
3+
4+
:let _value IPAddress'box
5+
:new (@_value)
6+
7+
:fun into_string_space USize:
8+
if @_value.is_v4 (
9+
"xxx.xxx.xxx.xxx".size
10+
|
11+
"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx".size
12+
)
13+
14+
:fun into_string(out String'iso) String'iso
15+
at_start = True
16+
if @_value.is_v4 (
17+
@_value._v4_each_u8 -> (u8 |
18+
if !at_start out.push_byte('.')
19+
at_start = False
20+
out = u8.into_string(--out)
21+
)
22+
|
23+
previous_deferred_zero = False
24+
is_compressing_zeros = False
25+
did_compress_zeros = False
26+
@_value._v6_each_u16 -> (u16 |
27+
if (u16 == 0 && !did_compress_zeros) (
28+
case (
29+
| is_compressing_zeros | next // go straight to the next u16
30+
| previous_deferred_zero |
31+
out << "::"
32+
at_start = True
33+
previous_deferred_zero = False
34+
is_compressing_zeros = True
35+
next
36+
|
37+
previous_deferred_zero = True
38+
next
39+
)
40+
|
41+
case (
42+
| is_compressing_zeros |
43+
is_compressing_zeros = False
44+
did_compress_zeros = True
45+
| previous_deferred_zero |
46+
if !at_start out.push_byte(':')
47+
at_start = False
48+
out.push_byte('0')
49+
previous_deferred_zero = False
50+
)
51+
)
52+
if !at_start out.push_byte(':')
53+
at_start = False
54+
out = u16
55+
.format.hex.bare.lowercase.without_leading_zeros
56+
.into_string(--out)
57+
)
58+
)
59+
--out

src/IPAddress.savi

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
:struct IPAddress
2+
:let _raw_v6_hi U64
3+
:let _raw_v6_lo U64
4+
5+
// This field indicates whether the address is IPv4 or IPv6, and also
6+
// indicates the number of leading significant bits (i.e. a CIDR mask).
7+
//
8+
// For IPv6, this is in the 0 to -128 range, indicating 0 to 128 CIDR bits.
9+
:let _bits I8
10+
11+
:fun is_v6: @_bits <= 0
12+
:fun is_v4: @_bits > 0
13+
14+
:fun _v6_cidr_bits U8: if (@_bits == -128) (128 | @_bits.negate.u8)
15+
:fun _v4_cidr_bits U8: @_bits.u8.bit_shr(8).bit_and(0x3F)
16+
17+
// For IPv4, the raw bytes are kept in the low 32 bits of the IPv6 raw bytes.
18+
// Here we define a getter and setter function to let us access them easily.
19+
:fun _raw_v4: @_raw_v6_lo.u32
20+
21+
:new val new_raw_v4(raw_v4 U32): @_raw_v6_hi = 0, @_raw_v6_lo = raw_v4.u64, @_bits = 96
22+
:new val new_raw_v6(@_raw_v6_hi, @_raw_v6_lo): @_bits = -128
23+
24+
:fun _v4_each_u8
25+
raw = @_raw_v4
26+
yield raw.bit_shr(24).u8
27+
yield raw.bit_shr(16).u8
28+
yield raw.bit_shr(8).u8
29+
yield raw.u8
30+
@
31+
32+
:fun _v6_each_u16
33+
raw_hi = @_raw_v6_hi
34+
raw_lo = @_raw_v6_lo
35+
yield raw_hi.bit_shr(48).u16
36+
yield raw_hi.bit_shr(32).u16
37+
yield raw_hi.bit_shr(16).u16
38+
yield raw_hi.u16
39+
yield raw_lo.bit_shr(48).u16
40+
yield raw_lo.bit_shr(32).u16
41+
yield raw_lo.bit_shr(16).u16
42+
yield raw_lo.u16
43+
@
44+
45+
:fun format: IPAddress.Format.new(@)
46+
47+
:is IntoString
48+
:fun into_string(out String'iso): @format.into_string(--out)
49+
:fun into_string_space: @format.into_string_space

0 commit comments

Comments
 (0)