Skip to content

Commit

Permalink
Support numeric bitstrings
Browse files Browse the repository at this point in the history
  • Loading branch information
dusty-phillips committed Aug 24, 2024
1 parent 91152a3 commit e1d7971
Show file tree
Hide file tree
Showing 7 changed files with 415 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Some tasks below are marked easy if you want to get started.
This is a list of all outstanding `todo` expressions (Gleam todo expressions
are ) in the codebase, as of the last time that I updated this list.

- imports with attributes are not implemented yet
- imports with attributes are not handled yet
- type imports are not implemented yet
- Unlabelled fields in custom types are not generated yet
- Given that labelled and unlabelled fields can be mixed on one class, I have
Expand Down
45 changes: 45 additions & 0 deletions src/compiler/internal/generator/expressions.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ pub fn generate_expression(expression: python.Expression) -> StringBuilder {

python.BinaryOperator(name, left, right) ->
generate_binop(name, left, right)

python.BitString(segments) -> generate_bitstring(segments)
}
}

Expand Down Expand Up @@ -161,3 +163,46 @@ fn generate_binop(
|> string_builder.append(op_string)
|> string_builder.append_builder(generate_expression(right))
}

fn generate_bitstring(segments: List(python.BitStringSegment)) -> StringBuilder {
string_builder.from_string("gleam_bitstring_segments_to_bytes(")
|> string_builder.append_builder(internal.generate_plural(
segments,
generate_bitstring_segment,
", ",
))
|> string_builder.append(")")
}

fn generate_bitstring_segment(segment: python.BitStringSegment) -> StringBuilder {
generate_expression(segment.value)
|> string_builder.prepend("(")
|> string_builder.append(", [")
|> string_builder.append_builder(internal.generate_plural(
segment.options,
generate_bitstring_segment_option,
", ",
))
|> string_builder.append("])")
}

fn generate_bitstring_segment_option(
option: python.BitStringSegmentOption,
) -> StringBuilder {
case option {
python.SizeValueOption(expression) ->
generate_expression(expression)
|> string_builder.prepend("\"SizeValue\", ")
python.UnitOption(integer) ->
integer
|> int.to_string
|> string_builder.from_string
|> string_builder.prepend("\"Unit\", ")
python.FloatOption -> string_builder.from_string("\"Float\", None")
python.BigOption -> string_builder.from_string("\"Big\", None")
python.LittleOption -> string_builder.from_string("\"Little\", None")
python.NativeOption -> string_builder.from_string("\"Native\", None")
}
|> string_builder.prepend("(")
|> string_builder.append(")")
}
9 changes: 8 additions & 1 deletion src/compiler/internal/transformer.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,21 @@ pub fn merge_state_prepend(
prev: TransformState(ReversedList(a)),
current: ExpressionReturn,
map_next: fn(python.Expression) -> a,
) -> TransformState(List(a)) {
) -> TransformState(ReversedList(a)) {
merge_state(
prev,
current,
list.prepend(prev.item, map_next(current.expression)),
)
}

pub fn map_state_prepend(
prev: TransformState(ReversedList(a)),
next: a,
) -> TransformState(ReversedList(a)) {
TransformState(prev.context, prev.statements, prev.item |> list.prepend(next))
}

