moonbit-barcoder

GS1-128 and Code 128 data parser toolkit for MoonBit logistics applications.

barcode
gs1
gs1-128
code128
logistics
parser
moon add Qlcdsba/moonbit-barcoder@0.2.0
Download zip
Author
Version
0.2.0
License
Apache-2.0
Last updated
7 hours ago
Downloads
2
README

#moonbit-barcoder

A MoonBit toolkit for turning GS1-128 scanner payloads into validated, typed logistics data and Code 128 symbols.

#Project positioning

Scanner integrations commonly receive text payloads rather than camera frames or bar/space measurements. This library handles the data contract at that boundary:

  • parse GS1 human-readable (01)...(10)... payloads;
  • parse raw scanner data with the ASCII GS/FNC1 separator;
  • validate identifiers, dates, quantities, measurements and profile requirements;
  • encode GS1-128/Code 128 symbol values and render deterministic ASCII previews;
  • process newline-delimited scanner batches without losing per-record diagnostics.

The root package has no runtime dependencies outside the MoonBit core libraries used by the optional executable.

#Core capabilities

#GS1 parsing

parse_gs1 auto-detects HRI and raw scanner payloads. parse_gs1_with_options provides explicit HRI/raw mode, maximum payload length, group-separator policy and empty-field policy.

The catalog includes common trade-item and logistic-unit identifiers, commercial amount/price fields, healthcare and product-traceability fields, production data, asset references, and the 310–369 implied-decimal measurement families.

#Validation and projections

Validation returns a stable, non-throwing ValidationReport with severity, field path and message. The library includes GTIN, SSCC, GLN, GRAI and date checks, duplicate-field reporting, profile validation, normalized date/measurement views, and a ShipmentProjection for common warehouse workflows.

#Code 128 and GS1-128

The encoder plans numeric pairs in Set C, switches to Set B for mixed data, handles FNC1, calculates the weighted checksum, exposes the complete standard symbol pattern table, and renders quiet-zone-aware module previews.

#Quick start

///|
test "README quick start" {
let message = parse_gs1("(01)09501101530003(10)LOT42(17)260930")
assert_eq(message.first_value("01"), Some("09501101530003"))
assert_eq(message.first_value("10"), Some("LOT42"))
assert_true(message.validate().ok)
assert_eq(message.profile().has_expiration, true)
}

#CLI

The runnable example supports a sample payload by default. A payload can be followed by any combination of:

moon run cmd/main -- --validate --encode --benchmark moon run cmd/main -- "(01)09501101530003(10)LOT42" --validate --encode moon run cmd/main -- "(01)09501101530003(10)LOT42" --ascii

The executable prints the normalized HRI payload, validation status, encoded symbol/module counts, an ASCII module preview, or a deterministic operation-count benchmark.

#Library example

///|
test "README Code 128 example" {
let encoded = encode_code128("123456").unwrap()
assert_eq(encoded.symbols, [105, 12, 34, 56, 44, 106])
assert_eq(encoded.checksum, 44)
assert_true(verify_code128_symbols(encoded.symbols))
}

#Architecture

