Add a bunch more IR stuff and anonymous functions.
This commit is contained in:
parent
781832eb7d
commit
bd2177940c
10 changed files with 444 additions and 80 deletions
|
@ -1,4 +1,6 @@
|
|||
use crate::error::CompileError;
|
||||
use crate::function::{Clause, Function, FunctionIdx};
|
||||
use crate::ir::IR;
|
||||
use crate::location::Location;
|
||||
use crate::stable::{StringIdx, StringTable};
|
||||
use crate::ty::{Ty, TyIdx};
|
||||
|
@ -9,20 +11,23 @@ pub struct Context {
|
|||
errors: Vec<CompileError>,
|
||||
strings: StringTable,
|
||||
types: Vec<Ty>,
|
||||
functions: Vec<Function>,
|
||||
current_file: StringIdx,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(path: &str) -> Context {
|
||||
let errors = Vec::default();
|
||||
let mut strings = StringTable::default();
|
||||
let types = Vec::default();
|
||||
let current_file = strings.intern(path);
|
||||
let errors = Vec::default();
|
||||
let functions = Vec::default();
|
||||
let types = Vec::default();
|
||||
Context {
|
||||
current_file,
|
||||
errors,
|
||||
functions,
|
||||
strings,
|
||||
types,
|
||||
current_file,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,20 +95,37 @@ impl Context {
|
|||
idx.into()
|
||||
}
|
||||
|
||||
pub fn unknown_type(&mut self, location: Location) -> TyIdx {
|
||||
let idx = self.types.len();
|
||||
let ty = Ty::unknown_type(location);
|
||||
self.types.push(ty);
|
||||
idx.into()
|
||||
}
|
||||
|
||||
pub fn get_type(&self, idx: &TyIdx) -> Option<&Ty> {
|
||||
self.types.get(usize::from(idx))
|
||||
}
|
||||
|
||||
pub fn location(&self, location: &InputLocation) -> Location {
|
||||
let path = self.strings.get(self.current_file.clone()).unwrap();
|
||||
Location::new(location.clone(), path)
|
||||
}
|
||||
|
||||
pub fn define_function(&mut self, function: Function) -> FunctionIdx {
|
||||
let idx = self.functions.len();
|
||||
self.functions.push(function);
|
||||
idx.into()
|
||||
}
|
||||
|
||||
pub fn get_function(&self, idx: FunctionIdx) -> Option<&Function> {
|
||||
self.functions.get(usize::from(idx))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn find_type(&mut self, name: &str) -> Option<&Ty> {
|
||||
let name = self.constant_string(name);
|
||||
self.types.iter().find(|ty| ty.name() == Some(name.clone()))
|
||||
}
|
||||
|
||||
pub fn location(&self, location: &InputLocation) -> Location {
|
||||
let path = self.strings.get(self.current_file.clone()).unwrap();
|
||||
Location::new(location.clone(), path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
83
huia-compiler/src/function.rs
Normal file
83
huia-compiler/src/function.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use crate::ir::IR;
|
||||
use crate::stable::StringIdx;
|
||||
use crate::ty::TyIdx;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Function {
|
||||
clauses: Vec<Clause>,
|
||||
return_type: TyIdx,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn new(return_type: TyIdx) -> Function {
|
||||
Function {
|
||||
return_type,
|
||||
clauses: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, clause: Clause) {
|
||||
self.clauses.push(clause);
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.clauses.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Clause {
|
||||
arguments: Vec<(StringIdx, TyIdx)>,
|
||||
body: Vec<IR>,
|
||||
}
|
||||
|
||||
impl Clause {
|
||||
pub fn new(arguments: Vec<(StringIdx, TyIdx)>) -> Clause {
|
||||
Clause {
|
||||
arguments,
|
||||
body: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, ir: IR) {
|
||||
self.body.push(ir);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionIdx(usize);
|
||||
|
||||
impl From<usize> for FunctionIdx {
|
||||
fn from(i: usize) -> FunctionIdx {
|
||||
FunctionIdx(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FunctionIdx> for usize {
|
||||
fn from(i: FunctionIdx) -> usize {
|
||||
i.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FunctionIdx> for usize {
|
||||
fn from(i: &FunctionIdx) -> usize {
|
||||
i.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_function_new() {
|
||||
let rt = TyIdx::from(13);
|
||||
let mut fun = Function::new(rt);
|
||||
assert!(fun.is_empty());
|
||||
|
||||
let clause = Clause::new(Vec::new());
|
||||
fun.push(clause);
|
||||
|
||||
assert!(!fun.is_empty());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
use crate::context::Context;
|
||||
use crate::function::{Clause, Function};
|
||||
use crate::ir::{Val, IR};
|
||||
use crate::stable::StringIdx;
|
||||
use crate::ty::TyIdx;
|
||||
use huia_parser::ast::{Location, NodeType, Term, Value};
|
||||
|
||||
|
@ -14,6 +16,10 @@ struct Builder {
|
|||
|
||||
/// Type stack - used to track which type is currently being defined.
|
||||
ty_stack: Vec<TyIdx>,
|
||||
|
||||
/// Function stack - used to track which function is currently being defined,
|
||||
fn_stack: Vec<Function>,
|
||||
clause_stack: Vec<Clause>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
|
@ -232,6 +238,55 @@ impl Builder {
|
|||
|
||||
self.pop_ty();
|
||||
}
|
||||
NodeType::Function => {
|
||||
let function = node.function().unwrap();
|
||||
let location = context.location(&node.location());
|
||||
let return_type = context.unknown_type(location);
|
||||
let mut fun = Function::new(return_type);
|
||||
|
||||
for clause in function.value_ref() {
|
||||
let arguments: Vec<(StringIdx, TyIdx)> = clause
|
||||
.arguments()
|
||||
.iter()
|
||||
.map(|(name, type_spec)| {
|
||||
let name = context.constant_string(name.value_ref().as_str());
|
||||
let location = context.location(&type_spec.location());
|
||||
let types = type_spec
|
||||
.value_ref()
|
||||
.iter()
|
||||
.map(|node| {
|
||||
let name = context.constant_string(node.value_ref().as_str());
|
||||
let location = context.location(&node.location());
|
||||
context.reference_type(&name, location)
|
||||
})
|
||||
.collect();
|
||||
let ty = context.anonymous_trait(types, location);
|
||||
(name, ty)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// FIXME: We need to put the built nodes into a scope
|
||||
// pointing at the current clause rather than just stuffing
|
||||
// them into the stack where they're just sitting there
|
||||
// doing no good to anyone.
|
||||
self.push_clause(Clause::new(arguments));
|
||||
|
||||
for node in clause.body() {
|
||||
self.build(node.clone(), &mut context);
|
||||
}
|
||||
|
||||
fun.push(self.pop_clause().unwrap());
|
||||
}
|
||||
|
||||
let idx = context.define_function(fun);
|
||||
self.push_ir(IR::Function(idx));
|
||||
}
|
||||
// NodeType::PublicMethod => {
|
||||
// let (name, args, typespec, body) = node.public_method().unwrap();
|
||||
|
||||
// let name = context.constant_string(name.value_ref().as_str());
|
||||
// let location = context.location(&node.location());
|
||||
// }
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -256,6 +311,27 @@ impl Builder {
|
|||
let len = self.ty_stack.len();
|
||||
self.ty_stack.get(len - 1)
|
||||
}
|
||||
|
||||
pub fn push_fn(&mut self, fun: Function) {
|
||||
self.fn_stack.push(fun);
|
||||
}
|
||||
|
||||
pub fn pop_fn(&mut self) -> Option<Function> {
|
||||
self.fn_stack.pop()
|
||||
}
|
||||
|
||||
pub fn push_clause(&mut self, clause: Clause) {
|
||||
self.clause_stack.push(clause);
|
||||
}
|
||||
|
||||
pub fn peek_clause_mut(&mut self) -> Option<&mut Clause> {
|
||||
let len = self.clause_stack.len();
|
||||
self.clause_stack.get_mut(len - 1)
|
||||
}
|
||||
|
||||
pub fn pop_clause(&mut self) -> Option<Clause> {
|
||||
self.clause_stack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -435,4 +511,18 @@ mod test {
|
|||
assert!(context.find_type("TimeMachine").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anonymous_function() {
|
||||
let term = Term::input(r#"fn (speed: Integer) do "WAT" end"#).unwrap()[0].clone();
|
||||
let mut builder = Builder::default();
|
||||
let mut context = Context::test();
|
||||
builder.build(term, &mut context);
|
||||
let ir = builder.pop_ir().unwrap();
|
||||
assert!(ir.is_function());
|
||||
|
||||
let fun = context
|
||||
.get_function(ir.function().unwrap().clone())
|
||||
.unwrap();
|
||||
assert!(!fun.is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
mod builder;
|
||||
|
||||
use crate::function::FunctionIdx;
|
||||
use crate::stable::StringIdx;
|
||||
use crate::ty::TyIdx;
|
||||
use huia_parser::ast::binary::Operator as BinOp;
|
||||
|
@ -18,6 +19,7 @@ pub enum IR {
|
|||
Unary(UnOp, Box<IR>),
|
||||
Call(StringIdx, Vec<IR>),
|
||||
Declaration(StringIdx, Box<IR>),
|
||||
Function(FunctionIdx),
|
||||
}
|
||||
|
||||
impl IR {
|
||||
|
@ -69,6 +71,20 @@ impl IR {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_function(&self) -> bool {
|
||||
match self {
|
||||
IR::Function(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn function(&self) -> Option<&FunctionIdx> {
|
||||
match self {
|
||||
IR::Function(ref idx) => Some(idx),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A constant value.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
mod context;
|
||||
mod error;
|
||||
mod function;
|
||||
mod ir;
|
||||
mod location;
|
||||
mod stable;
|
||||
|
|
|
@ -56,6 +56,15 @@ impl Ty {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn unknown_type(location: Location) -> Ty {
|
||||
Ty {
|
||||
name: None,
|
||||
location,
|
||||
kind: TyKind::Unresolved,
|
||||
inner: TyInner::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<StringIdx> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
|
95
huia-parser/src/ast/function.rs
Normal file
95
huia-parser/src/ast/function.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use crate::ast::{Identifier, Location, Term, TypeSpec, Value};
|
||||
use crate::grammar::Rule;
|
||||
use crate::input_location::InputLocation;
|
||||
use pest::iterators::Pair;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Function {
|
||||
clauses: Vec<Clause>,
|
||||
location: InputLocation,
|
||||
}
|
||||
|
||||
impl Value for Function {
|
||||
type Item = Vec<Clause>;
|
||||
|
||||
fn value(self) -> Self::Item {
|
||||
self.clauses
|
||||
}
|
||||
|
||||
fn value_ref(&self) -> &Self::Item {
|
||||
&self.clauses
|
||||
}
|
||||
}
|
||||
|
||||
impl Location for Function {
|
||||
fn location(&self) -> &InputLocation {
|
||||
&self.location
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Pair<'a, Rule>> for Function {
|
||||
fn from(pair: Pair<'a, Rule>) -> Self {
|
||||
match pair.as_rule() {
|
||||
Rule::function => {
|
||||
let clauses = pair.clone().into_inner().map(|p| Clause::from(p)).collect();
|
||||
let location = InputLocation::from(pair.into_span());
|
||||
Function { clauses, location }
|
||||
}
|
||||
_ => unreachable!("Expected pair to be a Function"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Clause {
|
||||
arguments: Vec<(Identifier, TypeSpec)>,
|
||||
body: Vec<Term>,
|
||||
location: InputLocation,
|
||||
}
|
||||
|
||||
impl Clause {
|
||||
pub fn arguments(&self) -> &Vec<(Identifier, TypeSpec)> {
|
||||
&self.arguments
|
||||
}
|
||||
|
||||
pub fn body(&self) -> &Vec<Term> {
|
||||
&self.body
|
||||
}
|
||||
}
|
||||
|
||||
impl Location for Clause {
|
||||
fn location(&self) -> &InputLocation {
|
||||
&self.location
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Pair<'a, Rule>> for Clause {
|
||||
fn from(pair: Pair<'a, Rule>) -> Self {
|
||||
match pair.as_rule() {
|
||||
Rule::function_clause => {
|
||||
println!("clause = {:#?}", pair);
|
||||
let mut inner = pair.clone().into_inner();
|
||||
let arguments = inner
|
||||
.next()
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.map(|p| {
|
||||
println!("p = {:?}", p);
|
||||
let mut inner = p.into_inner();
|
||||
let keyword = Identifier::from(inner.next().unwrap());
|
||||
let typespec = TypeSpec::from(inner.next().unwrap());
|
||||
(keyword, typespec)
|
||||
})
|
||||
.collect();
|
||||
let body = inner.next().unwrap().into_inner().map(Term::from).collect();
|
||||
let location = InputLocation::from(pair);
|
||||
Clause {
|
||||
arguments,
|
||||
body,
|
||||
location,
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Expected pair to be a Function Clause"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ mod atom;
|
|||
pub mod binary;
|
||||
mod boolean;
|
||||
mod float;
|
||||
mod function;
|
||||
mod identifier;
|
||||
mod integer;
|
||||
mod local;
|
||||
|
@ -15,6 +16,7 @@ pub use atom::Atom;
|
|||
pub use binary::Binary;
|
||||
pub use boolean::Boolean;
|
||||
pub use float::Float;
|
||||
pub use function::Function;
|
||||
pub use identifier::Identifier;
|
||||
pub use integer::Integer;
|
||||
pub use local::Local;
|
||||
|
|
|
@ -38,6 +38,7 @@ pub enum NodeType {
|
|||
Call,
|
||||
Declaration,
|
||||
Local,
|
||||
Function,
|
||||
|
||||
TypeDef,
|
||||
TraitDef,
|
||||
|
@ -54,24 +55,21 @@ enum Inner {
|
|||
Binary(Binary, Box<Term>, Box<Term>),
|
||||
Boolean(Boolean),
|
||||
Call(Identifier, Vec<Term>),
|
||||
Ty(Ty),
|
||||
Constructor(Ty, Vec<(Identifier, Term)>),
|
||||
Declaration(Identifier, Box<Term>),
|
||||
Float(Float),
|
||||
Function(Function),
|
||||
ImplDef(Ty, Vec<Term>),
|
||||
Integer(Integer),
|
||||
Local(Local),
|
||||
Map(Vec<(Term, Term)>),
|
||||
String(String),
|
||||
TypeDef(Ty, Vec<(Identifier, TypeSpec)>, Vec<Term>),
|
||||
TraitDef(Ty, Option<TypeSpec>, Vec<Term>),
|
||||
ImplDef(Ty, Vec<Term>),
|
||||
PublicMethod(
|
||||
PrivateMethod(
|
||||
Identifier,
|
||||
Vec<(Identifier, TypeSpec)>,
|
||||
Option<TypeSpec>,
|
||||
Vec<Term>,
|
||||
),
|
||||
PrivateMethod(
|
||||
PublicMethod(
|
||||
Identifier,
|
||||
Vec<(Identifier, TypeSpec)>,
|
||||
Option<TypeSpec>,
|
||||
|
@ -83,6 +81,10 @@ enum Inner {
|
|||
Option<TypeSpec>,
|
||||
Vec<Term>,
|
||||
),
|
||||
String(String),
|
||||
TraitDef(Ty, Option<TypeSpec>, Vec<Term>),
|
||||
Ty(Ty),
|
||||
TypeDef(Ty, Vec<(Identifier, TypeSpec)>, Vec<Term>),
|
||||
Unary(Unary, Box<Term>),
|
||||
}
|
||||
|
||||
|
@ -124,23 +126,24 @@ impl Term {
|
|||
|
||||
pub fn node_type(&self) -> NodeType {
|
||||
match self.inner {
|
||||
Inner::Array(_) => NodeType::Array,
|
||||
Inner::Atom(_) => NodeType::Atom,
|
||||
Inner::Boolean(_) => NodeType::Boolean,
|
||||
Inner::Binary(_, _, _) => NodeType::Binary,
|
||||
Inner::Call(_, _) => NodeType::Call,
|
||||
Inner::Ty(_) => NodeType::Ty,
|
||||
Inner::Constructor(_, _) => NodeType::Constructor,
|
||||
Inner::Declaration(_, _) => NodeType::Declaration,
|
||||
Inner::Float(_) => NodeType::Float,
|
||||
Inner::Integer(_) => NodeType::Integer,
|
||||
Inner::Local(_) => NodeType::Local,
|
||||
Inner::Map(_) => NodeType::Map,
|
||||
Inner::String(_) => NodeType::String,
|
||||
Inner::Unary(_, _) => NodeType::Unary,
|
||||
Inner::TypeDef(_, _, _) => NodeType::TypeDef,
|
||||
Inner::TraitDef(_, _, _) => NodeType::TraitDef,
|
||||
Inner::ImplDef(_, _) => NodeType::ImplDef,
|
||||
Inner::Array(..) => NodeType::Array,
|
||||
Inner::Atom(..) => NodeType::Atom,
|
||||
Inner::Boolean(..) => NodeType::Boolean,
|
||||
Inner::Binary(..) => NodeType::Binary,
|
||||
Inner::Call(..) => NodeType::Call,
|
||||
Inner::Ty(..) => NodeType::Ty,
|
||||
Inner::Constructor(..) => NodeType::Constructor,
|
||||
Inner::Declaration(..) => NodeType::Declaration,
|
||||
Inner::Function(..) => NodeType::Function,
|
||||
Inner::Float(..) => NodeType::Float,
|
||||
Inner::Integer(..) => NodeType::Integer,
|
||||
Inner::Local(..) => NodeType::Local,
|
||||
Inner::Map(..) => NodeType::Map,
|
||||
Inner::String(..) => NodeType::String,
|
||||
Inner::Unary(..) => NodeType::Unary,
|
||||
Inner::TypeDef(..) => NodeType::TypeDef,
|
||||
Inner::TraitDef(..) => NodeType::TraitDef,
|
||||
Inner::ImplDef(..) => NodeType::ImplDef,
|
||||
Inner::PublicMethod(_, _, _, _) => NodeType::PublicMethod,
|
||||
Inner::PrivateMethod(_, _, _, _) => NodeType::PrivateMethod,
|
||||
Inner::StaticMethod(_, _, _, _) => NodeType::StaticMethod,
|
||||
|
@ -217,6 +220,13 @@ impl Term {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn function(&self) -> Option<&Function> {
|
||||
match self.inner {
|
||||
Inner::Function(ref function) => Some(function),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn public_method(
|
||||
&self,
|
||||
) -> Option<(
|
||||
|
@ -402,6 +412,10 @@ impl<'a> From<Pair<'a, Rule>> for Term {
|
|||
location: InputLocation::from(pair.clone()),
|
||||
inner: Inner::Float(Float::from(pair)),
|
||||
},
|
||||
Rule::function => Term {
|
||||
location: InputLocation::from(pair.clone()),
|
||||
inner: Inner::Function(Function::from(pair)),
|
||||
},
|
||||
Rule::infix => precedence::climb(pair),
|
||||
Rule::integer => Term {
|
||||
location: InputLocation::from(pair.clone()),
|
||||
|
@ -977,6 +991,33 @@ mod test {
|
|||
assert_eq!(rhs2.integer().unwrap().value_ref(), &4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anonymous_function() {
|
||||
let terms = Term::input(
|
||||
r#"
|
||||
fn (speed: Integer) do
|
||||
"WAT"
|
||||
end,
|
||||
(speed: Float) do
|
||||
"WAT"
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let function = terms[0].function().unwrap();
|
||||
let clauses = function.value_ref();
|
||||
assert_eq!(clauses.len(), 2);
|
||||
|
||||
let clause0 = &clauses[0];
|
||||
let clause1 = &clauses[1];
|
||||
|
||||
assert_eq!(clause0.arguments().len(), 1);
|
||||
assert_eq!(clause0.body().len(), 1);
|
||||
assert_eq!(clause1.arguments().len(), 1);
|
||||
assert_eq!(clause1.body().len(), 1);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_acceptance() {
|
||||
// let terms = Term::input(
|
||||
|
|
|
@ -7,7 +7,7 @@ reserved = { "end" | "let" | "true" | "false" }
|
|||
|
||||
expression = _{ infix | expression_inner }
|
||||
infix = { expression_inner ~ (binary_operator ~ expression_inner)+ }
|
||||
expression_inner = _{ call | declaration | unary | literal | local | braced_expression }
|
||||
expression_inner = _{ function | call | declaration | unary | literal | local | braced_expression }
|
||||
braced_expression = _{ "(" ~ expression ~ ")" }
|
||||
|
||||
declaration = { "let" ~ ident ~ assign ~ expression }
|
||||
|
@ -64,6 +64,11 @@ atom = @{ ":" ~ ident }
|
|||
typename = @{ typename_name ~ ("." ~ typename_name)* }
|
||||
typename_name = @{ 'A'..'Z' ~ ('a'..'z' | 'A'..'Z' | "_")* }
|
||||
|
||||
function = { "fn" ~ function_clause ~ ("," ~ function_clause)* }
|
||||
function_args = { defargs }
|
||||
function_clause = { function_args ~ function_block }
|
||||
function_block = { "do" ~ expression* ~ "end" }
|
||||
|
||||
float = ${ float_characteristic ~ "." ~ float_mantissa }
|
||||
float_characteristic = { "0" | (('1'..'9') ~ ('0'..'9' | "_")*) }
|
||||
float_mantissa = { ('0'..'9')+ }
|
||||
|
|
Reference in a new issue