bsm 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7267b067dccaa3052d254696bbd5071b6a65a6bbf3bcab98bdecba5bc717dbdd
4
- data.tar.gz: 7b0f6874144a06c0a8a8240a6b854edb046f68cf7dff8086460c7124ba6b98af
3
+ metadata.gz: 0f86f5bdcd9b98750e22cff1c56e95f64f45e4bd89daaedc11fccfffb5657bf4
4
+ data.tar.gz: 966a4663e01a1d395110583be23eec8a89936e9b6bbc13114cf516b98372728c
5
5
  SHA512:
6
- metadata.gz: 4d89405622e5bd6829c86e1e021b3191d974f09c7f4d3c35e34d42479b5b6094178f1de67da63c2dea6cf0e847988f27e1bd896f97bf106a20843d64558b7a08
7
- data.tar.gz: 83d68e3f8b5312cb0bcdc4b3d95f916de59c54d9f17bf478aa9cf6fbd364b1b9780d612c1db67f8e267edc4478cc292275a80a71a535efd241405cc98d577b54
6
+ metadata.gz: e0012606b0e0ec82f03fbe23cda9b73d856746def0863290b72e62bdb493414b3ceb3d652ca8c9979dcc3cf9aa080d60b02568039fdc40a0af04f72b684156d1
7
+ data.tar.gz: 4921666d0a125c739bcd5f13bc85190200f41feaa7de7712626312ce545465f0874d0703c94e24fff634ee498e3336b8a7f0e0b3499c2b2ddaaf402752ae185a
data/Gemfile CHANGED
@@ -1,6 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in bsm.gemspec
4
6
  gemspec
5
7
 
