/* Copyright (c) 2014 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura */ #include #include "util/sstream.h" #include "util/sexpr/option_declarations.h" #include "kernel/type_checker.h" #include "library/io_state_stream.h" #include "library/scoped_ext.h" #include "library/aliases.h" #include "library/private.h" #include "library/locals.h" #include "frontends/lean/parser.h" #include "frontends/lean/calc.h" #include "frontends/lean/notation_cmd.h" namespace lean { static name g_raw("raw"); static name g_llevel_curly(".{"); static name g_rcurly("}"); static name g_colon(":"); static name g_assign(":="); static name g_private("[private]"); static name g_inline("[inline]"); static name g_true("true"); static name g_false("false"); static name g_options("options"); static name g_lparen("("); static name g_rparen(")"); static name g_arrow("->"); static name g_lbracket("["); static name g_rbracket("]"); static name g_declarations("declarations"); static name g_decls("decls"); static name g_hiding("hiding"); static name g_renaming("renaming"); static void check_atomic(name const & n) { if (!n.is_atomic()) throw exception(sstream() << "invalid declaration name '" << n << "', identifier must be atomic"); } environment universe_cmd(parser & p) { name n = p.check_id_next("invalid universe declaration, identifier expected"); check_atomic(n); environment env = p.env(); if (in_section(env)) { p.add_local_level(n, mk_param_univ(n)); } else { name const & ns = get_namespace(env); name full_n = ns + n; env = module::add_universe(env, full_n); if (!ns.is_anonymous()) env = add_alias(env, n, mk_global_univ(full_n)); } return env; } static void check_in_section(parser const & p) { if (!in_section(p.env())) throw exception(sstream() << "invalid command, it must be used in a section"); } static void parse_univ_params(parser & p, buffer & ps) { if (p.curr_is_token(g_llevel_curly)) { p.next(); while (!p.curr_is_token(g_rcurly)) { name l = p.check_id_next("invalid universe parameter, identifier expected"); p.add_local_level(l, mk_param_univ(l)); ps.push_back(l); } p.next(); } } static void update_parameters(buffer & ls_buffer, name_set const & found, parser const & p) { unsigned old_sz = ls_buffer.size(); found.for_each([&](name const & n) { if (std::find(ls_buffer.begin(), ls_buffer.begin() + old_sz, n) == ls_buffer.begin() + old_sz) ls_buffer.push_back(n); }); std::sort(ls_buffer.begin(), ls_buffer.end(), [&](name const & n1, name const & n2) { return p.get_local_level_index(n1) < p.get_local_level_index(n2); }); } static environment declare_var(parser & p, environment env, name const & n, level_param_names const & ls, expr const & type, bool is_axiom, binder_info const & bi, pos_info const & pos) { if (in_section(p.env())) { p.add_local_expr(n, p.save_pos(mk_local(n, type), pos), bi); return env; } else { name const & ns = get_namespace(env); name full_n = ns + n; if (is_axiom) env = module::add(env, check(env, mk_axiom(full_n, ls, type))); else env = module::add(env, check(env, mk_var_decl(full_n, ls, type))); if (!ns.is_anonymous()) env = add_alias(env, n, mk_constant(full_n)); return env; } } static environment variable_cmd_core(parser & p, bool is_axiom, binder_info const & bi) { auto pos = p.pos(); name n = p.check_id_next("invalid declaration, identifier expected"); check_atomic(n); buffer ls_buffer; if (p.curr_is_token(g_llevel_curly) && in_section(p.env())) throw parser_error("invalid declaration, axioms/parameters occurring in sections cannot be universe polymorphic", p.pos()); optional scope1; if (!in_section(p.env())) scope1.emplace(p); parse_univ_params(p, ls_buffer); parser::param_universe_scope scope2(p); expr type; if (!p.curr_is_token(g_colon)) { buffer ps; auto lenv = p.parse_binders(ps); p.check_token_next(g_colon, "invalid declaration, ':' expected"); type = p.parse_scoped_expr(ps, lenv); type = p.pi_abstract(ps, type); } else { p.next(); type = p.parse_expr(); } level_param_names ls; if (in_section(p.env())) { ls = to_level_param_names(collect_univ_params(type)); } else { update_parameters(ls_buffer, collect_univ_params(type), p); ls = to_list(ls_buffer.begin(), ls_buffer.end()); } type = p.elaborate(type, ls); return declare_var(p, p.env(), n, ls, type, is_axiom, bi, pos); } environment variable_cmd(parser & p) { return variable_cmd_core(p, false, binder_info()); } environment axiom_cmd(parser & p) { return variable_cmd_core(p, true, binder_info()); } environment implicit_variable_cmd(parser & p) { check_in_section(p); return variable_cmd_core(p, false, mk_implicit_binder_info()); } environment implicit_axiom_cmd(parser & p) { check_in_section(p); return variable_cmd_core(p, true, mk_implicit_binder_info()); } environment cast_variable_cmd(parser & p) { check_in_section(p); return variable_cmd_core(p, false, mk_cast_binder_info()); } static environment variables_cmd_core(parser & p, bool is_axiom, binder_info const & bi) { auto pos = p.pos(); buffer ids; while (!p.curr_is_token(g_colon)) { name id = p.check_id_next("invalid parameters declaration, identifier expected"); check_atomic(id); ids.push_back(id); } p.next(); optional scope1; if (!in_section(p.env())) scope1.emplace(p); parser::param_universe_scope scope2(p); expr type = p.parse_expr(); level_param_names ls = to_level_param_names(collect_univ_params(type)); type = p.elaborate(type, ls); environment env = p.env(); for (auto id : ids) env = declare_var(p, env, id, ls, type, is_axiom, bi, pos); return env; } environment variables_cmd(parser & p) { return variables_cmd_core(p, false, binder_info()); } environment implicit_variables_cmd(parser & p) { check_in_section(p); return variables_cmd_core(p, false, mk_implicit_binder_info()); } environment cast_variables_cmd(parser & p) { check_in_section(p); return variables_cmd_core(p, false, mk_cast_binder_info()); } // Collect local (section) constants occurring in type and value, sort them, and store in section_ps static void collect_section_locals(expr const & type, expr const & value, parser const & p, buffer & section_ps) { name_set ls = collect_locals(type, collect_locals(value)); ls.for_each([&](name const & n) { section_ps.push_back(*p.get_local(n)); }); std::sort(section_ps.begin(), section_ps.end(), [&](parameter const & p1, parameter const & p2) { return p.get_local_index(mlocal_name(p1.m_local)) < p.get_local_index(mlocal_name(p2.m_local)); }); } static void parse_modifiers(parser & p, bool & is_private, bool & is_opaque) { while (true) { if (p.curr_is_token(g_private)) { is_private = true; p.next(); } else if (p.curr_is_token(g_inline)) { is_opaque = false; p.next(); } else { break; } } } // Wrap \c e with the "explicit macro", the idea is to inform the elaborator // preprocessor, that we do not need create metavariables for implicit arguments static expr mark_explicit(expr const & e) { // TODO(Leo) return e; } environment definition_cmd_core(parser & p, bool is_theorem, bool is_opaque) { bool is_private = false; parse_modifiers(p, is_private, is_opaque); if (is_theorem && !is_opaque) throw exception("invalid theorem declaration, theorems cannot be transparent"); name n = p.check_id_next("invalid declaration, identifier expected"); check_atomic(n); environment env = p.env(); name real_n; // real name for this declaration if (is_private) { auto env_n = add_private_name(env, n, optional(hash(p.pos().first, p.pos().second))); env = env_n.first; real_n = env_n.second; } else { name const & ns = get_namespace(env); real_n = ns + n; } buffer ls_buffer; expr type, value; level_param_names ls; { parser::local_scope scope1(p); parse_univ_params(p, ls_buffer); if (!p.curr_is_token(g_colon)) { buffer ps; optional lenv; { parser::param_universe_scope scope2(p); lenv = p.parse_binders(ps); p.check_token_next(g_colon, "invalid declaration, ':' expected"); type = p.parse_scoped_expr(ps, *lenv); } p.check_token_next(g_assign, "invalid declaration, ':=' expected"); value = p.parse_scoped_expr(ps, *lenv); type = p.pi_abstract(ps, type); value = p.lambda_abstract(ps, value); } else { p.next(); { parser::param_universe_scope scope2(p); type = p.parse_expr(); } p.check_token_next(g_assign, "invalid declaration, ':=' expected"); value = p.parse_expr(); } update_parameters(ls_buffer, collect_univ_params(value, collect_univ_params(type)), p); ls = to_list(ls_buffer.begin(), ls_buffer.end()); } if (in_section(env)) { buffer section_ps; collect_section_locals(type, value, p, section_ps); type = p.pi_abstract(section_ps, type); value = p.lambda_abstract(section_ps, value); buffer section_ls_buffer; for (name const & l : ls) { if (p.get_local_level_index(l)) section_ls_buffer.push_back(mk_param_univ(l)); else break; } levels section_ls = to_list(section_ls_buffer.begin(), section_ls_buffer.end()); buffer section_args; for (auto const & p : section_ps) section_args.push_back(p.m_local); expr ref = mk_app(mark_explicit(mk_constant(real_n, section_ls)), section_args); p.add_local_expr(n, ref); } else { if (real_n != n) env = add_alias(env, n, mk_constant(real_n)); } if (is_theorem) { // TODO(Leo): delay theorems auto type_value = p.elaborate(type, value, ls); type = type_value.first; value = type_value.second; env = module::add(env, check(env, mk_theorem(real_n, ls, type, value))); } else { auto type_value = p.elaborate(type, value, ls); type = type_value.first; value = type_value.second; env = module::add(env, check(env, mk_definition(env, real_n, ls, type, value, is_opaque))); } return env; } environment definition_cmd(parser & p) { return definition_cmd_core(p, false, true); } environment abbreviation_cmd(parser & p) { return definition_cmd_core(p, false, false); } environment theorem_cmd(parser & p) { return definition_cmd_core(p, true, true); } environment print_cmd(parser & p) { if (p.curr() == scanner::token_kind::String) { p.regular_stream() << p.get_str_val() << endl; p.next(); } else if (p.curr_is_token_or_id(g_raw)) { p.next(); expr e = p.parse_expr(); p.regular_stream() << e << endl; } else if (p.curr_is_token_or_id(g_options)) { p.next(); p.regular_stream() << p.ios().get_options() << endl; } else { throw parser_error("invalid print command", p.pos()); } return p.env(); } environment section_cmd(parser & p) { p.push_local_scope(); return push_scope(p.env(), p.ios()); } environment namespace_cmd(parser & p) { name n = p.check_id_next("invalid namespace declaration, identifier expected"); check_atomic(n); return push_scope(p.env(), p.ios(), n); } environment end_scoped_cmd(parser & p) { if (in_section(p.env())) p.pop_local_scope(); return pop_scope(p.env()); } environment check_cmd(parser & p) { expr e = p.parse_expr(); level_param_names ls = to_level_param_names(collect_univ_params(e)); e = p.elaborate(e, ls); expr type = type_checker(p.env()).check(e, ls); p.regular_stream() << e << " : " << type << endl; return p.env(); } environment set_line_cmd(parser & p) { if (!p.curr_is_numeral()) throw parser_error("invalid #setline command, numeral expected", p.pos()); unsigned r = p.get_small_nat(); p.set_line(r); p.next(); return p.env(); } environment exit_cmd(parser &) { throw interrupt_parser(); } environment set_option_cmd(parser & p) { auto id_pos = p.pos(); name id = p.check_id_next("invalid set option, identifier (i.e., option name) expected"); auto decl_it = get_option_declarations().find(id); if (decl_it == get_option_declarations().end()) { // add "lean" prefix name lean_id = name("lean") + id; decl_it = get_option_declarations().find(lean_id); if (decl_it == get_option_declarations().end()) { throw parser_error(sstream() << "unknown option '" << id << "', type 'help options.' for list of available options", id_pos); } else { id = lean_id; } } option_kind k = decl_it->second.kind(); if (k == BoolOption) { if (p.curr_is_token_or_id(g_true)) p.set_option(id, true); else if (p.curr_is_token_or_id(g_false)) p.set_option(id, false); else throw parser_error("invalid Boolean option value, 'true' or 'false' expected", p.pos()); p.next(); } else if (k == StringOption) { if (!p.curr_is_string()) throw parser_error("invalid option value, given option is not a string", p.pos()); p.set_option(id, p.get_str_val()); p.next(); } else if (k == DoubleOption) { p.set_option(id, p.parse_double()); } else if (k == UnsignedOption || k == IntOption) { p.set_option(id, p.parse_small_nat()); } else { throw parser_error("invalid option value, 'true', 'false', string, integer or decimal value expected", p.pos()); } p.updt_options(); return p.env(); } static name parse_class(parser & p) { if (p.curr_is_token(g_lbracket)) { p.next(); name n; if (p.curr_is_identifier()) n = p.get_name_val(); else if (p.curr_is_keyword() || p.curr_is_command()) n = p.get_token_info().value(); else throw parser_error("invalid 'using' command, identifier or symbol expected", p.pos()); p.next(); p.check_token_next(g_rbracket, "invalid 'using' command, ']' expected"); return n; } else { return name(); } } static void check_identifier(parser & p, environment const & env, name const & ns, name const & id) { name full_id = ns + id; if (!env.find(full_id)) throw parser_error(sstream() << "invalid 'using' command, unknown declaration '" << full_id << "'", p.pos()); } // using [class] id (id ...) (renaming id->id id->id) (hiding id ... id) environment using_cmd(parser & p) { environment env = p.env(); name cls = parse_class(p); bool decls = cls.is_anonymous() || cls == g_decls || cls == g_declarations; name ns = p.check_id_next("invalid 'using' command, identifier expected"); env = using_namespace(env, p.ios(), ns, cls); if (decls) { // Remark: we currently to not allow renaming and hiding of universe levels buffer exceptions; bool found_explicit = false; while (p.curr_is_token(g_lparen)) { p.next(); if (p.curr_is_token_or_id(g_renaming)) { p.next(); while (p.curr_is_identifier()) { name from_id = p.get_name_val(); p.next(); p.check_token_next(g_arrow, "invalid 'using' command renaming, '->' expected"); name to_id = p.check_id_next("invalid 'using' command renaming, identifier expected"); check_identifier(p, env, ns, from_id); exceptions.push_back(from_id); env = add_alias(env, to_id, mk_constant(ns+from_id)); } } else if (p.curr_is_token_or_id(g_hiding)) { p.next(); while (p.curr_is_identifier()) { name id = p.get_name_val(); p.next(); check_identifier(p, env, ns, id); exceptions.push_back(id); } } else if (p.curr_is_identifier()) { found_explicit = true; while (p.curr_is_identifier()) { name id = p.get_name_val(); p.next(); check_identifier(p, env, ns, id); env = add_alias(env, id, mk_constant(ns+id)); } } else { throw parser_error("invalid 'using' command option, identifier, 'hiding' or 'renaming' expected", p.pos()); } if (found_explicit && !exceptions.empty()) throw parser_error("invalid 'using' command option, mixing explicit and implicit 'using' options", p.pos()); p.check_token_next(g_rparen, "invalid 'using' command option, ')' expected"); } if (!found_explicit) env = add_aliases(env, ns, name(), exceptions.size(), exceptions.data()); } return env; } cmd_table init_cmd_table() { cmd_table r; add_cmd(r, cmd_info("using", "create aliases for declarations, and use objects defined in other namespaces", using_cmd)); add_cmd(r, cmd_info("set_option", "set configuration option", set_option_cmd)); add_cmd(r, cmd_info("exit", "exit", exit_cmd)); add_cmd(r, cmd_info("print", "print a string", print_cmd)); add_cmd(r, cmd_info("universe", "declare a global universe level", universe_cmd)); add_cmd(r, cmd_info("section", "open a new section", section_cmd)); add_cmd(r, cmd_info("namespace", "open a new namespace", namespace_cmd)); add_cmd(r, cmd_info("end", "close the current namespace/section", end_scoped_cmd)); add_cmd(r, cmd_info("variable", "declare a new parameter", variable_cmd)); add_cmd(r, cmd_info("{variable}", "declare a new implict parameter", implicit_variable_cmd)); add_cmd(r, cmd_info("[variable]", "declare a new cast parameter", cast_variable_cmd)); add_cmd(r, cmd_info("axiom", "declare a new axiom", axiom_cmd)); add_cmd(r, cmd_info("{axiom}", "declare a new implicit axiom", implicit_axiom_cmd)); add_cmd(r, cmd_info("variables", "declare new parameters", variables_cmd)); add_cmd(r, cmd_info("{variables}", "declare new implict parameters", implicit_variables_cmd)); add_cmd(r, cmd_info("[variables]", "declare new cast parameters", cast_variables_cmd)); add_cmd(r, cmd_info("definition", "add new definition", definition_cmd)); add_cmd(r, cmd_info("abbreviation", "add new abbreviation (aka transparent definition)", abbreviation_cmd)); add_cmd(r, cmd_info("theorem", "add new theorem", theorem_cmd)); add_cmd(r, cmd_info("check", "type check given expression, and display its type", check_cmd)); add_cmd(r, cmd_info("precedence", "set token left binding power", precedence_cmd)); add_cmd(r, cmd_info("infixl", "declare a new infix (left) notation", infixl_cmd)); add_cmd(r, cmd_info("infix", "declare a new infix (left) notation", infixl_cmd)); add_cmd(r, cmd_info("infixr", "declare a new infix (right) notation", infixr_cmd)); add_cmd(r, cmd_info("postfix", "declare a new postfix notation", postfix_cmd)); add_cmd(r, cmd_info("notation", "declare a new notation", notation_cmd)); add_cmd(r, cmd_info("#setline", "modify the current line number, it only affects error/report messages", set_line_cmd)); register_calc_cmds(r); return r; } cmd_table get_builtin_cmds() { static optional r; if (!r) r = init_cmd_table(); return *r; } }