diff --git a/docs/file_format/README.md b/docs/file_format/README.md index 2912d59..20ca00e 100644 --- a/docs/file_format/README.md +++ b/docs/file_format/README.md @@ -9,7 +9,6 @@ attribute. Check their specific documents for in-deep explanations. ```yaml settings: base_path: build - use_subalign: True subalign: 32 segments: @@ -27,7 +26,7 @@ segments: - { path: asm/entry.o } - name: boot - use_subalign: False + subalign: null files: - { path: src/boot/boot_main.o } - { path: src/boot/dmadata.o } @@ -47,9 +46,9 @@ This will force this segment to be put at this specific address. Since the `boot` segment does not specify a fixed `vram` address then its address will be the end vram address of the previous segment (`entry`). -The `boot` segment specified `use_subalign` to be `False`, so a`SUBALIGN` -directive should not be used for this segment. This means sections from every -file will be aligned using the alignment from the elf files. +The `boot` segment specified `subalign` to be `null`, so a `SUBALIGN` directive +should not be used for this segment. This means sections from every file will +be aligned using the alignment from the elf files. The same sections of each file are put together in the order specified by the `settings`. Since no order was specified in this example then the default order diff --git a/docs/file_format/file.md b/docs/file_format/file.md index e048e10..b48db87 100644 --- a/docs/file_format/file.md +++ b/docs/file_format/file.md @@ -24,7 +24,7 @@ segments: ### Valid values -Any valid path. +Any valid non-empty path. ## `kind` diff --git a/docs/file_format/segments.md b/docs/file_format/segments.md index 0f4fc6f..40fd231 100644 --- a/docs/file_format/segments.md +++ b/docs/file_format/segments.md @@ -24,7 +24,7 @@ segments: ### Valid values -String. +Non empty string. TODO: Impose rules for valid names? @@ -70,35 +70,14 @@ Any unsigned integer. `null` -## `use_subalign` - -Toggle using the `SUBALIGN` directive on this segment. - -This option overrides the global setting, see -[settings.md#use_subalign](settings.md#use_subalign) for more info. - -### Example - -```yaml -segments: - - name: boot - use_subalign: False -``` - -### Valid values - -Boolean - -### Default value - -The value specified for [settings.md#use_subalign](settings.md#use_subalign) - ## `subalign` The value to use in the `SUBALIGN` directive for this segment. -The [`use_subalign`](#use_subalign) option controls if this directive is -emitted or not. +If `null` is specified then no `SUBALIGN` directive is used for this segment. + +If an integer is used then the `SUBALIGN` will be emitted for this segment, +regarding the global setting. This option overrides the global setting, see [settings.md#subalign](settings.md#subalign) for more info. @@ -108,12 +87,12 @@ This option overrides the global setting, see ```yaml segments: - name: main - subalign: 4 + subalign: null ``` ### Valid values -Positive integers +Positive integers or `null`. ### Default value diff --git a/docs/file_format/settings.md b/docs/file_format/settings.md index 4fcdb8f..106a02c 100644 --- a/docs/file_format/settings.md +++ b/docs/file_format/settings.md @@ -120,34 +120,11 @@ List of strings. `[.sbss, .scommon, .bss, COMMON]` -## `use_subalign` - -Toggle using `SUBALIGN` directives on the segments. - -This option can be overriden per segment, see -[segments.md#use_subalign](segments.md#use_subalign) for more info. - -### Example - -```yaml -settings: - use_subalign: False -``` - -### Valid values - -Boolean - -### Default value - -`True` - ## `subalign` The value to use in the `SUBALIGN` directives. -The [`use_subalign`](#use_subalign) option controls if this directive is -emitted or not. +If the value is `null` then disables using `SUBALIGN` directives. This option can be overriden per segment, see [segments.md#subalign](segments.md#subalign) for more info. @@ -161,7 +138,7 @@ settings: ### Valid values -Positive integers +Positive integers or `null`. ### Default value diff --git a/slinky-cli/src/main.rs b/slinky-cli/src/main.rs index dd48d16..bb5b5f6 100644 --- a/slinky-cli/src/main.rs +++ b/slinky-cli/src/main.rs @@ -10,6 +10,8 @@ fn main() { let document = Document::read_file(Path::new("test_case.yaml")).expect("Error while parsing input file"); + // println!("settings {:#?}", document.settings); + let mut writer = LinkerWriter::new(&document.settings); writer.begin_sections(); for segment in &document.segments { diff --git a/slinky/src/absent_nullable.rs b/slinky/src/absent_nullable.rs new file mode 100644 index 0000000..e9125c0 --- /dev/null +++ b/slinky/src/absent_nullable.rs @@ -0,0 +1,73 @@ +/* SPDX-FileCopyrightText: © 2024 decompals */ +/* SPDX-License-Identifier: MIT */ + +use serde::{Deserialize, Deserializer}; + +use crate::SlinkyError; + +// https://stackoverflow.com/a/44332837/6292472 + +#[derive(Debug, PartialEq, Default)] +pub(crate) enum AbsentNullable { + #[default] + Absent, + Null, + Value(T), +} + +impl From> for AbsentNullable { + fn from(opt: Option) -> AbsentNullable { + match opt { + Some(v) => AbsentNullable::Value(v), + None => AbsentNullable::Null, + } + } +} + +impl<'de, T> Deserialize<'de> for AbsentNullable +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Option::deserialize(deserializer).map(Into::into) + } +} + +impl AbsentNullable { + pub fn get_non_null(self, name: &str, default: F) -> Result + where + F: FnOnce() -> T, + { + match self { + AbsentNullable::Absent => Ok(default()), + AbsentNullable::Null => Err(SlinkyError::NullValueOnNonNull { + name: name.to_string(), + }), + AbsentNullable::Value(v) => Ok(v), + } + } + + pub fn get_non_null_no_default(self, name: &str) -> Result, SlinkyError> { + match self { + AbsentNullable::Absent => Ok(None), + AbsentNullable::Null => Err(SlinkyError::NullValueOnNonNull { + name: name.to_string(), + }), + AbsentNullable::Value(v) => Ok(Some(v)), + } + } + + pub fn get_optional_nullable(self, _name: &str, default: F) -> Result, SlinkyError> + where + F: FnOnce() -> Option, + { + match self { + AbsentNullable::Absent => Ok(default()), + AbsentNullable::Null => Ok(None), + AbsentNullable::Value(v) => Ok(Some(v)), + } + } +} diff --git a/slinky/src/document.rs b/slinky/src/document.rs index 1080b87..d82d246 100644 --- a/slinky/src/document.rs +++ b/slinky/src/document.rs @@ -5,11 +5,13 @@ use std::{fs, path::Path}; use serde::Deserialize; -use crate::{FileKind, Segment, Settings, SlinkyError}; +use crate::{ + absent_nullable::AbsentNullable, segment::SegmentSerial, settings::SettingsSerial, Segment, + Settings, SlinkyError, +}; -#[derive(Deserialize, PartialEq, Debug)] +#[derive(PartialEq, Debug, Default)] pub struct Document { - #[serde(default)] pub settings: Settings, pub segments: Vec, @@ -25,7 +27,7 @@ impl Document { }) } }; - let mut document: Document = match serde_yaml::from_reader(f) { + let document_serial: DocumentSerial = match serde_yaml::from_reader(f) { Ok(d) => d, Err(e) => { return Err(SlinkyError::FailedYamlParsing { @@ -34,22 +36,32 @@ impl Document { } }; - for segment in &mut document.segments { - segment - .use_subalign - .get_or_insert(document.settings.use_subalign); - segment.subalign.get_or_insert(document.settings.subalign); + Ok(document_serial.unserialize()?) + } +} + +#[derive(Deserialize, PartialEq, Debug)] +pub(crate) struct DocumentSerial { + #[serde(default)] + pub settings: AbsentNullable, + + pub segments: Vec, +} + +impl DocumentSerial { + pub fn unserialize(self) -> Result { + let mut ret = Document::default(); - segment - .wildcard_sections - .get_or_insert(document.settings.wildcard_sections); + ret.settings = match self.settings.get_non_null_no_default("settings")? { + None => ret.settings, + Some(v) => v.unserialize()?, + }; - for file in &mut segment.files { - // TODO: guess based on file extension - file.kind.get_or_insert(FileKind::Object); - } + ret.segments.reserve(self.segments.len()); + for seg in self.segments { + ret.segments.push(seg.unserialize(&ret.settings)?); } - Ok(document) + Ok(ret) } } diff --git a/slinky/src/error.rs b/slinky/src/error.rs index 54c2f96..2eaa0da 100644 --- a/slinky/src/error.rs +++ b/slinky/src/error.rs @@ -7,4 +7,8 @@ pub enum SlinkyError { FailedFileOpen { description: String }, #[error("Unable parse yaml: {description}")] FailedYamlParsing { description: String }, + #[error("Non-nullable attribute '{name}' was null")] + NullValueOnNonNull { name: String }, + #[error("The attribute '{name}' should not be empty")] + EmptyValue { name: String }, } diff --git a/slinky/src/file_info.rs b/slinky/src/file_info.rs index e298f97..3a6fc98 100644 --- a/slinky/src/file_info.rs +++ b/slinky/src/file_info.rs @@ -2,13 +2,38 @@ /* SPDX-License-Identifier: MIT */ use serde::Deserialize; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -use crate::file_kind::FileKind; +use crate::{absent_nullable::AbsentNullable, file_kind::FileKind, Settings, SlinkyError}; -#[derive(Deserialize, PartialEq, Debug)] +#[derive(PartialEq, Debug)] pub struct FileInfo { pub path: PathBuf, - pub kind: Option, + pub kind: FileKind, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub(crate) struct FileInfoSerial { + pub path: PathBuf, + + #[serde(default)] + pub kind: AbsentNullable, +} + +impl FileInfoSerial { + pub(crate) fn unserialize(self, _settings: &Settings) -> Result { + if self.path == Path::new("") { + return Err(SlinkyError::EmptyValue { + name: "path".to_string(), + }); + } + + let path = self.path; + let kind = self + .kind + .get_non_null("kind", || FileKind::from_path(&path))?; + + Ok(FileInfo { path, kind }) + } } diff --git a/slinky/src/file_kind.rs b/slinky/src/file_kind.rs index 055a9ae..822f1b0 100644 --- a/slinky/src/file_kind.rs +++ b/slinky/src/file_kind.rs @@ -1,6 +1,8 @@ /* SPDX-FileCopyrightText: © 2024 decompals */ /* SPDX-License-Identifier: MIT */ +use std::path::Path; + use serde::Deserialize; #[derive(Deserialize, PartialEq, Debug)] @@ -9,3 +11,16 @@ pub enum FileKind { Object, Archive, } + +impl FileKind { + pub fn from_path(path: &Path) -> Self { + match path.extension() { + None => Self::Object, + Some(ext) => match ext.to_str() { + None => Self::Object, + Some("o") => Self::Object, + Some(&_) => Self::Object, + }, + } + } +} diff --git a/slinky/src/lib.rs b/slinky/src/lib.rs index 8a7d907..97e344b 100644 --- a/slinky/src/lib.rs +++ b/slinky/src/lib.rs @@ -1,6 +1,7 @@ /* SPDX-FileCopyrightText: © 2024 decompals */ /* SPDX-License-Identifier: MIT */ +mod absent_nullable; mod error; mod linker_symbols_style; diff --git a/slinky/src/linker_writer.rs b/slinky/src/linker_writer.rs index f118b9b..dc3c8da 100644 --- a/slinky/src/linker_writer.rs +++ b/slinky/src/linker_writer.rs @@ -176,8 +176,8 @@ impl LinkerWriter<'_> { line += &format!(" : AT({})", style.segment_rom_start(&segment.name)); } - if segment.use_subalign.unwrap() { - line += &format!(" SUBALIGN({})", segment.subalign.unwrap()); + if let Some(subalign) = segment.subalign { + line += &format!(" SUBALIGN({})", subalign); } self.writeln(&line); @@ -201,14 +201,10 @@ impl LinkerWriter<'_> { path.extend(&file.path); - let wildcard = if segment.wildcard_sections.unwrap() { - "*" - } else { - "" - }; + let wildcard = if segment.wildcard_sections { "*" } else { "" }; // TODO: figure out glob support - match file.kind.as_ref().unwrap() { + match file.kind { FileKind::Object => { self.writeln(&format!("{}({}{});", path.display(), section, wildcard)); } diff --git a/slinky/src/segment.rs b/slinky/src/segment.rs index 721a986..cd9903c 100644 --- a/slinky/src/segment.rs +++ b/slinky/src/segment.rs @@ -3,9 +3,13 @@ use serde::Deserialize; -use crate::file_info::FileInfo; +use crate::{ + absent_nullable::AbsentNullable, + file_info::{FileInfo, FileInfoSerial}, + Settings, SlinkyError, +}; -#[derive(Deserialize, PartialEq, Debug)] +#[derive(PartialEq, Debug)] pub struct Segment { pub name: String, pub files: Vec, @@ -15,8 +19,69 @@ pub struct Segment { // The default of the following come from Options // TODO: section_order (both alloc and noload) - pub use_subalign: Option, pub subalign: Option, - pub wildcard_sections: Option, + pub wildcard_sections: bool, +} + +impl Default for Segment { + fn default() -> Self { + Self { + name: "".to_string(), + files: Vec::new(), + + fixed_vram: None, + + subalign: None, + + wildcard_sections: false, + } + } +} + +#[derive(Deserialize, PartialEq, Debug)] +pub(crate) struct SegmentSerial { + pub name: String, + pub files: Vec, + + pub fixed_vram: Option, + + // The default of the following come from Options + + // TODO: section_order (both alloc and noload) + #[serde(default)] + pub subalign: AbsentNullable, + + #[serde(default)] + pub wildcard_sections: AbsentNullable, +} + +impl SegmentSerial { + pub fn unserialize(self, settings: &Settings) -> Result { + let mut ret = Segment::default(); + + if self.name.is_empty() { + return Err(SlinkyError::EmptyValue { + name: "name".to_string(), + }); + } + ret.name = self.name; + + ret.files.reserve(self.files.len()); + for file in self.files { + ret.files.push(file.unserialize(settings)?); + } + + ret.fixed_vram = self.fixed_vram; + + ret.subalign = self + .subalign + .get_optional_nullable("subalign", || settings.subalign)?; + + ret.wildcard_sections = self + .wildcard_sections + .get_non_null("wildcard_sections", || settings.wildcard_sections)?; + + Ok(ret) + } } diff --git a/slinky/src/settings.rs b/slinky/src/settings.rs index 71a1f50..f18753a 100644 --- a/slinky/src/settings.rs +++ b/slinky/src/settings.rs @@ -4,10 +4,11 @@ use serde::Deserialize; use std::path::PathBuf; -use crate::linker_symbols_style::LinkerSymbolsStyle; +use crate::{ + absent_nullable::AbsentNullable, linker_symbols_style::LinkerSymbolsStyle, SlinkyError, +}; -#[derive(Deserialize, PartialEq, Debug)] -#[serde(default)] +#[derive(PartialEq, Debug)] pub struct Settings { pub base_path: PathBuf, pub linker_symbols_style: LinkerSymbolsStyle, @@ -16,10 +17,10 @@ pub struct Settings { pub noload_sections: Vec, // Options passed down to each segment - pub use_subalign: bool, - pub subalign: u32, + pub subalign: Option, pub wildcard_sections: bool, + //pub fill_value: Option, } // TODO: consider changing the defaults before 1.0.0 @@ -42,10 +43,60 @@ impl Default for Settings { "COMMON".into(), ], - use_subalign: true, - subalign: 16, + subalign: Some(16), wildcard_sections: true, + // fill_value: Some(Some(0)), } } } + +#[derive(Deserialize, PartialEq, Debug)] +pub(crate) struct SettingsSerial { + #[serde(default)] + pub base_path: AbsentNullable, + #[serde(default)] + pub linker_symbols_style: AbsentNullable, + + #[serde(default)] + pub alloc_sections: AbsentNullable>, + #[serde(default)] + pub noload_sections: AbsentNullable>, + + // Options passed down to each segment + #[serde(default)] + pub subalign: AbsentNullable, + + #[serde(default)] + pub wildcard_sections: AbsentNullable, + //#[serde(default)] + //pub fill_value: AbsentNullable, +} + +impl SettingsSerial { + pub fn unserialize(self) -> Result { + let mut ret = Settings::default(); + + ret.base_path = self.base_path.get_non_null("base_path", || ret.base_path)?; + ret.linker_symbols_style = self + .linker_symbols_style + .get_non_null("linker_symbols_style", || ret.linker_symbols_style)?; + + ret.alloc_sections = self + .alloc_sections + .get_non_null("alloc_sections", || ret.alloc_sections)?; + ret.noload_sections = self + .noload_sections + .get_non_null("noload_sections", || ret.noload_sections)?; + + ret.subalign = self + .subalign + .get_optional_nullable("subalign", || ret.subalign)?; + + ret.wildcard_sections = self + .wildcard_sections + .get_non_null("wildcard_sections", || ret.wildcard_sections)?; + + Ok(ret) + } +} diff --git a/test_case.yaml b/test_case.yaml index 8052fa3..cf769de 100644 --- a/test_case.yaml +++ b/test_case.yaml @@ -1,6 +1,5 @@ settings: base_path: build/us - #linker_symbols_style: makerom segments: - name: header @@ -18,7 +17,7 @@ segments: - { path: src/entry/entry.o } - name: boot - use_subalign: False + subalign: null files: - { path: src/boot/boot_main.o } - { path: src/boot/dmadata.o }