. |-- types.mbt public GS1 types and base catalog |-- catalog.mbt commercial, logistics and internal AI metadata |-- measurement.mbt 310–369 measurement families and conversion helpers |-- parser.mbt HRI/raw state-machine parser |-- parser_options.mbt strict/auto/raw parser policies |-- scanner.mbt line and scanner-stream normalization |-- batch.mbt per-record batch results |-- validation.mbt report construction and date rules |-- check_digits.mbt GS1 identifier algorithms |-- validation_profiles.mbt traceability/shipment profiles |-- normalized.mbt dates, implied decimals and safe views |-- pipeline.mbt warehouse projection helpers |-- code128.mbt encoder, checksum and GS1-128 bridge |-- code128_tables.mbt complete Code 128 module patterns |-- benchmark.mbt reproducible operation-count reports |-- cmd/main runnable CLI example `-- .github/workflows check/test and manual publish workflows

Public concrete types live in the root package. The implementation is split into small files so catalog data, parser state, validation rules and encoding logic can evolve independently.

#Benchmark

The benchmark command uses a fixed payload and reports successes, parsed fields and operation counts. It is intentionally deterministic across machines; wall-clock timing should be measured by the invoking environment.

moon run cmd/main -- --benchmark

The repository records the exact command and observed output in docs/benchmarks.md. The checked library surface is also available directly:

///|
test "README benchmark example" {
let report = benchmark_parse(
"(01)09501101530003(10)BATCH-7(17)260930",
iterations=10,
)
assert_eq(report.successes, 10)
assert_eq(report.fields, 30)
assert_true(report.operations > 0)
}

#Testing

Run the complete local gate:

moon fmt --check moon check --target all --deny-warn moon test --target all --deny-warn moon coverage report -f summary moon coverage analyze

The test suite covers HRI/raw parsing, separator and length boundaries, malformed fields, check digits, dates, duplicate AIs, measurement conversions, profile requirements, batch retention, Code 128 set planning, FNC1, checksums, symbol patterns, rendering and benchmark invariants.

#CI

GitHub Actions runs the MoonBit community-style matrix on Ubuntu, macOS and Windows. Each job installs the latest stable toolchain from the official installer, runs all compiler targets, runs tests, checks formatting, regenerates public interfaces and verifies a clean generated diff. Native coverage is reported as an additional quality signal.

The publish workflow is manual and uses the repository secret MOONCAKES_MOONBIT_COMMUNITY_TOKEN; credentials are never committed.

#Release and compatibility

The module metadata is in moon.mod, the generated public interface is tracked as pkg.generated.mbti, and release notes are maintained in CHANGELOG.md. Public APIs use explicit result/error types where malformed scanner input is expected.

#License

Licensed under the Apache License, Version 2.0. See LICENSE.

#
ParseError

pub suberror ParseError {
UnknownAI(String)
Truncated(String, Int, Int)
InvalidSyntax(String)
} derive(
Debug
)

#
ApplicationIdentifier

pub struct ApplicationIdentifier {
code : String
title : String
fixed_length : Int?
max_length : Int
kind : ValueKind
} derive(Eq,
Debug
)

#
ApplicationRule

pub struct ApplicationRule {
code : String
class : RuleClass
min_length : Int
max_length : Int
fixed : Bool
check_digit : Bool
allow_separator : Bool
} derive(Eq,
Debug
)

#
BatchRecord

pub struct BatchRecord {
index : Int
message : GS1Message?
error : String?
} derive(Eq,
Debug
)

#
BatchReport

pub struct BatchReport {
total : Int
succeeded : Int
failed : Int
records : Array[BatchRecord]
} derive(Eq,
Debug
)

#
BatchReport::errors

fn BatchReport::errors(self : BatchReport) -> Array[String]

#
BatchReport::successes

fn BatchReport::successes(self : BatchReport) -> Array[GS1Message]

#
BenchmarkReport

pub struct BenchmarkReport {
input_bytes : Int
iterations : Int
successes : Int
fields : Int
operations : Int
elapsed_micros : Int
} derive(Eq,
Debug
)

#
BenchmarkReport::success_rate

fn BenchmarkReport::success_rate(self : BenchmarkReport) -> Int

#
BenchmarkReport::summary

fn BenchmarkReport::summary(self : BenchmarkReport) -> String

#
Code128Encoded

pub struct Code128Encoded {
data : String
symbols : Array[Int]
checksum : Int
symbol_count : Int
module_count : Int
set_switches : Int
} derive(Eq,
Debug
)

#
Code128Error

pub enum Code128Error {
UnsupportedCharacter(Int)
InvalidFNC1Placement
EmptyPayload
} derive(Eq,
Debug
)

#
GS1Element

pub struct GS1Element {
ai : ApplicationIdentifier
value : String
} derive(Eq,
Debug
)

#
GS1Element::code

fn GS1Element::code(self : GS1Element) -> String

#
GS1Element::is_valid

fn GS1Element::is_valid(self : GS1Element) -> Bool

#
GS1Element::title

fn GS1Element::title(self : GS1Element) -> String

#
GS1Element::to_hri

fn GS1Element::to_hri(self : GS1Element) -> String

#
GS1Message

pub struct GS1Message {
elements : Array[GS1Element]
} derive(Eq,
Debug
)

#
GS1Message::first_value

fn GS1Message::first_value(self : GS1Message, code : String) -> String?

#
GS1Message::normalized_element

fn GS1Message::normalized_element(self : GS1Message, code : String) -> NormalizedElement?

#
GS1Message::profile

fn GS1Message::profile(self : GS1Message) -> LogisticsProfile

#
GS1Message::summary

fn GS1Message::summary(self : GS1Message) -> String

#
GS1Message::to_hri

fn GS1Message::to_hri(self : GS1Message) -> String

#
GS1Message::validate

fn GS1Message::validate(self : GS1Message) -> ValidationReport

#
GS1Message::values

fn GS1Message::values(self : GS1Message, code : String) -> Array[String]

#
LogisticsProfile

pub struct LogisticsProfile {
has_sscc : Bool
has_gtin : Bool
has_lot : Bool
has_serial : Bool
has_expiration : Bool
} derive(Eq,
Debug
)

#
MeasurementFamily

pub struct MeasurementFamily {
prefix : String
title : String
unit : String
decimal_digits : Bool
} derive(Eq,
Debug
)

#
NormalizedElement

pub struct NormalizedElement {
code : String
raw_value : String
numeric_value : Int?
decimal_places : Int?
date_value : String?
} derive(Eq,
Debug
)

#
NormalizedElement::code

fn NormalizedElement::code(self : NormalizedElement) -> String

#
NormalizedElement::raw

fn NormalizedElement::raw(self : NormalizedElement) -> String

#
ParseMode

pub(all) enum ParseMode {
Auto
HRI
Raw
} derive(Eq,
Debug
)

#
ParseOptions

pub struct ParseOptions {
mode : ParseMode
max_length : Int
allow_group_separator : Bool
allow_empty_variable : Bool
} derive(Eq,
Debug
)

#
ParseOptions::auto

fn ParseOptions::auto(max_length? : Int, allow_group_separator? : Bool) -> ParseOptions

#
ParseOptions::group_separator_allowed

fn ParseOptions::group_separator_allowed(self : ParseOptions) -> Bool

#
ParseOptions::max_length

fn ParseOptions::max_length(self : ParseOptions) -> Int

#
ParseOptions::mode

fn ParseOptions::mode(self : ParseOptions) -> ParseMode

#
ParseOptions::raw

fn ParseOptions::raw(max_length? : Int, allow_group_separator? : Bool) -> ParseOptions

#
ParseOptions::strict

fn ParseOptions::strict(max_length? : Int, allow_group_separator? : Bool) -> ParseOptions

#
ParseOptions::with_empty_variable

fn ParseOptions::with_empty_variable(self : ParseOptions) -> ParseOptions

#
ParseOptions::with_max_length

fn ParseOptions::with_max_length(self : ParseOptions, max_length : Int) -> ParseOptions

#
Profile

pub(all) enum Profile {
Traceability
Shipment
TradeItem
LogisticUnit
} derive(Eq,
Debug
)

#
ProfileReport

pub struct ProfileReport {
ok : Bool
missing : Array[String]
issues : Array[String]
} derive(Eq,
Debug
)

#
RuleClass

pub(all) enum RuleClass {
Numeric
Alphanumeric
Date
Decimal
Reference
} derive(Eq,
Debug
)

#
Severity

pub enum Severity {
Error
Warning
} derive(Eq,
Debug
)

#
ShipmentProjection

pub struct ShipmentProjection {
gtin : String?
lot : String?
serial : String?
expiration : String?
sscc : String?
quantity : String?
} derive(Eq,
Debug
)

#
ShipmentProjection::identity

fn ShipmentProjection::identity(self : ShipmentProjection) -> String

#
ShipmentProjection::is_traceable

fn ShipmentProjection::is_traceable(self : ShipmentProjection) -> Bool

#
ValidationIssue

pub struct ValidationIssue {
severity : Severity
path : String
message : String
} derive(Eq,
Debug
)

#
ValidationReport

pub struct ValidationReport {
ok : Bool
issues : Array[ValidationIssue]
} derive(Eq,
Debug
)

#
ValidationReport::summary

fn ValidationReport::summary(self : ValidationReport) -> String

#
ValueKind

pub enum ValueKind {
Fixed
Variable
} derive(Eq,
Debug
)

#
ai_codes_with_prefix

fn ai_codes_with_prefix(prefix : String) -> Array[String]

#
application_identifier_count

fn application_identifier_count() -> Int

#
application_identifiers

fn application_identifiers() -> Array[ApplicationIdentifier]

#
application_rule

fn application_rule(code : String) -> ApplicationRule?

#
application_rule_allows_separator

fn application_rule_allows_separator(code : String) -> Bool?

#
application_rule_class

fn application_rule_class(code : String) -> String?

#
application_rule_codes

fn application_rule_codes() -> Array[String]

#
application_rule_count

fn application_rule_count() -> Int

#
application_rule_is_date

fn application_rule_is_date(code : String) -> Bool

#
application_rule_is_fixed

fn application_rule_is_fixed(code : String) -> Bool?

#
application_rule_is_numeric

fn application_rule_is_numeric(code : String) -> Bool

#
application_rule_is_variable

fn application_rule_is_variable(code : String) -> Bool?

#
application_rule_max_length

fn application_rule_max_length(code : String) -> Int?

#
application_rule_min_length

fn application_rule_min_length(code : String) -> Int?

#
application_rule_needs_check_digit

fn application_rule_needs_check_digit(code : String) -> Bool?

#
application_rule_validate_length

fn application_rule_validate_length(code : String, value : String) -> Bool

#
available_profiles

fn available_profiles() -> Array[Profile]

#
benchmark_parse

fn benchmark_parse(input : String, iterations~ : Int) -> BenchmarkReport

#
catalog_codes

fn catalog_codes() -> Array[String]

#
catalog_has

fn catalog_has(code : String) -> Bool

#
catalog_is_fixed

fn catalog_is_fixed(code : String) -> Bool?

#
catalog_max_length

fn catalog_max_length(code : String) -> Int?

#
catalog_required_length

fn catalog_required_length(code : String) -> Int?

#
catalog_title

fn catalog_title(code : String) -> String?

#
catalog_validate_length

fn catalog_validate_length(code : String, value : String) -> Bool

#
catalog_variable_codes

fn catalog_variable_codes() -> Array[String]

#
check_digit

fn check_digit(value : String) -> Int?

#
code128_checksum

fn code128_checksum(symbols : ArrayView[Int]) -> Int

#
code128_is_bar

fn code128_is_bar(symbol : Int, unit : Int) -> Bool

#
code128_module_count

fn code128_module_count(symbols : ArrayView[Int]) -> Int

#
code128_pattern

fn code128_pattern(symbol : Int) -> String

#
code128_symbol_patterns

fn code128_symbol_patterns() -> Array[String]

#
code128_width

fn code128_width(symbol : Int) -> Int

#
encode_code128

fn encode_code128(data : String) -> Result[Code128Encoded, Code128Error]

#
encode_gs1_128

fn encode_gs1_128(message : GS1Message) -> Result[Code128Encoded, Code128Error]

#
is_gs1_128_symbols

fn is_gs1_128_symbols(symbols : ArrayView[Int]) -> Bool

#
is_valid_code128_symbol

fn is_valid_code128_symbol(symbol : Int) -> Bool

#
lookup_ai

fn lookup_ai(code : String) -> ApplicationIdentifier?

#
measurement_code_prefix

fn measurement_code_prefix(code : String) -> String?

#
measurement_codes_for_prefix

fn measurement_codes_for_prefix(prefix : String) -> Array[String]

#
measurement_compare

fn measurement_compare(code : String, left : String, right : String) -> Int

#
measurement_decimal_places

fn measurement_decimal_places(code : String) -> Int?

#
measurement_description

fn measurement_description(code : String) -> String?

#
measurement_family_codes

fn measurement_family_codes(prefix : String) -> Array[String]

#
measurement_family_count

fn measurement_family_count() -> Int

#
measurement_family_exists

fn measurement_family_exists(prefix : String) -> Bool

#
measurement_family_has_decimal

fn measurement_family_has_decimal(prefix : String) -> Bool

#
measurement_family_title

fn measurement_family_title(prefix : String) -> String?

#
measurement_family_unit

fn measurement_family_unit(prefix : String) -> String?

#
measurement_fixed_width

fn measurement_fixed_width(code : String) -> Bool

#
measurement_from_scaled_integer

fn measurement_from_scaled_integer(code : String, value : Int) -> String?

#
measurement_is_imperial

fn measurement_is_imperial(code : String) -> Bool

#
measurement_is_maximum

fn measurement_is_maximum(code : String, encoded : String) -> Bool

#
measurement_is_metric

fn measurement_is_metric(code : String) -> Bool

#
measurement_is_supported

fn measurement_is_supported(code : String) -> Bool

#
measurement_is_zero

fn measurement_is_zero(code : String, encoded : String) -> Bool

#
measurement_normalize_encoded

fn measurement_normalize_encoded(code : String, encoded : String) -> String?

#
measurement_percent

fn measurement_percent(code : String, encoded : String) -> Int?

#
measurement_range_valid

fn measurement_range_valid(code : String, encoded : String) -> Bool

#
measurement_report

fn measurement_report(code : String, encoded : String) -> String

#
measurement_scale_factor

fn measurement_scale_factor(code : String) -> Int

#
measurement_title

fn measurement_title(code : String) -> String?

#
measurement_to_scaled_integer

fn measurement_to_scaled_integer(code : String, encoded : String) -> Int?

#
measurement_unit

fn measurement_unit(code : String) -> String?

#
measurement_unit_symbol

fn measurement_unit_symbol(code : String) -> String?

#
measurement_uses_implied_decimal

fn measurement_uses_implied_decimal(code : String) -> Bool

#
measurement_value

fn measurement_value(code : String, encoded : String) -> Int?

#
measurement_value_as_decimal

fn measurement_value_as_decimal(code : String, encoded : String) -> String?

#
normalize_scanner_input

fn normalize_scanner_input(input : String) -> String

#
parse_batch

fn parse_batch(input : String) -> BatchReport

#
parse_gs1

fn parse_gs1(input : String) -> GS1Message raise ParseError

#
parse_gs1_with_options

fn parse_gs1_with_options(input : String, options : ParseOptions) -> GS1Message raise ParseError

#
project_shipment

fn project_shipment(message : GS1Message) -> ShipmentProjection

#
render_code128_ascii

fn render_code128_ascii(encoded : Code128Encoded, quiet_zone? : Int, bar? : String, space? : String) -> String

#
scanner_lines

fn scanner_lines(input : String) -> Array[String]

#
scanner_payloads

fn scanner_payloads(input : String) -> Array[String]

#
validate_application_rule

fn validate_application_rule(code : String, value : String) -> Bool

#
validate_giai

fn validate_giai(value : String) -> Bool

#
validate_gln

fn validate_gln(value : String) -> Bool

#
validate_grai

fn validate_grai(value : String) -> Bool

#
validate_gtin

fn validate_gtin(value : String) -> Bool

#
validate_profile

fn validate_profile(message : GS1Message, profile : Profile) -> ProfileReport

#
validate_sscc

fn validate_sscc(value : String) -> Bool

#
verify_code128_symbols

fn verify_code128_symbols(symbols_with_check : ArrayView[Int]) -> Bool