Add capability to get current compiler version (#2016)

This commit is contained in:
T0mstone 2023-10-02 20:28:19 +02:00 committed by GitHub
parent 34ebbaeb10
commit cf9bde3245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 301 additions and 8 deletions

View File

@ -11,7 +11,6 @@ mod watch;
mod world;
use std::cell::Cell;
use std::env;
use std::io::{self, IsTerminal, Write};
use std::process::ExitCode;

View File

@ -1,5 +1,5 @@
use typst::eval::{
Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex,
Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex, Version,
};
use crate::prelude::*;
@ -22,6 +22,7 @@ pub(super) fn define(global: &mut Scope) {
global.define_type::<Regex>();
global.define_type::<Datetime>();
global.define_type::<Duration>();
global.define_type::<Version>();
global.define_type::<Plugin>();
global.define_func::<repr>();
global.define_func::<panic>();

View File

@ -1,6 +1,7 @@
//! Computational functions.
pub mod calc;
pub mod sys;
mod data;
mod foundations;
@ -15,4 +16,5 @@ pub(super) fn define(global: &mut Scope) {
self::foundations::define(global);
self::data::define(global);
self::calc::define(global);
self::sys::define(global);
}

View File

@ -0,0 +1,24 @@
//! System-related things.
use typst::eval::{Module, Scope, Version};
/// Hook up all calculation definitions.
pub(super) fn define(global: &mut Scope) {
global.category("sys");
global.define_module(module());
}
/// A module with system-related things.
fn module() -> Module {
let mut scope = Scope::deduplicating();
scope.category("sys");
scope.define(
"version",
Version::from_iter([
env!("CARGO_PKG_VERSION_MAJOR").parse::<u32>().unwrap(),
env!("CARGO_PKG_VERSION_MINOR").parse::<u32>().unwrap(),
env!("CARGO_PKG_VERSION_PATCH").parse::<u32>().unwrap(),
]),
);
Module::new("sys", scope)
}

View File

@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use super::{
cast, func, ops, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue,
Reflect, Value, Vm,
Reflect, Value, Version, Vm,
};
use crate::diag::{At, SourceResult, StrResult};
use crate::eval::ops::{add, mul};
@ -804,6 +804,7 @@ cast! {
ToArray,
v: Bytes => Self(v.iter().map(|&b| Value::Int(b.into())).collect()),
v: Array => Self(v),
v: Version => Self(v.values().iter().map(|&v| Value::Int(v as i64)).collect())
}
impl Debug for Array {

View File

@ -1,6 +1,7 @@
use ecow::{eco_format, EcoString};
use crate::diag::StrResult;
use crate::eval::Version;
use crate::geom::{Align, Length, Rel, Stroke};
use super::{IntoValue, Type, Value};
@ -16,6 +17,10 @@ pub(crate) fn field(value: &Value, field: &str) -> StrResult<Value> {
// Special cases, such as module and dict, are handled by Value itself
let result = match value {
Value::Version(version) => match version.component(field) {
Ok(i) => i.into_value(),
Err(_) => return missing(),
},
Value::Length(length) => match field {
"em" => length.em.get().into_value(),
"abs" => length.abs.into_value(),
@ -69,7 +74,9 @@ fn missing_field(ty: Type, field: &str) -> EcoString {
/// List the available fields for a type.
pub fn fields_on(ty: Type) -> &'static [&'static str] {
if ty == Type::of::<Length>() {
if ty == Type::of::<Version>() {
&Version::COMPONENTS
} else if ty == Type::of::<Length>() {
&["em", "abs"]
} else if ty == Type::of::<Rel>() {
&["ratio", "length"]

View File

@ -31,6 +31,7 @@ mod scope;
mod symbol;
mod tracer;
mod ty;
mod version;
#[doc(hidden)]
pub use {
@ -65,6 +66,7 @@ pub use self::symbol::{symbols, Symbol};
pub use self::tracer::Tracer;
pub use self::ty::{scope, ty, NativeType, NativeTypeData, Type};
pub use self::value::{Dynamic, Value};
pub use self::version::Version;
use std::collections::HashSet;
use std::mem;

View File

@ -366,6 +366,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
(Fraction(a), Fraction(b)) => a == b,
(Color(a), Color(b)) => a == b,
(Symbol(a), Symbol(b)) => a == b,
(Version(a), Version(b)) => a == b,
(Str(a), Str(b)) => a == b,
(Bytes(a), Bytes(b)) => a == b,
(Label(a), Label(b)) => a == b,
@ -408,6 +409,7 @@ pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> {
(Ratio(a), Ratio(b)) => a.cmp(b),
(Relative(a), Relative(b)) => try_cmp_values(a, b)?,
(Fraction(a), Fraction(b)) => a.cmp(b),
(Version(a), Version(b)) => a.cmp(b),
(Str(a), Str(b)) => a.cmp(b),
// Some technically different things should be comparable.

View File

@ -9,7 +9,7 @@ use unicode_segmentation::UnicodeSegmentation;
use super::{
cast, dict, func, scope, ty, Args, Array, Bytes, Dict, Func, IntoValue, Type, Value,
Vm,
Version, Vm,
};
use crate::diag::{bail, At, SourceResult, StrResult};
use crate::geom::Align;
@ -605,6 +605,7 @@ cast! {
ToStr,
v: i64 => Self::Int(v),
v: f64 => Self::Str(format_str!("{}", v)),
v: Version => Self::Str(format_str!("{}", v)),
v: Bytes => Self::Str(
std::str::from_utf8(&v)
.map_err(|_| "bytes are not valid utf-8")?

View File

@ -14,7 +14,7 @@ use typst::eval::Duration;
use super::{
fields, format_str, ops, Args, Array, AutoValue, Bytes, CastInfo, Content, Dict,
FromValue, Func, IntoValue, Module, NativeType, NoneValue, Plugin, Reflect, Scope,
Str, Symbol, Type,
Str, Symbol, Type, Version,
};
use crate::diag::StrResult;
use crate::eval::Datetime;
@ -50,6 +50,8 @@ pub enum Value {
Color(Color),
/// A symbol: `arrow.l`.
Symbol(Symbol),
/// A version.
Version(Version),
/// A string: `"string"`.
Str(Str),
/// Raw bytes.
@ -122,6 +124,7 @@ impl Value {
Self::Fraction(_) => Type::of::<Fr>(),
Self::Color(_) => Type::of::<Color>(),
Self::Symbol(_) => Type::of::<Symbol>(),
Self::Version(_) => Type::of::<Version>(),
Self::Str(_) => Type::of::<Str>(),
Self::Bytes(_) => Type::of::<Bytes>(),
Self::Label(_) => Type::of::<Label>(),
@ -149,6 +152,7 @@ impl Value {
pub fn field(&self, field: &str) -> StrResult<Value> {
match self {
Self::Symbol(symbol) => symbol.clone().modified(field).map(Self::Symbol),
Self::Version(version) => version.component(field).map(Self::Int),
Self::Dict(dict) => dict.get(field).cloned(),
Self::Content(content) => content.get(field),
Self::Type(ty) => ty.field(field).cloned(),
@ -199,6 +203,7 @@ impl Value {
Self::Int(v) => item!(text)(eco_format!("{v}")),
Self::Float(v) => item!(text)(eco_format!("{v}")),
Self::Str(v) => item!(text)(v.into()),
Self::Version(v) => item!(text)(eco_format!("{v}")),
Self::Symbol(v) => item!(text)(v.get().into()),
Self::Content(v) => v,
Self::Module(module) => module.content(),
@ -231,6 +236,7 @@ impl Debug for Value {
Self::Fraction(v) => Debug::fmt(v, f),
Self::Color(v) => Debug::fmt(v, f),
Self::Symbol(v) => Debug::fmt(v, f),
Self::Version(v) => Debug::fmt(v, f),
Self::Str(v) => Debug::fmt(v, f),
Self::Bytes(v) => Debug::fmt(v, f),
Self::Label(v) => Debug::fmt(v, f),
@ -278,6 +284,7 @@ impl Hash for Value {
Self::Fraction(v) => v.hash(state),
Self::Color(v) => v.hash(state),
Self::Symbol(v) => v.hash(state),
Self::Version(v) => v.hash(state),
Self::Str(v) => v.hash(state),
Self::Bytes(v) => v.hash(state),
Self::Label(v) => v.hash(state),
@ -582,6 +589,7 @@ primitive! { Rel<Length>: "relative length",
primitive! { Fr: "fraction", Fraction }
primitive! { Color: "color", Color }
primitive! { Symbol: "symbol", Symbol }
primitive! { Version: "version", Version }
primitive! {
Str: "string",
Str,

View File

@ -0,0 +1,199 @@
use std::cmp::Ordering;
use std::fmt::{self, Debug, Display, Formatter, Write};
use std::hash::Hash;
use std::iter::repeat;
use ecow::{eco_format, EcoVec};
use super::{cast, func, scope, ty};
use crate::diag::{bail, error, StrResult};
use crate::util::pretty_array_like;
/// A version, with any number of components.
///
/// The list of components is semantically extended by an infinite list of
/// zeros. This means that, for example, `0.8` is the same as `0.8.0`. As a
/// special case, the empty version (that has no components at all) is the same
/// as `0`, `0.0`, `0.0.0`, and so on.
///
/// The first three components have names: `major`, `minor`, `patch`. All
/// components after that do not have names.
#[ty(scope)]
#[derive(Default, Clone, Hash)]
#[allow(clippy::derived_hash_with_manual_eq)]
pub struct Version(EcoVec<u32>);
impl Version {
/// The names for the first components of a version.
pub const COMPONENTS: [&'static str; 3] = ["major", "minor", "patch"];
/// Create a new (empty) version.
pub fn new() -> Self {
Self::default()
}
/// Get a named component of a version.
///
/// Always non-negative. Returns `0` if the version isn't specified to the
/// necessary length.
pub fn component(&self, name: &str) -> StrResult<i64> {
self.0
.iter()
.zip(Self::COMPONENTS)
.find_map(|(&i, s)| (s == name).then_some(i as i64))
.ok_or_else(|| error!("unknown version component"))
}
/// Push a component to the end of this version.
pub fn push(&mut self, component: u32) {
self.0.push(component);
}
/// The values of the version
pub fn values(&self) -> &[u32] {
&self.0
}
}
#[scope]
impl Version {
/// Creates a new version.
///
/// It can have any number of components (even zero).
///
/// ```example
/// #version() \
/// #version(1) \
/// #version(1, 2, 3, 4) \
/// #version((1, 2, 3, 4)) \
/// #version((1, 2), 3)
/// ```
#[func(constructor)]
pub fn construct(
/// The components of the version (array arguments are flattened)
#[variadic]
components: Vec<VersionComponents>,
) -> Version {
let mut version = Version::new();
for c in components {
match c {
VersionComponents::Single(v) => version.push(v),
VersionComponents::Multiple(values) => {
for v in values {
version.push(v);
}
}
}
}
version
}
/// Get a component of a version.
///
/// Always non-negative. Returns `0` if the version isn't specified to the
/// necessary length.
#[func]
pub fn at(
&self,
/// The index at which to retrieve the component. If negative, indexes
/// from the back of the explicitly given components.
index: i64,
) -> StrResult<i64> {
let mut index = index;
if index < 0 {
match (self.0.len() as i64).checked_add(index) {
Some(pos_index) if pos_index >= 0 => index = pos_index,
_ => bail!(
"component index out of bounds (index: {index}, len: {})",
self.0.len()
),
}
}
Ok(usize::try_from(index)
.ok()
.and_then(|i| self.0.get(i).copied())
.unwrap_or_default() as i64)
}
}
impl FromIterator<u32> for Version {
fn from_iter<T: IntoIterator<Item = u32>>(iter: T) -> Self {
Self(EcoVec::from_iter(iter))
}
}
impl IntoIterator for Version {
type Item = u32;
type IntoIter = ecow::vec::IntoIter<u32>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
let max_len = self.0.len().max(other.0.len());
let tail = repeat(&0);
let self_iter = self.0.iter().chain(tail.clone());
let other_iter = other.0.iter().chain(tail);
for (l, r) in self_iter.zip(other_iter).take(max_len) {
match l.cmp(r) {
Ordering::Equal => (),
ord => return ord,
}
}
Ordering::Equal
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for Version {}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
matches!(self.cmp(other), Ordering::Equal)
}
}
impl Display for Version {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let mut first = true;
for &v in &self.0 {
if !first {
f.write_char('.')?;
}
write!(f, "{v}")?;
first = false;
}
Ok(())
}
}
impl Debug for Version {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("version")?;
let parts: Vec<_> = self.0.iter().map(|v| eco_format!("{v}")).collect();
f.write_str(&pretty_array_like(&parts, false))
}
}
/// One or multiple version components.
pub enum VersionComponents {
Single(u32),
Multiple(Vec<u32>),
}
cast! {
VersionComponents,
v: u32 => Self::Single(v),
v: Vec<u32> => Self::Multiple(v)
}

View File

@ -28,5 +28,5 @@
#bytes((a: 1))
---
// Error: 8-15 expected bytes or array, found string
// Error: 8-15 expected bytes, array, or version, found string
#array("hello")

View File

@ -168,7 +168,7 @@
#test(str(10 / 3).len() > 10, true)
---
// Error: 6-8 expected integer, float, bytes, label, type, or string, found content
// Error: 6-8 expected integer, float, version, bytes, label, type, or string, found content
#str([])
---

View File

@ -0,0 +1,47 @@
// Test versions.
// Ref: false
---
// Test version constructor.
// Empty.
#version()
// Plain.
#version(1, 2)
// Single Array argument.
#version((1, 2))
// Mixed arguments.
#version(1, (2, 3), 4, (5, 6), 7)
---
// Test equality of different-length versions
#test(version(), version(0))
#test(version(0), version(0, 0))
#test(version(1, 2), version(1, 2, 0, 0, 0, 0))
---
// Test `version.at`.
// Non-negative index in bounds
#test(version(1, 2).at(1), 2)
// Non-negative index out of bounds
#test(version(1, 2).at(4), 0)
// Negative index in bounds
#test(version(1, 2).at(-2), 1)
// Error: 2-22 component index out of bounds (index: -3, len: 2)
#version(1, 2).at(-3)
---
// Test version fields.
#test(version(1, 2, 3).major, 1)
#test(version(1, 2, 3).minor, 2)
#test(version(1, 2, 3).patch, 3)
---
// Test the type of `sys.version`
#test(type(sys.version), version)