From e3e92b56fbd60ffc0afdb66c150b4172119658ed Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 31 Jul 2019 22:48:02 -0400 Subject: [PATCH] Attempt to sort all the symbols. --- src/canonicalize.rs | 175 ++++++++++++++++++++++++++----------- src/collections.rs | 15 ++++ src/expr.rs | 2 +- src/region.rs | 4 +- tests/helpers/mod.rs | 9 +- tests/test_canonicalize.rs | 114 +++++++++++++++++++----- 6 files changed, 240 insertions(+), 79 deletions(-) diff --git a/src/canonicalize.rs b/src/canonicalize.rs index 3b3e11a0f7..bbd64e95fd 100644 --- a/src/canonicalize.rs +++ b/src/canonicalize.rs @@ -2,7 +2,7 @@ use region::{Located, Region}; use operator::Operator; use operator::Operator::Pizza; use operator::Associativity::*; -use collections::{ImSet, ImMap, MutMap}; +use collections::{ImSortedSet, ImSortedMap, MutMap, MutSortedMap, MutSet}; use std::cmp::Ordering; use expr::{Ident, VariantName}; use expr; @@ -85,7 +85,7 @@ pub enum Pattern { /// A globally unique identifier, used for both vars and variants. /// It will be used directly in code gen. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Symbol(String); impl Symbol { @@ -114,13 +114,13 @@ impl Into for Symbol { #[derive(Clone, Debug, PartialEq)] struct Scope { - pub idents: ImMap, + pub idents: ImSortedMap, symbol_prefix: String, next_unique_id: u64, } impl Scope { - pub fn new(symbol_prefix: String, declared_idents: ImMap) -> Scope { + pub fn new(symbol_prefix: String, declared_idents: ImSortedMap) -> Scope { Scope { symbol_prefix, @@ -146,22 +146,22 @@ impl Scope { #[derive(Clone, Debug, PartialEq)] pub struct Procedure { pub name: Option, - pub closes_over: ImSet, pub is_self_tail_recursive: bool, pub definition: Region, pub args: Vec, - pub body: Expr + pub body: Expr, + pub references: References, } impl Procedure { - pub fn new(definition: Region, closes_over: ImSet, args: Vec, body: Expr) -> Procedure { + pub fn new(definition: Region, args: Vec, body: Expr, references: References) -> Procedure { Procedure { name: None, - closes_over, is_self_tail_recursive: false, definition, args, - body + body, + references, } } } @@ -176,19 +176,19 @@ struct Env { problems: Vec, /// Variants either declared in this module, or imported. - variants: ImMap>, + variants: ImSortedMap>, /// Former closures converted to top-level procedures. - procedures: MutMap, + procedures: MutSortedMap, } impl Env { - pub fn new(home: String, declared_variants: ImMap>) -> Env { + pub fn new(home: String, declared_variants: ImSortedMap>) -> Env { Env { home, variants: declared_variants, problems: Vec::new(), - procedures: MutMap::default(), + procedures: MutSortedMap::default(), } } @@ -199,16 +199,16 @@ impl Env { pub fn register_closure( &mut self, symbol: Symbol, - closes_over: ImSet, args: Vec, body: Expr, - definition: Region + definition: Region, + references: References ) -> () { // We can't if the closure is self tail recursive yet, because it doesn't know its final name yet. // (Assign sets that.) Assume this is false, and let Assign change it to true after it sets final name. let is_self_tail_recursive = false; let name = None; // The Assign logic is also responsible for setting names after the fact. - let procedure = Procedure {closes_over, args, name, body, is_self_tail_recursive, definition}; + let procedure = Procedure {args, name, body, is_self_tail_recursive, definition, references}; self.procedures.insert(symbol, procedure); } @@ -218,9 +218,9 @@ pub fn canonicalize_declaration( home: String, name: &str, loc_expr: Located, - declared_idents: &ImMap, - declared_variants: &ImMap>, -) -> (Located, Output, Vec, MutMap) { + declared_idents: &ImSortedMap, + declared_variants: &ImSortedMap>, +) -> (Located, Output, Vec, MutSortedMap) { // If we're canonicalizing the declaration `foo = ...` inside the `Main` module, // scope_prefix will be "Main$foo$" and its first closure will be named "Main$foo$0" let scope_prefix = format!("{}${}$", home, name); @@ -244,19 +244,24 @@ pub struct Output { pub tail_call: Option, } +/// These are all ordered sets because they end up getting traversed in a graph search +/// to determine how assignments shuold be ordered. We want builds to be reproducible, +/// so it's important that building the same code gives the same order every time! #[derive(Clone, Debug, PartialEq)] pub struct References { - pub locals: ImSet, - pub globals: ImSet, - pub variants: ImSet, + pub locals: ImSortedSet, + pub globals: ImSortedSet, + pub variants: ImSortedSet, + pub calls: ImSortedSet, } impl References { pub fn new() -> References { References { - locals: ImSet::default(), - globals: ImSet::default(), - variants: ImSet::default(), + locals: ImSortedSet::default(), + globals: ImSortedSet::default(), + variants: ImSortedSet::default(), + calls: ImSortedSet::default(), } } @@ -264,6 +269,7 @@ impl References { self.locals = self.locals.union(other.locals); self.globals = self.globals.union(other.globals); self.variants = self.variants.union(other.variants); + self.calls = self.calls.union(other.calls); self } @@ -414,6 +420,9 @@ fn canonicalize( let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) { Ok(symbol) => { + // Record that we did, in fact, call this symbol. + output.references.calls.insert(symbol.clone()); + // CallByName expressions are considered tail calls, // so that their parents in the expression tree will // correctly inherit tail-call-ness from them. @@ -505,13 +514,13 @@ fn canonicalize( // Add the assigned identifiers to scope. If there's a collision, it means there // was shadowing, which will be handled later. - let assigned_idents: ImMap = + let assigned_idents: ImSortedMap = idents_from_patterns(assignments.clone().into_iter().map(|(loc_pattern, _)| loc_pattern), &scope); scope.idents = scope.idents.union(assigned_idents.clone()); let mut refs_by_assignment: MutMap, References)> = MutMap::default(); - let mut can_assignments_by_symbol: MutMap)> = MutMap::default(); + let mut can_assignments_by_symbol: MutSortedMap)> = MutSortedMap::default(); for (loc_pattern, expr) in assignments { // Each assignment gets to have all the idents in scope that are assigned in this @@ -564,6 +573,14 @@ fn canonicalize( // when code elsewhere calls it by assigned name, it'll resolve properly. env.procedures.insert(assigned_symbol.clone(), procedure); + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular assignment errors!) + refs_by_assignment + .entry(assigned_symbol.clone()) + .and_modify(|(_, refs)| { + refs.locals = refs.locals.without(assigned_symbol); + }); + // Return a pointer to the assigned symbol, since the auto-generated one no // longer references any entry in the procedure map! FunctionPointer(assigned_symbol.clone()) @@ -587,7 +604,7 @@ fn canonicalize( let (ret_expr, mut output) = canonicalize(env, &mut scope, *box_loc_returned); // Determine the full set of references by traversing the graph. - let mut visited_symbols = ImSet::default(); + let mut visited_symbols = MutSet::default(); // Start with the return expression's referenced locals. They are the only ones that count! // @@ -602,7 +619,16 @@ fn canonicalize( // we'd erroneously give a warning that `b` was unused since it wasn't directly referenced. for symbol in output.references.locals.clone().into_iter() { // Traverse the graph and look up *all* the references for this local symbol. - let refs = get_all_referenced(symbol, &mut visited_symbols, &refs_by_assignment); + let refs = references_from_local(symbol, &mut visited_symbols, &refs_by_assignment, &env.procedures); + + output.references = output.references.union(refs); + } + + for symbol in output.references.calls.clone().into_iter() { + // Traverse the graph and look up *all* the references for this call. + // Reuse the same visited_symbols as before; if we already visited it, we + // won't learn anything new from visiting it again! + let refs = references_from_call(symbol, &mut visited_symbols, &refs_by_assignment, &env.procedures); output.references = output.references.union(refs); } @@ -621,7 +647,7 @@ fn canonicalize( // This way, during code gen, no assignment will refer to a value that hasn't been initialized yet. // As a bonus, the topological sort also reveals any cycles between the assignments, allowing // us to give a CircularAssignment error. - let successors = |symbol: &Symbol| -> ImSet { + let successors = |symbol: &Symbol| -> ImSortedSet { let (_, references) = refs_by_assignment.get(symbol).unwrap(); references.locals.clone() @@ -635,6 +661,7 @@ fn canonicalize( let can_assignments = sorted_symbols .into_iter() + .rev() // Topological sort gives us the reverse of the sorting we want! .map(|symbol| can_assignments_by_symbol.get(&symbol).unwrap().clone()) .collect(); @@ -675,7 +702,7 @@ fn canonicalize( // Add the arguments' idents to scope.idents. If there's a collision, // it means there was shadowing, which will be handled later. - let arg_idents: ImMap = + let arg_idents: ImSortedMap = idents_from_patterns(loc_arg_patterns.clone().into_iter(), &scope); scope.idents = scope.idents.union(arg_idents.clone()); @@ -704,12 +731,14 @@ fn canonicalize( output.references.locals.remove(&arg_symbol); } - // We only ever need to close over locals. Globals are always available! - // Note: This must happen *after* removing args from locals. Never close over arguments! - let closes_over: ImSet = output.references.locals.clone(); + // We've finished analyzing the closure. Its references.locals are now the values it closes over, + // since we removed the only locals it shouldn't close over (its arguments). + // Register it as a top-level procedure in the Env! + env.register_closure(symbol.clone(), can_args, loc_body_expr.value, region, output.references); - // We've finished analyzing the closure. Register it as a top-level procedure in the Env! - env.register_closure(symbol.clone(), closes_over, can_args, loc_body_expr.value, region); + // Having now registered the closure's references, the function pointer that remains has + // no references. The references we registered will be used only if this symbol gets called! + output.references = References::new(); // Always return a function pointer, in case that's how the closure is being used (e.g. with Apply). // It's possible that Assign will rewrite this. In that case, Assign will need to know the symbol we @@ -739,7 +768,7 @@ fn canonicalize( // Patterns introduce new idents to the scope! // Add the assigned identifiers to scope. If there's a collision, it means there // was shadowing, which will be handled later. - let assigned_idents: ImMap = + let assigned_idents: ImSortedMap = idents_from_patterns(std::iter::once(loc_pattern), &scope); scope.idents = scope.idents.union(assigned_idents.clone()); @@ -798,10 +827,11 @@ fn canonicalize( (Located {region, value: expr}, output) } -fn get_all_referenced( +fn references_from_local( assigned_symbol: Symbol, - visited: &mut ImSet, - refs_by_assignment: &MutMap + visited: &mut MutSet, + refs_by_assignment: &MutMap, + procedures: &MutSortedMap, ) -> References { match refs_by_assignment.get(&assigned_symbol) { Some((_, refs)) => { @@ -809,14 +839,24 @@ fn get_all_referenced( visited.insert(assigned_symbol); - for local in refs.locals.clone() { + for local in refs.locals.iter() { if !visited.contains(&local) { - let other_refs = get_all_referenced(local.clone(), visited, refs_by_assignment); + let other_refs = references_from_local(local.clone(), visited, refs_by_assignment, procedures); answer = answer.union(other_refs); } - answer.locals.insert(local); + answer.locals.insert(local.clone()); + } + + for call in refs.calls.iter() { + if !visited.contains(&call) { + let other_refs = references_from_call(call.clone(), visited, refs_by_assignment, procedures); + + answer = answer.union(other_refs); + } + + answer.calls.insert(call.clone()); } answer @@ -825,10 +865,47 @@ fn get_all_referenced( } } -fn idents_from_patterns(loc_patterns: I, scope: &Scope) -> ImMap +fn references_from_call( + call_symbol: Symbol, + visited: &mut MutSet, + refs_by_assignment: &MutMap, + procedures: &MutSortedMap, +) -> References { + // This shuold be safe to unwrap. All unrecognized call symbols should have been recorded as + // such, and should never have made it into output.references.calls! + let procedure = procedures.get(&call_symbol).unwrap(); + let mut answer = procedure.references.clone(); + + visited.insert(call_symbol); + + for closed_over_local in procedure.references.locals.iter() { + if !visited.contains(&closed_over_local) { + let other_refs = references_from_local(closed_over_local.clone(), visited, refs_by_assignment, procedures); + + answer = answer.union(other_refs); + } + + answer.locals.insert(closed_over_local.clone()); + } + + for call in procedure.references.calls.iter() { + if !visited.contains(&call) { + let other_refs = references_from_call(call.clone(), visited, refs_by_assignment, procedures); + + answer = answer.union(other_refs); + } + + answer.calls.insert(call.clone()); + } + + answer +} + + +fn idents_from_patterns(loc_patterns: I, scope: &Scope) -> ImSortedMap where I: Iterator> { - let mut answer = ImMap::default(); + let mut answer = ImSortedMap::default(); for loc_pattern in loc_patterns { add_idents_from_pattern(loc_pattern, scope, &mut answer); @@ -841,7 +918,7 @@ where I: Iterator> fn add_idents_from_pattern( loc_pattern: Located, scope: &Scope, - answer: &mut ImMap + answer: &mut ImSortedMap ) { use expr::Pattern::*; @@ -863,7 +940,7 @@ fn add_idents_from_pattern( fn remove_idents( pattern: expr::Pattern, - idents: &mut ImMap + idents: &mut ImSortedMap ) { use expr::Pattern::*; @@ -968,7 +1045,7 @@ fn canonicalize_pattern( scope: &mut Scope, pattern_type: &PatternType, loc_pattern: &Located, - shadowable_idents: &mut ImMap, + shadowable_idents: &mut ImSortedMap, ) -> Pattern { use expr::Pattern::*; @@ -1308,4 +1385,4 @@ impl Iterator for Infixes { } } } -} \ No newline at end of file +} diff --git a/src/collections.rs b/src/collections.rs index 34760bb466..fb4b2d8978 100644 --- a/src/collections.rs +++ b/src/collections.rs @@ -19,3 +19,18 @@ pub type ImMap = pub type ImSet = im_rc::hashset::HashSet>; + +// OrdMap equivalents, for naming symmetry. +// Someday we may switch these implementations out. + +pub type MutSortedMap = + std::collections::BTreeMap; + +pub type MutSortedSet = + std::collections::BTreeSet; + +pub type ImSortedMap = + im_rc::ordmap::OrdMap; + +pub type ImSortedSet = + im_rc::ordset::OrdSet; diff --git a/src/expr.rs b/src/expr.rs index f4d49cf879..8c1333230c 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -46,7 +46,7 @@ pub enum VariantName { /// An identifier, possibly fully-qualified with a module name /// e.g. (Http.Request from http) /// Parameterized on a phantom marker for whether it has been canonicalized -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Ident { Unqualified(String), Qualified(String, String), diff --git a/src/region.rs b/src/region.rs index 4aff25fba5..1127c5d6dc 100644 --- a/src/region.rs +++ b/src/region.rs @@ -1,6 +1,6 @@ use std::fmt; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct Region { pub start_line: u32, pub start_col: u32, @@ -9,7 +9,7 @@ pub struct Region { pub end_col: u32, } -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] pub struct Located { pub region: Region, pub value: T, diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index 506ecb4f98..27eee88b57 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -1,7 +1,6 @@ use roc::expr::{Expr, Pattern}; use roc::region::{Located, Region}; -use std::hash::Hash; -use roc::collections::{MutMap}; +use roc::collections::{MutSortedMap}; pub fn loc_box(val: T) -> Box> { Box::new(loc(val)) @@ -74,11 +73,11 @@ pub fn zero_loc_pattern(loc_pattern: Located) -> Located { } #[allow(dead_code)] // For some reason rustc thinks this isn't used. It is, though, in test_canonicalize.rs -pub fn mut_map_from_pairs(pairs: I) -> MutMap +pub fn mut_sorted_map_from_pairs(pairs: I) -> MutSortedMap where I: IntoIterator, - K: Hash + Eq + K: Ord { - let mut answer = MutMap::default(); + let mut answer = MutSortedMap::default(); for (key, value) in pairs { answer.insert(key, value); diff --git a/tests/test_canonicalize.rs b/tests/test_canonicalize.rs index e2b3b4249b..21f4f940e2 100644 --- a/tests/test_canonicalize.rs +++ b/tests/test_canonicalize.rs @@ -11,27 +11,28 @@ mod test_canonicalize { use roc::canonicalize; use roc::canonicalize::{Expr, Output, Problem, Symbol, References, Procedure, Pattern}; use roc::canonicalize::Expr::*; + use roc::canonicalize::Pattern::*; use roc::expr::{Ident}; use roc::expr; use roc::operator::Operator; use roc::region::{Located, Region}; use roc::parse; - use roc::collections::{ImMap, ImSet, MutMap}; + use roc::collections::{ImSortedMap, ImSortedSet, MutSortedMap}; use roc::parse_state::{IndentablePosition}; use combine::{Parser, eof}; use combine::stream::state::{State}; - use helpers::{loc, loc_box, empty_region, zero_loc_expr, mut_map_from_pairs}; + use helpers::{loc, loc_box, empty_region, zero_loc_expr, mut_sorted_map_from_pairs}; - fn can_expr(expr_str: &str) -> (Expr, Output, Vec, MutMap) { - can_expr_with("testDecl", expr_str, &ImMap::default(), &ImMap::default()) + fn can_expr(expr_str: &str) -> (Expr, Output, Vec, MutSortedMap) { + can_expr_with("testDecl", expr_str, &ImSortedMap::default(), &ImSortedMap::default()) } fn can_expr_with( name: &str, expr_str: &str, - declared_idents: &ImMap, - declared_variants: &ImMap>, - ) -> (Expr, Output, Vec, MutMap) { + declared_idents: &ImSortedMap, + declared_variants: &ImSortedMap>, + ) -> (Expr, Output, Vec, MutSortedMap) { let parse_state: State<&str, IndentablePosition> = State::with_positioner(expr_str, IndentablePosition::default()); let expr = match parse::expr().skip(eof()).easy_parse(parse_state) { Ok((expr, state)) => { @@ -70,19 +71,17 @@ mod test_canonicalize { locals: Vec<&'a str>, globals: Vec<&'a str>, variants: Vec<&'a str>, + calls: Vec<&'a str>, tail_call: Option<&'a str> } impl<'a> Into for Out<'a> { fn into(self) -> Output { - fn vec_to_set<'b>(vec: Vec<&'b str>) -> ImSet { - ImSet::from(vec.into_iter().map(sym).collect::>()) - } - let references = References { locals: vec_to_set(self.locals), globals: vec_to_set(self.globals), - variants: vec_to_set(self.variants) + variants: vec_to_set(self.variants), + calls: vec_to_set(self.calls), }; let tail_call = self.tail_call.map(sym); @@ -91,6 +90,10 @@ mod test_canonicalize { } } + fn vec_to_set<'a>(vec: Vec<&'a str>) -> ImSortedSet { + ImSortedSet::from(vec.into_iter().map(sym).collect::>()) + } + // BASIC CANONICALIZATION #[test] @@ -109,14 +112,14 @@ mod test_canonicalize { locals: vec!["func"], globals: vec![], variants: vec![], + calls: vec!["func"], tail_call: None }.into()); assert_eq!(procedures, - mut_map_from_pairs(vec![(sym("func"), + mut_sorted_map_from_pairs(vec![(sym("func"), Procedure { name: Some("func".to_string()), - closes_over: ImSet::default(), is_self_tail_recursive: false, definition: empty_region(), args: vec![Pattern::Identifier(sym("arg"))], @@ -124,7 +127,13 @@ mod test_canonicalize { loc_box(Expr::Var(sym("arg"))), loc(Operator::Plus), loc_box(Expr::Int(1)) - ) + ), + references: References { + locals: vec_to_set(vec![]), + globals: vec_to_set(vec![]), + variants: vec_to_set(vec![]), + calls: vec_to_set(vec![]), + } })]) ); } @@ -149,6 +158,7 @@ mod test_canonicalize { locals: vec!["func", "local"], globals: vec![], variants: vec![], + calls: vec!["func"], tail_call: None }.into()); } @@ -173,11 +183,11 @@ mod test_canonicalize { locals: vec!["local"], globals: vec![], variants: vec![], + calls: vec![], tail_call: None }.into()); } - // UNRECOGNIZED #[test] @@ -198,6 +208,7 @@ mod test_canonicalize { locals: vec![], globals: vec![], variants: vec![], + calls: vec![], tail_call: None }.into()); } @@ -219,6 +230,7 @@ mod test_canonicalize { locals: vec!["a", "b"], globals: vec![], variants: vec![], + calls: vec![], tail_call: None }.into()); } @@ -226,11 +238,12 @@ mod test_canonicalize { // UNUSED #[test] - fn mutual_unused_closed_over_vars() { + fn mutual_unused_circular_vars() { // This should report that both a and b are unused, since the return expr never references them. + // It should not report them as circular, since we haven't solved the halting problem here. let (_, output, problems, _) = can_expr(indoc!(r#" - a = \_ -> b 7 - b = \_ -> a 6 + a = \arg -> if arg > 0 then b 7 else 0 + b = \arg -> if arg > 0 then a (arg - 1) else 0 c = 5 c @@ -242,10 +255,9 @@ mod test_canonicalize { locals: vec!["c"], globals: vec![], variants: vec![], + calls: vec![], tail_call: None }.into()); - - panic!("TODO this shuoldn't report circular assignment problems; we haven't solved the halting problem here!"); } #[test] @@ -266,6 +278,7 @@ mod test_canonicalize { locals: vec!["fibonacci"], globals: vec![], variants: vec![], + calls: vec!["fibonacci"], tail_call: Some("fibonacci") }.into()); } @@ -289,6 +302,7 @@ mod test_canonicalize { locals: vec!["func", "x", "y", "z"], globals: vec![], variants: vec![], + calls: vec!["func"], tail_call: None }.into()); @@ -304,7 +318,59 @@ mod test_canonicalize { "#)).0); } - // TODO test reordering where closed-over values are part of the dependency chain + #[test] + fn reorder_closed_over_assignments() { + let (expr, output, problems, _) = can_expr(indoc!(r#" + z = func1 x + x = 9 + y = func2 3 + func1 = \arg -> func2 arg + y + func2 = \arg -> arg + x + + z + "#)); + + assert_eq!(problems, vec![]); + + assert_eq!(output, Out { + locals: vec!["func1", "func2", "x", "y", "z"], + globals: vec![], + variants: vec![], + calls: vec!["func1", "func2"], + tail_call: None + }.into()); + + // This should get reordered to the following, so that in code gen + // everything will have been set before it gets read. + // (The order of the function definitions doesn't matter.) + assert_assignment_order(expr, + vec!["func1", "x", "z", "func2", "y"], + ); + } + + fn assert_assignment_order(expr: Expr, expected_strings: Vec<&str>) { + match expr { + Assign(assignments, _) => { + let expected_symbols: Vec = expected_strings.into_iter().map(sym).collect(); + let actual_symbols: Vec = assignments.into_iter().map(|(pattern, _)| { + match pattern { + Identifier(symbol) => { + symbol + }, + _ => { + panic!("Called assert_assignment_order passing an Assign expr with non-Identifier patterns!"); + } + } + }).collect(); + + assert_eq!(actual_symbols, expected_symbols); + } + _ => { + panic!("Called assert_assignment_order passing a non-Assign expr!"); + } + } + } + // CIRCULAR ASSIGNMENT @@ -329,6 +395,10 @@ mod test_canonicalize { panic!("TODO strongly_connected_component doesn't sort these, but we want them sorted!"); } + + // TODO verify that Apply handles output.references.calls correctly + + // UNSUPPORTED PATTERNS // TODO verify that in closures and assignments, you can't assign to int/string/underscore/etc