6
- gem 'markspec', path: '/Users/davidsiaw/work/markspec'
8
+ # Specify development dependencies here and not in the gemspec
9
+ gem 'markspec'
10
+ gem 'rake'
11
+ gem 'rake-compiler'
12
+ gem 'rspec'
13
+ gem 'rubocop'
14
+ gem 'rubocop-rake'
15
+ gem 'rubocop-rspec'
16
+ gem 'rubocop-yard'
17
+ gem 'yard'
18
+ gem 'yard-rspec'
data/bsm.gemspec CHANGED
@@ -1,37 +1,38 @@
1
- lib = File.expand_path('lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'bsm/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/bsm/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'bsm'
7
7
  spec.version = Bsm::VERSION
8
8
  spec.authors = ['David Siaw']
9
- spec.email = ['dsiaw@degica.com']
9
+ spec.email = ['874280+davidsiaw@users.noreply.github.com']
10
10
 
11
- spec.summary = 'a short summary'
12
- spec.description = 'a longer description'
13
- spec.homepage = 'https://github.org/davidsiaw/bsm'
11
+ spec.summary = 'Write binary files in a human-readable way'
12
+ spec.description = 'Bsm converts literate text (semicolon-prefixed lines of hex, ' \
13
+ 'strings, bitfields) into binary. Backed by the native bsm2 library.'
14
+ spec.homepage = 'https://github.com/davidsiaw/bsm'
14
15
  spec.license = 'MIT'
16
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.0')
15
17
 
16
- if spec.respond_to?(:metadata)
17
- spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
19
 
19
- spec.metadata['homepage_uri'] = spec.homepage
20
- spec.metadata['source_code_uri'] = 'https://github.org/davidsiaw/bsm'
21
- spec.metadata['changelog_uri'] = 'https://github.org/davidsiaw/bsm'
22
- else
23
- raise 'RubyGems 2.0 or newer is required to protect against ' \
24
- 'public gem pushes.'
25
- end
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = 'https://github.com/davidsiaw/bsm'
22
+ spec.metadata['changelog_uri'] = 'https://github.com/davidsiaw/bsm'
23
+ spec.metadata['rubygems_mfa_required'] = 'true'
26
24
 
27
- spec.files = Dir['{exe,lib}/**/*'] + %w[Gemfile bsm.gemspec]
25
+ # bsm2 is vendored as a git submodule and built from source at install time
26
+ # by ext/bsm/internal/extconf.rb. Ship the source + build files so the gem
27
+ # is self-contained. Exclude ext build artifacts (mkmf output) from the gem.
28
+ spec.files = (Dir['{exe,lib,ext}/**/*'] +
29
+ Dir['bsm2/src/**/*'] +
30
+ %w[Gemfile bsm.gemspec bsm2/Makefile]).reject do |f|
31
+ f =~ %r{^ext/.*\.(o|so|bundle)$} ||
32
+ f =~ %r{^ext/.*/(Makefile|mkmf\.log)$}
33
+ end
28
34
  spec.bindir = 'exe'
29
35
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
36
  spec.require_paths = ['lib']
31
37
  spec.extensions = %w[ext/bsm/internal/extconf.rb]
32
-
33
- spec.add_development_dependency 'bundler', '~> 2.0'
34
- spec.add_development_dependency 'rake', '~> 10.0'
35
- spec.add_development_dependency 'rake-compiler'
36
- spec.add_development_dependency 'rspec', '~> 3.0'
37
38
  end
data/bsm2/Makefile ADDED
@@ -0,0 +1,104 @@
1
+ SRC_DIR := src
2
+ BSMSRC_DIR := src/bsm2
3
+ MSBSRC_DIR := src/msb2
4
+ OBJ_DIR := obj
5
+ BSMOBJ_DIR := obj/bsm2
6
+ MSBOBJ_DIR := obj/msb2
7
+ LIB_DIR := lib
8
+ BIN_DIR := bin
9
+
10
+ EXE := $(BIN_DIR)/bsm2
11
+ MSBEXE := $(BIN_DIR)/msb2
12
+ LIB := $(LIB_DIR)/libbsm2.a
13
+ CAPITEST := $(BIN_DIR)/c_api_test
14
+ EMPTYTEST := $(BIN_DIR)/empty_out_test
15
+
16
+ # Library sources (top-level src/*.cpp) — no main, no I/O
17
+ LIB_SRC := $(wildcard $(SRC_DIR)/*.cpp)
18
+ LIB_OBJ := $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(LIB_SRC))
19
+
20
+ # bsm2 executable main
21
+ BSMSRC := $(wildcard $(BSMSRC_DIR)/*.cpp)
22
+ BSMOBJ := $(patsubst $(BSMSRC_DIR)/%.cpp, $(BSMOBJ_DIR)/%.o, $(BSMSRC))
23
+
24
+ # msb2 executable (standalone, no lib dependency)
25
+ MSBSRC := $(wildcard $(MSBSRC_DIR)/*.cpp)
26
+ MSBOBJ := $(patsubst $(MSBSRC_DIR)/%.cpp, $(MSBOBJ_DIR)/%.o, $(MSBSRC))
27
+
28
+ CPPFLAGS := -Isrc -MMD -MP -std=c++17
29
+ CFLAGS := -Wall -Wpedantic -fPIC
30
+ LDFLAGS :=
31
+ LDLIBS :=
32
+
33
+ PREFIX ?= /usr/local
34
+ BINDIR ?= $(PREFIX)/bin
35
+ INSTALL ?= install
36
+
37
+ all: $(EXE) $(MSBEXE)
38
+
39
+ libbsm2: $(LIB)
40
+
41
+ $(LIB): $(LIB_OBJ) | $(LIB_DIR)
42
+ $(AR) rcs $@ $^
43
+
44
+ # bsm2: main.o linked against libbsm2
45
+ $(EXE): $(BSMOBJ) $(LIB) | $(BIN_DIR)
46
+ $(CXX) $(LDFLAGS) $(BSMOBJ) -L$(LIB_DIR) -lbsm2 $(LDLIBS) -o $@
47
+
48
+ # msb2: standalone, does not use libbsm2
49
+ $(MSBEXE): $(MSBOBJ) | $(BIN_DIR)
50
+ $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
51
+
52
+ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR)
53
+ $(CXX) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
54
+
55
+ $(BSMOBJ_DIR)/%.o: $(BSMSRC_DIR)/%.cpp | $(BSMOBJ_DIR)
56
+ $(CXX) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
57
+
58
+ $(MSBOBJ_DIR)/%.o: $(MSBSRC_DIR)/%.cpp | $(MSBOBJ_DIR)
59
+ $(CXX) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
60
+
61
+ $(BIN_DIR) $(OBJ_DIR) $(BSMOBJ_DIR) $(MSBOBJ_DIR) $(LIB_DIR):
62
+ mkdir -p $@
63
+
64
+ .PHONY: clean format test test-c-api memtest install uninstall libbsm2
65
+
66
+ install: all
67
+ $(INSTALL) -d $(DESTDIR)$(BINDIR)
68
+ $(INSTALL) -m 755 $(EXE) $(DESTDIR)$(BINDIR)/bsm2
69
+ $(INSTALL) -m 755 $(MSBEXE) $(DESTDIR)$(BINDIR)/msb2
70
+
71
+ uninstall:
72
+ $(RM) $(DESTDIR)$(BINDIR)/bsm2 $(DESTDIR)$(BINDIR)/msb2
73
+
74
+ # C tests: C sources linked against libbsm2 (g++ driver for libstdc++)
75
+ # Built with -g -O0 so valgrind reports are meaningful.
76
+ $(CAPITEST): test/c_api_test.c $(LIB) | $(BIN_DIR)
77
+ $(CC) -Isrc -g -O0 -MMD -MP -c $< -o $(OBJ_DIR)/c_api_test.o
78
+ $(CXX) $(LDFLAGS) $(OBJ_DIR)/c_api_test.o -L$(LIB_DIR) -lbsm2 $(LDLIBS) -o $@
79
+
80
+ $(EMPTYTEST): test/empty_out_test.c $(LIB) | $(BIN_DIR)
81
+ $(CC) -Isrc -g -O0 -MMD -MP -c $< -o $(OBJ_DIR)/empty_out_test.o
82
+ $(CXX) $(LDFLAGS) $(OBJ_DIR)/empty_out_test.o -L$(LIB_DIR) -lbsm2 $(LDLIBS) -o $@
83
+
84
+ test-c-api: $(CAPITEST) $(EMPTYTEST)
85
+ ./$(CAPITEST)
86
+ ./$(EMPTYTEST)
87
+
88
+ # Valgrind memory check of the C API tests. Requires valgrind on PATH.
89
+ VALGRIND := valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=99
90
+
91
+ memtest: $(CAPITEST) $(EMPTYTEST)
92
+ $(VALGRIND) ./$(CAPITEST)
93
+ $(VALGRIND) ./$(EMPTYTEST)
94
+
95
+ test: $(EXE) test-c-api
96
+ PATH="$(HOME)/.local/share/mise/installs/ruby/3.3.5/bin:$$PATH" markspec
97
+
98
+ format:
99
+ uncrustify -c uncrustify.cfg src/*.cpp src/*.h src/bsm2/*.cpp src/msb2/*.cpp --replace --no-backup
100
+
101
+ clean:
102
+ @$(RM) -rv $(BIN_DIR) $(OBJ_DIR) $(LIB_DIR)
103
+
104
+ -include $(LIB_OBJ:.o=.d) $(BSMOBJ:.o=.d) $(MSBOBJ:.o=.d)
@@ -0,0 +1,50 @@
1
+ #include "error_info.h"
2
+ #include "processor.h"
3
+ #include <iostream>
4
+ #include <memory>
5
+ #include <string>
6
+
7
+ static void report_error(ErrorInfo const &e)
8
+ {
9
+ std::cerr << "BSM: Error at line " << e.line_number << " and column "
10
+ << e.column << std::endl
11
+ << "---" << std::endl;
12
+ std::cerr << e.line_text << std::endl;
13
+
14
+ for (unsigned i = 0; i < e.column; i++)
15
+ {
16
+ std::cerr << " ";
17
+ }
18
+
19
+ std::cerr << "^";
20
+
21
+ for (unsigned i = 0; i < e.char_length; i++)
22
+ {
23
+ std::cerr << "~";
24
+ }
25
+
26
+ std::cerr << " " << e.message << std::endl;
27
+ }
28
+
29
+ int main()
30
+ {
31
+ std::unique_ptr<Processor> p = std::make_unique<Processor>();
32
+
33
+ while (std::cin)
34
+ {
35
+ std::string input_line;
36
+ std::getline(std::cin, input_line);
37
+
38
+ ErrorInfo error = {};
39
+ std::string output = p->lex_line(input_line, &error);
40
+ std::cout << output;
41
+
42
+ if (error.line_number > 0)
43
+ {
44
+ report_error(error);
45
+ return EXIT_FAILURE;
46
+ }
47
+ }
48
+
49
+ return EXIT_SUCCESS;
50
+ }
@@ -0,0 +1,127 @@
1
+ #include "c_api.h"
2
+ #include "error_info.h"
3
+ #include "processor.h"
4
+
5
+ #include <cstdlib>
6
+ #include <cstring>
7
+ #include <new>
8
+ #include <string>
9
+
10
+ struct bsm2_processor
11
+ {
12
+ Processor impl;
13
+ };
14
+
15
+ struct bsm2_error
16
+ {
17
+ std::string message;
18
+ std::string line_text;
19
+ unsigned line_number;
20
+ unsigned column;
21
+ unsigned char_length;
22
+ };
23
+
24
+ bsm2_processor* bsm2_new(void)
25
+ {
26
+ bsm2_processor* p =
27
+ static_cast<bsm2_processor*>(std::malloc(sizeof(bsm2_processor)));
28
+ if (!p)
29
+ {
30
+ return nullptr;
31
+ }
32
+ new (&p->impl) Processor();
33
+ return p;
34
+ }
35
+
36
+ void bsm2_free(bsm2_processor* p)
37
+ {
38
+ if (!p)
39
+ {
40
+ return;
41
+ }
42
+ p->impl.~Processor();
43
+ std::free(p);
44
+ }
45
+
46
+ char* bsm2_convert_line(bsm2_processor* p, const char* line, size_t len,
47
+ size_t* out_len, bsm2_error** err)
48
+ {
49
+ if (err)
50
+ {
51
+ *err = nullptr;
52
+ }
53
+ if (!p || !line)
54
+ {
55
+ return nullptr;
56
+ }
57
+
58
+ ErrorInfo info = {};
59
+ std::string input(line, len);
60
+ std::string out = p->impl.lex_line(input, &info);
61
+
62
+ if (info.line_number > 0)
63
+ {
64
+ if (err)
65
+ {
66
+ bsm2_error* e = new (std::nothrow) bsm2_error();
67
+ if (e)
68
+ {
69
+ e->message = info.message;
70
+ e->line_text = info.line_text;
71
+ e->line_number = info.line_number;
72
+ e->column = info.column;
73
+ e->char_length = info.char_length;
74
+ *err = e;
75
+ }
76
+ }
77
+ return nullptr;
78
+ }
79
+
80
+ size_t n = out.size();
81
+ if (out_len)
82
+ {
83
+ *out_len = n;
84
+ }
85
+ // Always hand back a non-NULL buffer on success so callers can distinguish
86
+ // success-with-empty-output from error (NULL + *err set).
87
+ char* buf = static_cast<char*>(std::malloc(n ? n : 1));
88
+ if (!buf)
89
+ {
90
+ return nullptr;
91
+ }
92
+ if (n)
93
+ {
94
+ std::memcpy(buf, out.data(), n);
95
+ }
96
+ return buf;
97
+ }
98
+
99
+ const char* bsm2_error_message(bsm2_error* e)
100
+ {
101
+ return e ? e->message.c_str() : "";
102
+ }
103
+
104
+ unsigned bsm2_error_line(bsm2_error* e)
105
+ {
106
+ return e ? e->line_number : 0;
107
+ }
108
+
109
+ unsigned bsm2_error_column(bsm2_error* e)
110
+ {
111
+ return e ? e->column : 0;
112
+ }
113
+
114
+ unsigned bsm2_error_length(bsm2_error* e)
115
+ {
116
+ return e ? e->char_length : 0;
117
+ }
118
+
119
+ const char* bsm2_error_line_text(bsm2_error* e)
120
+ {
121
+ return e ? e->line_text.c_str() : "";
122
+ }
123
+
124
+ void bsm2_error_free(bsm2_error* e)
125
+ {
126
+ delete e;
127
+ }
data/bsm2/src/c_api.h ADDED
@@ -0,0 +1,41 @@
1
+ #ifndef BSM2_C_API_H
2
+ #define BSM2_C_API_H
3
+
4
+ #include <stddef.h>
5
+
6
+ #ifdef __cplusplus
7
+ extern "C" {
8
+ #endif
9
+
10
+ /* Opaque handle: one conversion session. The internal line counter
11
+ * accumulates across calls, so feed lines in order. */
12
+ typedef struct bsm2_processor bsm2_processor;
13
+
14
+ /* Opaque error. Heap-allocated by bsm2_convert_line on failure; caller frees
15
+ * with bsm2_error_free(). Pure data — the caller owns presentation. */
16
+ typedef struct bsm2_error bsm2_error;
17
+
18
+ bsm2_processor* bsm2_new(void);
19
+ void bsm2_free(bsm2_processor* p);
20
+
21
+ /* Feed one raw line, including its leading ';' (the ';' convention lives in
22
+ * the library; non-';' lines are ignored and yield empty output).
23
+ *
24
+ * On success: returns a malloc'd buffer of *out_len bytes (caller frees with
25
+ * free(); always non-NULL even when *out_len == 0). *err is set to NULL.
26
+ * On error: returns NULL and sets *err (caller frees with bsm2_error_free()).*/
27
+ char* bsm2_convert_line(bsm2_processor* p, const char* line, size_t len,
28
+ size_t* out_len, bsm2_error** err);
29
+
30
+ const char* bsm2_error_message(bsm2_error* e);
31
+ unsigned bsm2_error_line(bsm2_error* e);
32
+ unsigned bsm2_error_column(bsm2_error* e);
33
+ unsigned bsm2_error_length(bsm2_error* e);
34
+ const char* bsm2_error_line_text(bsm2_error* e);
35
+ void bsm2_error_free(bsm2_error* e);
36
+
37
+ #ifdef __cplusplus
38
+ }
39
+ #endif
40
+
41
+ #endif /* BSM2_C_API_H */
@@ -0,0 +1,58 @@
1
+ #ifndef CONVERTER_H
2
+ #define CONVERTER_H
3
+
4
+ #include <string>
5
+ #include <sstream>
6
+
7
+ class Converter
8
+ {
9
+ public:
10
+ Converter()
11
+ {
12
+ }
13
+
14
+ ~Converter()
15
+ {
16
+ }
17
+
18
+ char convert_hexpair(const std::string& yytext)
19
+ {
20
+ int chr = stoi(std::string(yytext), nullptr, 16);
21
+ return static_cast<char>(chr);
22
+ }
23
+
24
+ char convert_quotechar(const std::string& yytext)
25
+ {
26
+ return yytext[1];
27
+ }
28
+
29
+ std::string convert_doublequote(const std::string& yytext)
30
+ {
31
+ std::stringstream result;
32
+
33
+ for (uint i = 1; i < yytext.length() - 1; i++)
34
+ {
35
+ result << yytext[i];
36
+ }
37
+
38
+ return result.str();
39
+ }
40
+
41
+ unsigned char convert_bindigits(const std::string& yytext)
42
+ {
43
+ unsigned char c = 0;
44
+
45
+ for (uint i = 1; i < yytext.length() - 1; i++)
46
+ {
47
+ if (yytext[i] == '1')
48
+ {
49
+ c += 1 << (8-i);
50
+ }
51
+ }
52
+
53
+ return c;
54
+ }
55
+
56
+ };
57
+
58
+ #endif // CONVERTER_H
@@ -0,0 +1,15 @@
1
+ #ifndef ERROR_H
2
+ #define ERROR_H
3
+
4
+ #include <string>
5
+
6
+ struct ErrorInfo
7
+ {
8
+ unsigned line_number;
9
+ unsigned column;
10
+ unsigned char_length;
11
+ std::string line_text;
12
+ std::string message;
13
+ };
14
+
15
+ #endif // ERROR_H
@@ -0,0 +1,276 @@
1
+ #include "lexer.h"
2
+ #include <cctype>
3
+ #include <string>
4
+
5
+ static char to_char(int c)
6
+ {
7
+ return static_cast<char>(c);
8
+ }
9
+
10
+ static char hex_to_byte(char hi, char lo)
11
+ {
12
+ auto nibble = [](char c) -> int
13
+ {
14
+ if (c >= '0' && c <= '9')
15
+ return c - '0';
16
+
17
+ if (c >= 'a' && c <= 'f')
18
+ return c - 'a' + 10;
19
+
20
+ if (c >= 'A' && c <= 'F')
21
+ return c - 'A' + 10;
22
+
23
+ return -1;
24
+ };
25
+ int hi_val = nibble(hi);
26
+ int lo_val = nibble(lo);
27
+
28
+ if (hi_val < 0 || lo_val < 0)
29
+ return to_char(-1);
30
+
31
+ return to_char((hi_val << 4) | lo_val);
32
+ }
33
+
34
+ static char read_single_char(std::string const &token)
35
+ {
36
+ return to_char(token[1]);
37
+ }
38
+
39
+ static std::string read_double_string(std::string const &token)
40
+ {
41
+ return token.substr(1, token.size() - 2);
42
+ }
43
+
44
+ static bool is_whitespace(char c)
45
+ {
46
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n';
47
+ }
48
+
49
+ static char read_bitfield(std::string const &token)
50
+ {
51
+ unsigned char value = 0;
52
+
53
+ for (size_t i = 1; i <= 8; i++)
54
+ {
55
+ if (token[i] == '1')
56
+ {
57
+ value |= (1u << (8 - i));
58
+ }
59
+ }
60
+
61
+ return to_char(value);
62
+ }
63
+
64
+ Lexer::Lexer(std::string const &input, unsigned line_number,
65
+ std::string const &line_text)
66
+ : input(input), m_pos(0), m_column(1), m_line_number(line_number),
67
+
68
+ m_line_text(line_text)
69
+ {
70
+ }
71
+
72
+ size_t Lexer::pos() const
73
+ {
74
+ return m_pos;
75
+ }
76
+
77
+ size_t Lexer::size() const
78
+ {
79
+ return input.size();
80
+ }
81
+
82
+ bool Lexer::consume()
83
+ {
84
+ if (m_pos >= input.size())
85
+ {
86
+ return true;
87
+ }
88
+
89
+ m_error_line = m_line_number;
90
+ m_error_line_text = m_line_text;
91
+ char c = current_char();
92
+ return dispatch(c, m_column);
93
+ }
94
+
95
+ unsigned Lexer::error_line_number() const
96
+ {
97
+ return m_error_line;
98
+ }
99
+
100
+ unsigned Lexer::error_column() const
101
+ {
102
+ return m_error_column;
103
+ }
104
+
105
+ unsigned Lexer::error_length() const
106
+ {
107
+ return m_error_length;
108
+ }
109
+
110
+ std::string const &Lexer::error_line_text() const
111
+ {
112
+ return m_error_line_text;
113
+ }
114
+
115
+ std::string const &Lexer::error_message() const
116
+ {
117
+ return m_error_message;
118
+ }
119
+
120
+ bool Lexer::fail(unsigned column, unsigned length, std::string const &message)
121
+ {
122
+ m_error_column = column;
123
+ m_error_length = length;
124
+ m_error_message = message;
125
+ return false;
126
+ }
127
+
128
+ std::string const &Lexer::result() const
129
+ {
130
+ return m_result;
131
+ }
132
+
133
+ char Lexer::current_char() const
134
+ {
135
+ return input[m_pos];
136
+ }
137
+
138
+ bool Lexer::dispatch(char c, unsigned error_column)
139
+ {
140
+ if (is_whitespace(c))
141
+
142
+ {
143
+ skip_whitespace();
144
+ return true;
145
+ }
146
+
147
+ if (std::isxdigit(c))
148
+
149
+ {
150
+ return consume_hex_pair(error_column);
151
+ }
152
+
153
+ if (c == '\'')
154
+ {
155
+ return consume_single_char(error_column);
156
+ }
157
+
158
+ if (c == '"')
159
+ {
160
+ return consume_double_string(error_column);
161
+ }
162
+
163
+ if (c == '<')
164
+ {
165
+ return consume_bitfield(error_column);
166
+ }
167
+
168
+ return fail(error_column, 1, "unrecognized character");
169
+ }
170
+
171
+ void Lexer::skip_whitespace()
172
+ {
173
+ m_pos++;
174
+ m_column++;
175
+
176
+ while (m_pos < input.size())
177
+ {
178
+ char c = input[m_pos];
179
+
180
+ if (is_whitespace(c))
181
+
182
+ {
183
+ m_pos++;
184
+ m_column++;
185
+ }
186
+
187
+ else
188
+ {
189
+ break;
190
+ }
191
+ }
192
+ }
193
+
194
+ bool Lexer::consume_hex_pair(unsigned error_column)
195
+ {
196
+ if (m_pos + 1 < input.size() && std::isxdigit(input[m_pos + 1]))
197
+ {
198
+ char first = input[m_pos];
199
+ char second = input[m_pos + 1];
200
+ m_pos += 2;
201
+ m_column += 2;
202
+ m_result += hex_to_byte(first, second);
203
+ return true;
204
+ }
205
+
206
+ else
207
+ {
208
+ return fail(error_column, 1, "hex byte needs two hex digits");
209
+ }
210
+ }
211
+
212
+ bool Lexer::consume_single_char(unsigned error_column)
213
+ {
214
+ if (m_pos + 2 < input.size() && input[m_pos + 2] == '\'')
215
+ {
216
+ size_t token_start = m_pos;
217
+ m_pos += 3;
218
+ m_column += 3;
219
+ std::string token(input, token_start, 3);
220
+
221
+ m_result += read_single_char(token);
222
+ return true;
223
+ }
224
+
225
+ else
226
+ {
227
+ return fail(error_column, 1,
228
+ "single quotes must hold exactly one character");
229
+ }
230
+ }
231
+
232
+ bool Lexer::consume_double_string(unsigned error_column)
233
+ {
234
+ size_t token_start = m_pos;
235
+ size_t close = input.find('"', m_pos + 1);
236
+
237
+ if (close == std::string::npos)
238
+ {
239
+ return fail(error_column, 1,
240
+ "string has no closing quote on this line");
241
+ }
242
+
243
+ m_pos = close + 1;
244
+ m_column += close - token_start + 1;
245
+ std::string token(input, token_start, close - token_start + 1);
246
+
247
+ m_result += read_double_string(token);
248
+ return true;
249
+ }
250
+
251
+ bool Lexer::consume_bitfield(unsigned error_column)
252
+ {
253
+ if (m_pos + 9 >= input.size() || input[m_pos + 9] != '>')
254
+ {
255
+ return fail(error_column, 1,
256
+ "bitfield must be exactly 8 bits closed by '>' on the same line");
257
+ }
258
+
259
+ for (size_t i = 0; i < 8; i++)
260
+ {
261
+ char b = input[m_pos + 1 + i];
262
+
263
+ if (b != '0' && b != '1' && b != '.')
264
+ {
265
+ return fail(error_column + 1 + i, 1, "bitfield bits must be 0, 1, or .");
266
+ }
267
+ }
268
+
269
+ size_t token_start = m_pos;
270
+ m_pos += 10;
271
+ m_column += 10;
272
+ std::string token(input, token_start, 10);
273
+
274
+ m_result += read_bitfield(token);
275
+ return true;
276
+ }
data/bsm2/src/lexer.h ADDED
@@ -0,0 +1,59 @@
1
+ #ifndef LEXER_H
2
+ #define LEXER_H
3
+
4
+ #include "error_info.h"
5
+ #include <string>
6
+
7
+ class Lexer
8
+ {
9
+ std::string const &input;
10
+ size_t m_pos;
11
+ unsigned m_column;
12
+ unsigned m_error_line;
13
+ unsigned m_error_column;
14
+ unsigned m_error_length;
15
+ std::string m_error_line_text;
16
+ std::string m_error_message;
17
+ std::string m_result;
18
+ unsigned m_line_number;
19
+ std::string m_line_text;
20
+
21
+ public:
22
+ Lexer(std::string const &input, unsigned line_number,
23
+ std::string const &line_text);
24
+
25
+ size_t pos() const;
26
+
27
+ size_t size() const;
28
+
29
+ bool consume();
30
+
31
+ unsigned error_line_number() const;
32
+
33
+ unsigned error_column() const;
34
+
35
+ unsigned error_length() const;
36
+
37
+ std::string const &error_line_text() const;
38
+ std::string const &error_message() const;
39
+ std::string const &result() const;
40
+
41
+ private:
42
+ char current_char() const;
43
+
44
+ bool fail(unsigned column, unsigned length, std::string const &message);
45
+
46
+ bool dispatch(char c, unsigned error_column);
47
+
48
+ void skip_whitespace();
49
+
50
+ bool consume_hex_pair(unsigned error_column);
51
+
52
+ bool consume_single_char(unsigned error_column);
53
+
54
+ bool consume_double_string(unsigned error_column);
55
+
56
+ bool consume_bitfield(unsigned error_column);
57
+ };
58
+
59
+ #endif // LEXER_H
@@ -0,0 +1,2 @@
1
+ # MSB2 convert binary file to bsm2 format
2
+
@@ -0,0 +1,38 @@
1
+ #include <iostream>
2
+ #include <iomanip>
3
+ #include <fstream>
4
+
5
+ int main()
6
+ {
7
+ int bytecount = 0;
8
+
9
+ std::cout << ";";
10
+
11
+ do
12
+ {
13
+ unsigned char c = std::cin.get();
14
+
15
+ if (!std::cin.good())
16
+ {
17
+ break;
18
+ }
19
+
20
+ std::cout << " " << std::setw(2) << std::setfill('0') << std::hex << (int)c;
21
+ bytecount++;
22
+
23
+ if (bytecount % 16 == 0)
24
+ {
25
+ std::cout << "\n;";
26
+ }
27
+
28
+ else if (bytecount % 4 == 0)
29
+ {
30
+ std::cout << " ";
31
+ }
32
+ }
33
+ while (std::cin.good());
34
+
35
+ std::cout << "\nTotal bytes: " << bytecount << std::endl;
36
+
37
+ return EXIT_SUCCESS;
38
+ }
@@ -0,0 +1,31 @@
1
+ #include "processor.h"
2
+ #include "lexer.h"
3
+ #include <string>
4
+
5
+ std::string Processor::lex_line(std::string const &input, ErrorInfo *out_error)
6
+ {
7
+ curline++;
8
+
9
+ if (input.length() == 0 || input[0] != ';')
10
+ {
11
+ return "";
12
+ }
13
+
14
+ std::string const rest = input.substr(1);
15
+ Lexer l(rest, curline, input);
16
+
17
+ while (l.pos() < l.size())
18
+ {
19
+ if (!l.consume())
20
+ {
21
+ out_error->line_number = l.error_line_number();
22
+ out_error->column = l.error_column();
23
+ out_error->char_length = l.error_length();
24
+ out_error->line_text = l.error_line_text();
25
+ out_error->message = l.error_message();
26
+ return "";
27
+ }
28
+ }
29
+
30
+ return l.result();
31
+ }
@@ -0,0 +1,23 @@
1
+ #ifndef PROCESSOR_H
2
+ #define PROCESSOR_H
3
+
4
+ #include "lexer.h"
5
+ #include <string>
6
+
7
+ class Processor
8
+ {
9
+ unsigned curline;
10
+
11
+ public:
12
+ Processor() : curline(0)
13
+ {
14
+ }
15
+
16
+ ~Processor()
17
+ {
18
+ }
19
+
20
+ std::string lex_line(std::string const &input, ErrorInfo *out_error);
21
+ };
22
+
23
+ #endif // PROCESSOR_H
data/exe/bsm CHANGED
@@ -1,14 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require 'bsm/bsm_parse'
4
4
  require 'json'
5
+ require 'bsm'
5
6
 
6
- ast = JSON.parse(Bsm::BsmParse::compile_to_ast(ARGF.read))
7
+ content = ARGF.to_a.join("\n")
8
+ gen = Bsm::Generator.new
7
9
 
8
- result = ''
9
-
10
- ast['hexlits'].each do |chr|
11
- result << chr['_token'].to_i(16)
12
- end
13
-
14
- print result
10
+ print gen.generate(content)
@@ -0,0 +1,119 @@
1
+ #include "c_api.h"
2
+
3
+ #include <ruby.h>
4
+ #include <ruby/encoding.h>
5
+ #include <string.h>
6
+
7
+ static VALUE mBsm;
8
+ static VALUE cGenerator;
9
+ static VALUE eInvalidInput;
10
+
11
+ struct generator_wrapper
12
+ {
13
+ bsm2_processor* proc;
14
+ };
15
+
16
+ static void generator_free(void* ptr);
17
+
18
+ static const rb_data_type_t generator_data_type = {
19
+ "Bsm/GeneratorInternal",
20
+ {NULL, generator_free, NULL},
21
+ NULL,
22
+ NULL,
23
+ RUBY_TYPED_FREE_IMMEDIATELY,
24
+ };
25
+
26
+ static void generator_free(void* ptr)
27
+ {
28
+ struct generator_wrapper* w = (struct generator_wrapper*)ptr;
29
+ if (w)
30
+ {
31
+ if (w->proc)
32
+ {
33
+ bsm2_free(w->proc);
34
+ }
35
+ xfree(w);
36
+ }
37
+ }
38
+
39
+ static VALUE generator_alloc(VALUE klass)
40
+ {
41
+ struct generator_wrapper* w;
42
+ VALUE obj = TypedData_Make_Struct(
43
+ klass, struct generator_wrapper, &(generator_data_type), w);
44
+ w->proc = bsm2_new();
45
+ if (!w->proc)
46
+ {
47
+ rb_raise(eInvalidInput, "bsm2_new failed");
48
+ }
49
+ return obj;
50
+ }
51
+
52
+ static VALUE generator_generate_raw(VALUE self, VALUE input)
53
+ {
54
+ struct generator_wrapper* w;
55
+ TypedData_Get_Struct(self, struct generator_wrapper, &generator_data_type, w);
56
+
57
+ input = rb_String(input);
58
+
59
+ /* Iterate over lines, feeding each to bsm2_convert_line, concatenating
60
+ * the returned bytes into one binary String. */
61
+ VALUE result = rb_str_new_literal("");
62
+ rb_enc_associate(result, rb_ascii8bit_encoding());
63
+
64
+ char* cstr = StringValuePtr(input);
65
+ long total = RSTRING_LEN(input);
66
+ long line_start = 0;
67
+
68
+ for (long i = 0; i <= total; i++)
69
+ {
70
+ if (i == total || cstr[i] == '\n')
71
+ {
72
+ long line_len = i - line_start;
73
+ const char* line = cstr + line_start;
74
+
75
+ size_t out_len = 0;
76
+ bsm2_error* err = NULL;
77
+ char* out = bsm2_convert_line(w->proc, line, (size_t)line_len,
78
+ &out_len, &err);
79
+ if (!out)
80
+ {
81
+ if (err)
82
+ {
83
+ VALUE msg = rb_str_new_cstr(bsm2_error_message(err));
84
+ VALUE exc = rb_funcall(eInvalidInput, rb_intern("new"), 1, msg);
85
+ rb_iv_set(exc, "@line", UINT2NUM(bsm2_error_line(err)));
86
+ rb_iv_set(exc, "@column", UINT2NUM(bsm2_error_column(err)));
87
+ rb_iv_set(exc, "@length", UINT2NUM(bsm2_error_length(err)));
88
+ rb_iv_set(exc, "@line_text",
89
+ rb_str_new_cstr(bsm2_error_line_text(err)));
90
+ bsm2_error_free(err);
91
+ rb_exc_raise(exc);
92
+ }
93
+ rb_raise(eInvalidInput, "bsm2_convert_line failed");
94
+ }
95
+
96
+ if (out_len)
97
+ {
98
+ rb_str_cat(result, out, (long)out_len);
99
+ }
100
+ free(out);
101
+
102
+ line_start = i + 1;
103
+ }
104
+ }
105
+
106
+ return result;
107
+ }
108
+
109
+ void Init_internal(void)
110
+ {
111
+ mBsm = rb_define_module("Bsm");
112
+ eInvalidInput =
113
+ rb_define_class_under(mBsm, "InvalidInput", rb_eStandardError);
114
+ cGenerator =
115
+ rb_define_class_under(mBsm, "GeneratorInternal", rb_cObject);
116
+
117
+ rb_define_alloc_func(cGenerator, generator_alloc);
118
+ rb_define_method(cGenerator, "generate_raw", generator_generate_raw, 1);
119
+ }
@@ -2,6 +2,29 @@
2
2
 
3
3
  require 'mkmf'
4
4
 
5
- have_library('stdc++')
6
- $CXXFLAGS = '-g -Wall -std=gnu++11'
5
+ # bsm2 source is vendored at GEM_ROOT/bsm2 (a git submodule).
6
+ # extconf lives at GEM_ROOT/ext/bsm/internal, so three dirs up to the gem root.
7
+ BSM2_DIR = File.expand_path('../../../bsm2', __dir__)
8
+
9
+ unless File.directory?(File.join(BSM2_DIR, 'src'))
10
+ abort "bsm2 source not found at #{BSM2_DIR}/src. " \
11
+ 'If this is a checkout, init the submodule: git submodule update --init'
12
+ end
13
+
14
+ # Build the static library from vendored source.
15
+ Dir.chdir(BSM2_DIR) do
16
+ success = system('make', 'libbsm2')
17
+ abort 'Failed to build bsm2/libbsm2.a (requires g++ with C++17)' unless success
18
+ end
19
+
20
+ # Expose the C API header and the built static lib to the extension.
21
+ # rubocop:disable Style/GlobalVars
22
+ $INCFLAGS << " -I#{File.join(BSM2_DIR, 'src')}"
23
+ $LDFLAGS << " -L#{File.join(BSM2_DIR, 'lib')}"
24
+ # rubocop:enable Style/GlobalVars
25
+
26
+ # libbsm2.a is C++ internally (std::string, new/delete); link the C++ runtime.
27
+ have_library('stdc++') or abort 'libstdc++ not found'
28
+ have_library('bsm2', 'bsm2_new') or abort 'libbsm2 not found / bsm2_new missing'
29
+
7
30
  create_makefile('bsm/internal')
Binary file
data/lib/bsm/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bsm
2
- VERSION = '0.1.0'.freeze
4
+ VERSION = '0.2.1'
3
5
  end
data/lib/bsm.rb CHANGED
@@ -1,6 +1,25 @@
1
+ # frozen_string_literal: false
2
+
1
3
  require 'bsm/version'
4
+ require 'bsm/internal'
2
5
 
3
6
  module Bsm
4
- class Error < StandardError; end
5
- # Your code goes here...
7
+ # Raised when bsm2 rejects input. Carries the structured error fields
8
+ # (line, column, length, line_text) from the native library so callers can
9
+ # report precise locations.
10
+ class InvalidInput < StandardError
11
+ attr_accessor :line, :column, :length, :line_text
12
+ end
13
+
14
+ # The generator class. Input is in bsm2 literate format: a line is data
15
+ # only if it starts with a semicolon (;); other lines are ignored.
16
+ class Generator
17
+ def initialize(_options = {})
18
+ @generator = GeneratorInternal.new
19
+ end
20
+
21
+ def generate(input)
22
+ @generator.generate_raw(input)
23
+ end
24
+ end
6
25
  end
metadata CHANGED
@@ -1,74 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Siaw
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-09-27 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '10.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '10.0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake-compiler
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rspec
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.0'
69
- description: a longer description
11
+ date: 2026-07-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Bsm converts literate text (semicolon-prefixed lines of hex, strings,
14
+ bitfields) into binary. Backed by the native bsm2 library.
70
15
  email:
71
- - dsiaw@degica.com
16
+ - 874280+davidsiaw@users.noreply.github.com
72
17
  executables:
73
18
  - bsm
74
19
  extensions:
@@ -77,21 +22,34 @@ extra_rdoc_files: []
77
22
  files:
78
23
  - Gemfile
79
24
  - bsm.gemspec
25
+ - bsm2/Makefile
26
+ - bsm2/src/bsm2/main.cpp
27
+ - bsm2/src/c_api.cpp
28
+ - bsm2/src/c_api.h
29
+ - bsm2/src/converter.h
30
+ - bsm2/src/error_info.h
31
+ - bsm2/src/lexer.cpp
32
+ - bsm2/src/lexer.h
33
+ - bsm2/src/msb2/README.md
34
+ - bsm2/src/msb2/main.cpp
35
+ - bsm2/src/processor.cpp
36
+ - bsm2/src/processor.h
80
37
  - exe/bsm
38
+ - ext/bsm/internal/bsm.c
81
39
  - ext/bsm/internal/extconf.rb
82
40
  - lib/bsm.rb
83
- - lib/bsm/bsm_parse.rb
84
- - lib/bsm/bsm_parse/internal.bundle
41
+ - lib/bsm/internal.so
85
42
  - lib/bsm/version.rb
86
- homepage: https://github.org/davidsiaw/bsm
43
+ homepage: https://github.com/davidsiaw/bsm
87
44
  licenses:
88
45
  - MIT
89
46
  metadata:
90
47
  allowed_push_host: https://rubygems.org
91
- homepage_uri: https://github.org/davidsiaw/bsm
92
- source_code_uri: https://github.org/davidsiaw/bsm
93
- changelog_uri: https://github.org/davidsiaw/bsm
94
- post_install_message:
48
+ homepage_uri: https://github.com/davidsiaw/bsm
49
+ source_code_uri: https://github.com/davidsiaw/bsm
50
+ changelog_uri: https://github.com/davidsiaw/bsm
51
+ rubygems_mfa_required: 'true'
52
+ post_install_message:
95
53
  rdoc_options: []
96
54
  require_paths:
97
55
  - lib
@@ -99,16 +57,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
99
57
  requirements:
100
58
  - - ">="
101
59
  - !ruby/object:Gem::Version
102
- version: '0'
60
+ version: '3.0'
103
61
  required_rubygems_version: !ruby/object:Gem::Requirement
104
62
  requirements:
105
63
  - - ">="
106
64
  - !ruby/object:Gem::Version
107
65
  version: '0'
108
66
  requirements: []
109
- rubyforge_project:
110
- rubygems_version: 2.7.7
111
- signing_key:
67
+ rubygems_version: 3.5.22
68
+ signing_key:
112
69
  specification_version: 4
113
- summary: a short summary
70
+ summary: Write binary files in a human-readable way
114
71
  test_files: []
Binary file
data/lib/bsm/bsm_parse.rb DELETED
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bsm/bsm_parse/internal'
4
-
5
- module Bsm
6
- # Parser module, wraps the native functions with some error handling
7
- module BsmParse
8
- def self.runtest(hello)
9
- bsm_parse_test(hello)
10
- end
11
-
12
- def self.compile_to_ast(string)
13
- return bsm_parse_compile_to_ast(string) if string.is_a? String
14
-
15
- raise 'Invalid type given. This method only accepts strings'
16
- end
17
- end
18
- end