Skip to content

Commit 1ec2e6c

Browse files
committed
Preliminary Rust code generation, integration test
1 parent cac1444 commit 1ec2e6c

28 files changed

+2968
-134
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ csharp/sbe-generated/since-deprecated
110110
csharp/sbe-generated/mktdata/*.cs
111111
csharp/sbe-generated/uk_co_real_logic_sbe_benchmarks_fix
112112

113+
# rust
114+
rust/car_example/.vscode
115+
rust/car_example/car_example_data.sbe
116+
rust/car_example/Cargo.lock
117+
rust/car_example/src/car_example_generated_codec.rs
118+
rust/car_example/target
119+
113120
# Mac
114121
.DS_Store
115122
/sbe-tool/src/main/golang/uk_co_real_logic_sbe_ir_generated/

build.gradle

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def validationXsdPath = project(':sbe-tool').projectDir.toString() + '/src/main/
165165
project(':sbe-tool') {
166166
dependencies {
167167
compile 'org.agrona:agrona:0.9.6'
168+
compile 'com.google.guava:guava:21.0'
168169

169170
testCompile files('build/classes/java/generated')
170171
}
@@ -468,6 +469,56 @@ project(':sbe-benchmarks') {
468469
}
469470
}
470471

472+
/*
473+
* Rust codec targets used for testing and demonstration
474+
*/
475+
task(generateRustCarExample, type: JavaExec) {
476+
main = 'uk.co.real_logic.sbe.SbeTool'
477+
classpath = project(':sbe-all').sourceSets.main.runtimeClasspath
478+
systemProperties(
479+
'sbe.output.dir': 'rust/car_example/src',
480+
'sbe.target.language': 'rust',
481+
'sbe.target.namespace': 'car_example_generated_codec')
482+
args = ['sbe-tool/src/test/resources/example-schema.xml']
483+
}
484+
task(generateCarExampleDataFile, type: JavaExec) {
485+
main = 'uk.co.real_logic.sbe.examples.ExampleUsingGeneratedStub'
486+
classpath = project(':sbe-samples').sourceSets.main.runtimeClasspath
487+
systemProperties(
488+
'sbe.encoding.filename': 'rust/car_example/car_example_data.sbe')
489+
args = []
490+
standardOutput = new ByteArrayOutputStream()
491+
}
492+
493+
task(runRustCarExample, type: Exec) {
494+
workingDir './rust/car_example'
495+
commandLine 'cargo'
496+
args 'run'
497+
standardOutput = new ByteArrayOutputStream()
498+
errorOutput = new ByteArrayOutputStream()
499+
dependsOn 'generateRustCarExample', 'generateCarExampleDataFile'
500+
}
501+
502+
def cargo_exists() {
503+
def stdout = new ByteArrayOutputStream()
504+
def result = project.exec {
505+
commandLine 'cargo'
506+
args '-v'
507+
standardOutput = stdout
508+
}
509+
return result.exitValue == 0
510+
}
511+
512+
if (cargo_exists()) {
513+
test.dependsOn('runRustCarExample')
514+
}
515+
516+
task generateRustCodecs {
517+
description = 'Generate rust test codecs'
518+
dependsOn 'generateRustCarExample',
519+
'generateCarExampleDataFile'
520+
}
521+
471522
/*
472523
* Golang codec targets used for testing, benchmarking etc. We have
473524
* multiple targets as:

rust/car_example/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "sbe_rust_car_example"
3+
version = "0.1.0"
4+
authors = []
5+
6+
[dependencies]
7+
[dev-dependencies]

rust/car_example/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Rust Car Example project
2+
3+
Depends on SBE-tool generated bindings, which can be created by running, from the simple-binary-encoding project root,
4+
`./gradlew generateRustCodecs`.
5+
6+
Assumes the presence of Rust and Cargo.

rust/car_example/src/main.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
2+
/// Note that the included codec file and the data file further below
3+
/// are generated through SBE's gradle tooling. `./gradlew generateRustCodecs`
4+
include!("car_example_generated_codec.rs");
5+
6+
use std::fs::File;
7+
use std::io::prelude::*;
8+
use std::io::Write;
9+
10+
pub fn main() {
11+
::std::process::exit(match run_car_example() {
12+
Ok(_) => 0,
13+
Err(e) => {
14+
writeln!(::std::io::stderr(), "error: {:?}", e).unwrap();
15+
1
16+
}
17+
});
18+
}
19+
20+
fn read_sbe_file_generated_from_java_example() -> IoResult<Vec<u8>> {
21+
// Generated by the generateCarExampleDataFile gradle task.
22+
let mut f = File::open("car_example_data.sbe")?;
23+
let mut buffer = Vec::new();
24+
f.read_to_end(&mut buffer)?;
25+
Ok(buffer)
26+
}
27+
28+
fn run_car_example() -> IoResult<()> {
29+
let reference_example_bytes = read_sbe_file_generated_from_java_example()?;
30+
decode_car_and_assert_expected_content(&reference_example_bytes)?;
31+
let bytes_encoded_from_rust = encode_car_from_scratch()?;
32+
assert_eq!(reference_example_bytes, bytes_encoded_from_rust);
33+
decode_car_and_assert_expected_content(&bytes_encoded_from_rust)?;
34+
Ok(())
35+
}
36+
37+
fn decode_car_and_assert_expected_content(buffer: &[u8]) -> IoResult<()> {
38+
let (h, dec_fields) = start_decoding_car(&buffer).header()?;
39+
assert_eq!(49u16, h.block_length);
40+
assert_eq!(1u16, h.template_id);
41+
assert_eq!(1u16, h.schema_id);
42+
assert_eq!(0u16, h.version);
43+
println!("Header read");
44+
45+
46+
let mut found_fuel_figures = Vec::<FuelFigure>::with_capacity(EXPECTED_FUEL_FIGURES.len());
47+
48+
let (fields, dec_fuel_figures_header) = dec_fields.car_fields()?;
49+
assert_eq!(1234, fields.serial_number);
50+
assert_eq!(2013, fields.model_year);
51+
assert_eq!(BooleanType::T, fields.available);
52+
assert_eq!([97_i8, 98, 99, 100, 101, 102], fields.vehicle_code); // abcdef
53+
assert_eq!([0_u32, 1, 2, 3, 4], fields.some_numbers);
54+
55+
let dec_perf_figures_header = match dec_fuel_figures_header.fuel_figures_individually()? {
56+
Either::Left(mut dec_ff_members) => {
57+
println!("Got some fuel figure members");
58+
let mut decoder_after_group = None;
59+
loop {
60+
let (ff_fields, dec_usage_description) = dec_ff_members.next_fuel_figures_member()?;
61+
let (usage_description, next_step) = dec_usage_description.usage_description()?;
62+
let usage_str = std::str::from_utf8(usage_description).unwrap();
63+
println!("Fuel Figure: Speed: {0}, MPG: {1}, Usage: {2}",
64+
ff_fields.speed,
65+
ff_fields.mpg,
66+
usage_str);
67+
found_fuel_figures.push(FuelFigure {
68+
speed: ff_fields.speed,
69+
mpg: ff_fields.mpg,
70+
usage_description: usage_str,
71+
});
72+
match next_step {
73+
Either::Left(more_members) => dec_ff_members = more_members,
74+
Either::Right(done_with_group) => {
75+
decoder_after_group = Some(done_with_group);
76+
break;
77+
}
78+
}
79+
}
80+
decoder_after_group.unwrap()
81+
}
82+
Either::Right(next_decoder) => next_decoder,
83+
};
84+
assert!(EXPECTED_FUEL_FIGURES
85+
.iter()
86+
.zip(found_fuel_figures.iter())
87+
.all(|(exp, found)| exp == found),
88+
"fuel figures should match expected values");
89+
90+
let dec_manufacturer = match dec_perf_figures_header.performance_figures_individually()? {
91+
Either::Left(mut dec_pf_members) => {
92+
let mut decoder_after_pf_group = None;
93+
println!("Got some performance figure members");
94+
loop {
95+
let (pf_fields, dec_acceleration_header) = dec_pf_members
96+
.next_performance_figures_member()?;
97+
println!("Performance Figure Fixed Fields: Octane Rating: {0}",
98+
pf_fields.octane_rating);
99+
let (accel_slice, next_step) = dec_acceleration_header.acceleration_as_slice()?;
100+
for accel_fields in accel_slice {
101+
println!("Acceleration: MPH: {0}, Seconds: {1}",
102+
accel_fields.mph,
103+
accel_fields.seconds);
104+
}
105+
match next_step {
106+
Either::Left(more_members) => dec_pf_members = more_members,
107+
Either::Right(done_with_group) => {
108+
decoder_after_pf_group = Some(done_with_group);
109+
break;
110+
}
111+
}
112+
}
113+
decoder_after_pf_group.unwrap()
114+
}
115+
Either::Right(next_decoder) => next_decoder,
116+
};
117+
let (manufacturer, dec_model) = dec_manufacturer.manufacturer()?;
118+
let manufacturer = std::str::from_utf8(manufacturer).unwrap();
119+
println!("Manufacturer: {}", manufacturer);
120+
assert_eq!("Honda", manufacturer);
121+
122+
let (model, dec_activation_code) = dec_model.model()?;
123+
let model = std::str::from_utf8(model).unwrap();
124+
println!("Model: {}", model);
125+
assert_eq!("Civic VTi", model);
126+
127+
let (activation_code, dec_done) = dec_activation_code.activation_code()?;
128+
let activation_code = std::str::from_utf8(activation_code).unwrap();
129+
println!("Activation Code: {}", activation_code);
130+
assert_eq!("abcdef", activation_code);
131+
132+
let (position, buffer_back) = dec_done.unwrap();
133+
println!("Finished decoding. Made it to position {0} out of {1}",
134+
position,
135+
buffer_back.len());
136+
Ok(())
137+
}
138+
139+
fn encode_car_from_scratch() -> IoResult<Vec<u8>> {
140+
let mut buffer = vec![0u8; 256];
141+
let used_pos = {
142+
let enc_header = start_encoding_car(&mut buffer);
143+
let enc_fields = enc_header
144+
.header_copy(&CarMessageHeader::default().message_header)?;
145+
println!("encoded header");
146+
let (fields, enc_fuel_figures_header) = enc_fields.car_fields()?;
147+
fields.serial_number = 1234;
148+
fields.model_year = 2013;
149+
fields.available = BooleanType::T;
150+
fields.code = Model::A;
151+
fields.vehicle_code = [97_i8, 98, 99, 100, 101, 102]; // abcdef
152+
fields.some_numbers = [0_u32, 1, 2, 3, 4];
153+
fields.extras = OptionalExtras(6);
154+
fields.engine = Engine {
155+
capacity: 2000,
156+
num_cylinders: 4,
157+
manufacturer_code: [49, 50, 51], // 123
158+
efficiency: 35,
159+
booster_enabled: BooleanType::T,
160+
booster: Booster {
161+
boost_type: BoostType::NITROUS,
162+
horse_power: 200,
163+
},
164+
};
165+
println!("encoded top level fields");
166+
let mut enc_fuel_figures = enc_fuel_figures_header.fuel_figures_individually()?;
167+
let mut fuel_figure_scratch = CarFuelFiguresMember { speed: 0, mpg: 0.0 };
168+
for ff in EXPECTED_FUEL_FIGURES {
169+
fuel_figure_scratch.speed = ff.speed;
170+
fuel_figure_scratch.mpg = ff.mpg;
171+
//fuel_figure_scratch.usage_description = ff.mpg;
172+
let enc_usage = enc_fuel_figures
173+
.next_fuel_figures_member(&fuel_figure_scratch)?;
174+
enc_fuel_figures = enc_usage
175+
.usage_description(ff.usage_description.as_bytes())?;
176+
}
177+
let enc_perf_figures_header = enc_fuel_figures.done_with_fuel_figures()?;
178+
println!("encoded fuel figures");
179+
let mut perf_figure_member_scratch = CarPerformanceFiguresMember { octane_rating: 0 };
180+
let mut enc_perf_figures = enc_perf_figures_header.performance_figures_individually()?;
181+
for pf in EXPECTED_PERF_FIXTURES {
182+
perf_figure_member_scratch.octane_rating = pf.octane_rating;
183+
let enc_accel = enc_perf_figures
184+
.next_performance_figures_member(&perf_figure_member_scratch)?;
185+
enc_perf_figures = enc_accel.acceleration_from_slice(&pf.acceleration)?;
186+
}
187+
let enc_manufacturer = enc_perf_figures.done_with_performance_figures()?;
188+
println!("encoded perf figures");
189+
let enc_model = enc_manufacturer.manufacturer("Honda".as_bytes())?;
190+
let enc_activation_code = enc_model.model("Civic VTi".as_bytes())?;
191+
let done = enc_activation_code.activation_code("abcdef".as_bytes())?;
192+
let (pos, _) = done.unwrap();
193+
pos
194+
};
195+
println!("encoded up to position {}", used_pos);
196+
buffer.truncate(used_pos);
197+
198+
Ok(buffer)
199+
}
200+
201+
#[derive(Debug, PartialEq)]
202+
struct FuelFigure<'d> {
203+
speed: u16,
204+
mpg: f32,
205+
usage_description: &'d str,
206+
}
207+
208+
const EXPECTED_FUEL_FIGURES: &'static [FuelFigure] = &[FuelFigure {
209+
speed: 30,
210+
mpg: 35.9,
211+
usage_description: "Urban Cycle",
212+
},
213+
FuelFigure {
214+
speed: 55,
215+
mpg: 49.0,
216+
usage_description: "Combined Cycle",
217+
},
218+
FuelFigure {
219+
speed: 75,
220+
mpg: 40.0,
221+
usage_description: "Highway Cycle",
222+
}];
223+
224+
struct PerfFigure {
225+
octane_rating: u8,
226+
acceleration: [CarPerformanceFiguresAccelerationMember; 3],
227+
}
228+
229+
const EXPECTED_PERF_FIXTURES: &'static [PerfFigure] = &[PerfFigure {
230+
octane_rating: 95,
231+
acceleration: [CarPerformanceFiguresAccelerationMember {
232+
mph: 30,
233+
seconds: 4.0,
234+
},
235+
CarPerformanceFiguresAccelerationMember {
236+
mph: 60,
237+
seconds: 7.5,
238+
},
239+
CarPerformanceFiguresAccelerationMember {
240+
mph: 100,
241+
seconds: 12.2,
242+
}],
243+
},
244+
PerfFigure {
245+
octane_rating: 99,
246+
acceleration: [CarPerformanceFiguresAccelerationMember {
247+
mph: 30,
248+
seconds: 3.8,
249+
},
250+
CarPerformanceFiguresAccelerationMember {
251+
mph: 60,
252+
seconds: 7.1,
253+
},
254+
CarPerformanceFiguresAccelerationMember {
255+
mph: 100,
256+
seconds: 11.8,
257+
}],
258+
}];

sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import uk.co.real_logic.sbe.generation.golang.GolangGenerator;
2222
import uk.co.real_logic.sbe.generation.golang.GolangOutputManager;
2323
import uk.co.real_logic.sbe.generation.java.JavaGenerator;
24+
import uk.co.real_logic.sbe.generation.rust.Rust;
2425
import uk.co.real_logic.sbe.ir.Ir;
2526

2627
import java.io.IOException;
@@ -58,6 +59,14 @@ public CodeGenerator newInstance(final Ir ir, final String outputDir) throws IOE
5859
{
5960
return new GolangGenerator(ir, new GolangOutputManager(outputDir, ir.applicableNamespace()));
6061
}
62+
},
63+
64+
RUST()
65+
{
66+
public CodeGenerator newInstance(final Ir ir, final String outputDir) throws IOException
67+
{
68+
return Rust.defaultRustGenerator(ir, outputDir);
69+
}
6170
};
6271

6372
/**
@@ -84,7 +93,6 @@ public static TargetCodeGenerator get(final String name)
8493
{
8594
// do nothing and fall through
8695
}
87-
8896
throw new IllegalArgumentException("No code generator for name: " + name);
8997
}
9098
}

0 commit comments

Comments
 (0)