pub fn reverse_state_to_return(
state: TransformState(ReversedList(a)),
mapper: fn(List(a)) -> python.Expression,
Expand Down
85 changes: 81 additions & 4 deletions src/compiler/internal/transformer/statements.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,13 @@ fn transform_expression(
glance.RecordUpdate(record:, fields:, ..) ->
transform_record_update(context, record, fields)

glance.BitString(_) as expr -> {
pprint.debug(expr)
todo as "BitString expressions not supported yet"
glance.BitString(segments) -> {
segments
|> list.fold(
internal.TransformState(context, [], []),
fold_bitstring_segment,
)
|> internal.reverse_state_to_return(python.BitString)
}
}
}
Expand Down Expand Up @@ -488,7 +492,7 @@ fn transform_record_update(
let record_result = transform_expression(context, record)
fields
|> list.fold(
internal.TransformState(record_result.context, [], []),
internal.TransformState(record_result.context, record_result.statements, []),
fn(state, tuple) {
internal.merge_state_prepend(
state,
Expand All @@ -502,3 +506,76 @@ fn transform_record_update(
fields: _,
))
}

fn fold_bitstring_segment(
state: internal.TransformState(internal.ReversedList(python.BitStringSegment)),
segment: #(
glance.Expression,
List(glance.BitStringSegmentOption(glance.Expression)),
),
) -> internal.TransformState(internal.ReversedList(python.BitStringSegment)) {
let #(expression, options) = segment
let expression_result = transform_expression(state.context, expression)
let options_result =
options
|> list.fold(
internal.TransformState(
expression_result.context,
expression_result.statements,
[],
),
fold_bitsting_segment_option,
)

internal.TransformState(
options_result.context,
options_result.statements,
list.prepend(
state.item,
python.BitStringSegment(
expression_result.expression,
options_result.item |> list.reverse,
),
),
)
}

fn fold_bitsting_segment_option(
state: internal.TransformState(
internal.ReversedList(python.BitStringSegmentOption),
),
option: glance.BitStringSegmentOption(glance.Expression),
) -> internal.TransformState(
internal.ReversedList(python.BitStringSegmentOption),
) {
case option {
glance.FloatOption -> internal.map_state_prepend(state, python.FloatOption)
glance.LittleOption ->
internal.map_state_prepend(state, python.LittleOption)
glance.BigOption -> internal.map_state_prepend(state, python.BigOption)
glance.NativeOption ->
internal.map_state_prepend(state, python.NativeOption)
glance.UnitOption(size) ->
internal.map_state_prepend(state, python.UnitOption(size))
glance.SizeOption(size) ->
internal.map_state_prepend(
state,
python.SizeValueOption(python.Number(size |> int.to_string)),
)
glance.SizeValueOption(expression) -> {
let expression_result = transform_expression(state.context, expression)
internal.merge_state_prepend(
state,
expression_result,
python.SizeValueOption,
)
}
glance.SignedOption | glance.UnsignedOption -> {
panic as "Signed and unsigned are not valid when constructing bitstrings"
}
_ -> {
pprint.debug(option)
todo as "Some bitstring segment options not supported yet"
}
}
}
14 changes: 14 additions & 0 deletions src/compiler/python.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ pub type Expression {
Call(function: Expression, arguments: List(Field(Expression)))
RecordUpdate(record: Expression, fields: List(Field(Expression)))
BinaryOperator(name: BinaryOperator, left: Expression, right: Expression)
BitString(List(BitStringSegment))
}

pub type BitStringSegment {
BitStringSegment(value: Expression, options: List(BitStringSegmentOption))
}

pub type BitStringSegmentOption {
SizeValueOption(Expression)
UnitOption(Int)
FloatOption
LittleOption
BigOption
NativeOption
}

pub type Statement {
Expand Down
85 changes: 82 additions & 3 deletions src/python_prelude.gleam
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
pub const gleam_builtins = "
import dataclasses
import sys
import typing
import struct
class GleamPanic(BaseException):
pass
GleamListElem = typing.TypeVar(\"GleamListElem\")
GleamListElem = typing.TypeVar('GleamListElem')
class GleamList(typing.Generic[GleamListElem]):
Expand All @@ -18,11 +20,11 @@ class GleamList(typing.Generic[GleamListElem]):
strs.append(head.value)
head = head.tail
return \"GleamList([\" + \", \".join(strs) + \"])\"
return 'GleamList([' + ', '.join(strs) + '])'
class NonEmptyGleamList(GleamList[GleamListElem]):
__slots__ = [\"value\", \"tail\"]
__slots__ = ['value', 'tail']
is_empty = False
def __init__(self, value: GleamListElem, tail: GleamList[GleamListElem]):
Expand All @@ -41,6 +43,83 @@ def to_gleam_list(elements: list[GleamListElem], tail: GleamList = EmptyGleamLis
for element in reversed(elements):
head = NonEmptyGleamList(element, head)
return head
def gleam_bitstring_segments_to_bytes(*segments):
result = bytearray()
for segment in segments:
result.extend(gleam_bitstring_segment_to_bytes(segment))
return bytes(result)
def gleam_bitstring_segment_to_bytes(segment) -> bytes:
value, options = segment
size = None
unit = None
type = None
endianness = 'big'
for option in options:
match option:
case ('SizeValue', size):
size = size
case('Unit', unit):
unit = unit
case('Little', None):
endianness = 'little'
case('Big', None):
endianness = 'big'
case('Native', None):
endianness = sys.byteorder
case('Float', None):
type = 'float'
case _:
raise Exception(f'Unexpected bitstring option {option}')
# Defaults from https://www.erlang.org/doc/system/bit_syntax.html
if type == None:
type = 'int'
if size == None:
match type:
case 'int':
size = 8
case 'float':
size = 64
if unit == None:
match type:
case 'int':
unit = 1
case 'float':
unit = 1
bitsize = unit * size
if bitsize % 8:
raise Exception(f'Python bitstrings must be byte aligned, but got {bitsize}')
bytesize = bitsize // 8
match type:
case 'int':
return value.to_bytes(bitsize // 8, endianness, signed=value < 0)
case 'float':
match endianness:
case 'big':
order = '>'
case 'little':
order = '<'
case 'native':
onder = '='
match bitsize:
case 32:
fmt = 'f'
case 64:
fmt = 'd'
case _:
raise Exception('bitstring floats must be 32 or 64 bits')
return struct.pack(f'{order}{fmt}', value)
raise Exception('Unexpected bitstring encountered')
"

pub const prelude = "from gleam_builtins import *\n\n"
Loading

0 comments on commit e1d7971

Please sign in to comment.